From 97c6be4e8dead31e1e9ba57dcbe972c4b2832897 Mon Sep 17 00:00:00 2001 From: SimonovaMI Date: Tue, 29 Apr 2025 16:31:44 +0300 Subject: [PATCH] First commit --- README.txt | 5 + design_viewer.ui | 2423 +++++++++++++++++++++++++++++++++++++++++ logo.ico | Bin 0 -> 18861 bytes project/__init__.py | 8 + project/controller.py | 472 ++++++++ project/main.py | 15 + project/model.py | 58 + project/view.py | 626 +++++++++++ requirements.txt | 5 + 9 files changed, 3612 insertions(+) create mode 100644 README.txt create mode 100644 design_viewer.ui create mode 100644 logo.ico create mode 100644 project/__init__.py create mode 100644 project/controller.py create mode 100644 project/main.py create mode 100644 project/model.py create mode 100644 project/view.py create mode 100644 requirements.txt diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..f42f8c0 --- /dev/null +++ b/README.txt @@ -0,0 +1,5 @@ +Для ознакомления с кодом проще всего запустить программу в PyCharm +1. создать папку с проектом +2. в папке с проектом создать папку project +3. в папку project поместить файлы с кодом +4. в папку с проектом разместить файл ui и ico diff --git a/design_viewer.ui b/design_viewer.ui new file mode 100644 index 0000000..f655a5d --- /dev/null +++ b/design_viewer.ui @@ -0,0 +1,2423 @@ + + + MainWindow + + + + 0 + 0 + 1920 + 1000 + + + + MainWindow + + + Qt::LeftToRight + + + + + + 200 + 30 + 454 + 41 + + + + + + + true + + + Открыть файл + + + + + + + Закрыть файл + + + + + + + + true + + + + 1410 + 80 + 501 + 251 + + + + QFrame::WinPanel + + + QFrame::Raised + + + 3 + + + + + 30 + 10 + 255 + 16 + + + + + 75 + true + + + + Настройки детекции событий + + + Qt::AlignCenter + + + + + + 13 + 68 + 261 + 22 + + + + QFrame::Box + + + Минимальная чувствительность, мВ + + + Qt::AlignCenter + + + + + + 14 + 127 + 261 + 22 + + + + QFrame::Box + + + Длительность защиты от Т волны, мс + + + Qt::AlignCenter + + + + + + 14 + 156 + 261 + 22 + + + + QFrame::Box + + + Порог чувствительности Т волны + + + Qt::AlignCenter + + + + + + 273 + 68 + 61 + 22 + + + + true + + + 4 + + + + 4.0 + + + + + 3.5 + + + + + 3.0 + + + + + 2.5 + + + + + 2.0 + + + + + 1.5 + + + + + 1.25 + + + + + 1.0 + + + + + 0.75 + + + + + 0.5 + + + + + + + 14 + 97 + 261 + 22 + + + + QFrame::Box + + + Максимальная чувствительность, мВ + + + Qt::AlignCenter + + + + + + 273 + 97 + 61 + 22 + + + + true + + + 3 + + + + 15.0 + + + + + 12.0 + + + + + 10.0 + + + + + 8.0 + + + + + 6.0 + + + + + 4.0 + + + + + 3.5 + + + + + 3.0 + + + + + 2.5 + + + + + + + 274 + 127 + 61 + 22 + + + + true + + + 2 + + + + 300 + + + + + 325 + + + + + 350 + + + + + 375 + + + + + 400 + + + + + + + 274 + 156 + 61 + 22 + + + + true + + + 0 + + + + 0.75 + + + + + 0.55 + + + + + 0.35 + + + + + 0.25 + + + + + + + 13 + 39 + 261 + 22 + + + + + 8 + + + + QFrame::Box + + + Длительность поиска максимума, мс + + + Qt::AlignCenter + + + + + + 273 + 39 + 61 + 22 + + + + + 8 + + + + true + + + 3 + + + + 120 + + + + + 125 + + + + + 130 + + + + + 135 + + + + + 140 + + + + + 145 + + + + + 150 + + + + + + + 274 + 185 + 61 + 22 + + + + + 0 + 2 + + + + true + + + 3 + + + + 500 + + + + + 750 + + + + + 1000 + + + + + 1250 + + + + + 1500 + + + + + 1750 + + + + + 2000 + + + + + + + 14 + 185 + 261 + 22 + + + + QFrame::Box + + + Максимальный размер RR интервала, мс + + + Qt::AlignCenter + + + + + + 274 + 218 + 61 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 14 + 218 + 261 + 22 + + + + QFrame::Box + + + Минимально регистрируемая ЧСС, Гц + + + Qt::AlignCenter + + + + + + 340 + 30 + 141 + 22 + + + + Размер окна, сек + + + Qt::AlignCenter + + + + + + 380 + 50 + 61 + 22 + + + + true + + + 2 + + + + 30 + + + + + 20 + + + + + 10 + + + + + 8 + + + + + 6 + + + + + 4 + + + + + 2 + + + + + 1 + + + + + + + 370 + 100 + 91 + 20 + + + + с фильтром + + + 1 + + + + без фильтра + + + + + с фильтром + + + + + + + 380 + 80 + 81 + 20 + + + + Вывод сигнала + + + + + + 350 + 120 + 141 + 20 + + + + Активный канал передачи + + + + + + 370 + 140 + 91 + 20 + + + + 2 + + + 2 + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + + + 350 + 160 + 141 + 20 + + + + Постоянная составляющая + + + + + + 370 + 180 + 91 + 20 + + + + без + + + 1 + + + + с + + + + + без + + + + + + + + 20 + 80 + 1371 + 111 + + + + + + + 20 + 190 + 1371 + 601 + + + + + + + 170 + 0 + 321 + 21 + + + + QFrame::Box + + + 0 + + + + + + Qt::AlignCenter + + + + + + 680 + 30 + 641 + 41 + + + + + + + + + + + + + + true + + + Старт + + + + + + + true + + + Стоп + + + + + + + true + + + Начать запись + + + + + + + true + + + Закончить запись + + + + + + + + + 780 + 0 + 201 + 21 + + + + QFrame::Box + + + 0 + + + Вывод сигнала по COM-порту + + + Qt::AlignCenter + + + + + true + + + + 1410 + 340 + 501 + 611 + + + + QFrame::WinPanel + + + QFrame::Raised + + + 3 + + + + + 170 + 30 + 201 + 22 + + + + Верхние пороги корзин, мс + + + Qt::AlignCenter + + + + + + 20 + 70 + 71 + 21 + + + + 250 + + + 350 + + + 5 + + + 250 + + + + + + 230 + 70 + 81 + 21 + + + + 300 + + + 400 + + + 5 + + + 400 + + + + + + 390 + 70 + 81 + 21 + + + + 350 + + + 450 + + + 5 + + + 450 + + + + + + 20 + 190 + 71 + 16 + + + + 10 + + + 0 + + + %p + + + + + + 0 + 100 + 171 + 22 + + + + Порог корзины фибриляций + + + Qt::AlignCenter + + + + + + 20 + 120 + 81 + 21 + + + + 10 + + + 60 + + + 10 + + + + + + 220 + 150 + 91 + 16 + + + + + 75 + true + + + + Корзины + + + Qt::AlignCenter + + + + + + 10 + 170 + 81 + 20 + + + + Фибрилляция + + + Qt::AlignCenter + + + + + + 140 + 170 + 71 + 20 + + + + ЖТ2 + + + Qt::AlignCenter + + + + + + 270 + 170 + 61 + 20 + + + + ЖТ1 + + + Qt::AlignCenter + + + + + + 390 + 170 + 71 + 21 + + + + Норма + + + Qt::AlignCenter + + + + + + 160 + 190 + 71 + 16 + + + + 10 + + + 0 + + + %p + + + + + + 280 + 190 + 71 + 16 + + + + 10 + + + 0 + + + %p + + + + + + 410 + 190 + 71 + 16 + + + + 5 + + + 0 + + + %p + + + + + + 140 + 10 + 255 + 16 + + + + + 75 + true + + + + Настройки корзин + + + Qt::AlignCenter + + + + + + 10 + 50 + 91 + 22 + + + + Фибриляция + + + Qt::AlignCenter + + + + + + 210 + 50 + 91 + 22 + + + + Тахикардия 2 + + + Qt::AlignCenter + + + + + + 370 + 50 + 91 + 22 + + + + Тахикардия 1 + + + Qt::AlignCenter + + + + + + 210 + 100 + 131 + 22 + + + + Порог корзины ЖТ2 + + + Qt::AlignCenter + + + + + + 370 + 100 + 121 + 22 + + + + Порог корзины ЖТ1 + + + Qt::AlignCenter + + + + + + 230 + 120 + 81 + 21 + + + + 10 + + + 60 + + + 10 + + + + + + 390 + 120 + 81 + 21 + + + + 10 + + + 60 + + + 10 + + + + + + 20 + 260 + 191 + 22 + + + + Кол-во ступеней ВВ терапии + + + Qt::AlignCenter + + + + + + 240 + 260 + 41 + 21 + + + + 1 + + + 8 + + + 8 + + + + + + 19 + 288 + 191 + 22 + + + + Текущая ступень ВВ терапии + + + Qt::AlignCenter + + + + + + 240 + 288 + 61 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 20 + 316 + 191 + 22 + + + + Минимальная энергия, Дж + + + Qt::AlignCenter + + + + + + 240 + 317 + 81 + 21 + + + + 2 + + + 19 + + + 5 + + + 10 + + + + + + 20 + 344 + 191 + 22 + + + + Максимальная энергия, Дж + + + Qt::AlignCenter + + + + + + 240 + 345 + 81 + 21 + + + + 20 + + + 40 + + + 40 + + + + + + 20 + 374 + 191 + 22 + + + + Текущая энергия, Дж + + + Qt::AlignCenter + + + + + + 241 + 373 + 61 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 20 + 400 + 191 + 22 + + + + Полярность + + + Qt::AlignCenter + + + + + + 170 + 400 + 151 + 22 + + + + + 0 + 2 + + + + true + + + Положительная + + + 0 + + + + Положительная + + + + + Отрицательная + + + + + Биполярная + + + + + + + 240 + 430 + 81 + 21 + + + + 100 + + + 1000 + + + 200 + + + + + + 20 + 430 + 201 + 22 + + + + Время слепоты после разряда, мс + + + Qt::AlignCenter + + + + + + 20 + 460 + 201 + 22 + + + + Таймаут отключения , с + + + Qt::AlignCenter + + + + + + 240 + 460 + 81 + 21 + + + + 10 + + + 3600 + + + 10 + + + 30 + + + + + + 20 + 490 + 201 + 22 + + + + Размер буффера редетекции + + + Qt::AlignCenter + + + + + + 240 + 490 + 81 + 21 + + + + 4 + + + 10 + + + 10 + + + + + + 20 + 520 + 201 + 22 + + + + Порог буффера редетекции + + + Qt::AlignCenter + + + + + + 240 + 520 + 81 + 21 + + + + 2 + + + 8 + + + 8 + + + + + + 220 + 210 + 91 + 16 + + + + + 75 + true + + + + Терапия + + + Qt::AlignCenter + + + + + + 320 + 260 + 171 + 341 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + 0 + 0 + 161 + 22 + + + + Текущий режим терапии + + + Qt::AlignCenter + + + + + + 10 + 20 + 151 + 31 + + + + + + + + + + 10 + 50 + 121 + 22 + + + + Текущий подрежим + + + Qt::AlignCenter + + + + + + 10 + 70 + 151 + 31 + + + + + + + + + + 0 + 100 + 161 + 22 + + + + Мгновенный период + + + Qt::AlignCenter + + + + + + 50 + 120 + 51 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 130 + 161 + 22 + + + + Усреднёный период + + + Qt::AlignCenter + + + + + + 50 + 150 + 61 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 160 + 161 + 22 + + + + Счётчик Vs + + + Qt::AlignCenter + + + + + + 30 + 180 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 190 + 161 + 22 + + + + Счётчик Vp + + + Qt::AlignCenter + + + + + + 30 + 210 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 440 + 161 + 22 + + + + Счётчик Vn + + + Qt::AlignCenter + + + + + + 10 + 470 + 111 + 22 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 220 + 161 + 22 + + + + U на батарее, В + + + Qt::AlignCenter + + + + + + 30 + 240 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 280 + 161 + 22 + + + + U на конденсаторе, В + + + Qt::AlignCenter + + + + + + 30 + 300 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 250 + 161 + 22 + + + + U на батарее, % + + + Qt::AlignCenter + + + + + + 30 + 270 + 111 + 16 + + + + QFrame::Box + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 30 + 230 + 231 + 21 + + + + + 75 + true + true + + + + Текущий режим работы стимулятора + + + Qt::AlignCenter + + + + + + 270 + 230 + 221 + 22 + + + + + 8 + + + + true + + + без стимуляции + + + 0 + + + + без стимуляции + + + + + нормальный + + + + + с принудительной стимуляцией + + + + + + + 20 + 550 + 201 + 22 + + + + Коэффициент усиления + + + Qt::AlignCenter + + + + + + 240 + 550 + 81 + 21 + + + + 0 + + + 255 + + + 0 + + + + + + + 20 + 790 + 1371 + 161 + + + + + + + 20 + 10 + 141 + 61 + + + + + 10 + + + + Чтение из + + + false + + + + + 10 + 20 + 111 + 17 + + + + + 8 + + + + файла + + + + + + 10 + 40 + 101 + 17 + + + + + 8 + + + + com-порта + + + true + + + + + + true + + + + 1340 + 40 + 151 + 23 + + + + Перезагрузка + + + + + + 20 + 950 + 1371 + 16 + + + + Qt::Horizontal + + + + + + 1020 + 0 + 361 + 21 + + + + QFrame::Box + + + 0 + + + + + + Qt::AlignCenter + + + + + + 500 + 0 + 75 + 23 + + + + Старт + + + + + + 580 + 0 + 75 + 23 + + + + Стоп + + + + + + 1500 + 40 + 111 + 22 + + + + Запись на SD-карту + + + Qt::AlignCenter + + + + + + 1620 + 40 + 51 + 20 + + + + нет + + + 0 + + + + нет + + + + + да + + + + + + + 1780 + 40 + 91 + 20 + + + + 433 + + + 0 + + + + 433 + + + + + Bluetooth + + + + + + + 1680 + 40 + 101 + 22 + + + + Режим передачи + + + Qt::AlignCenter + + + + + + + + PlotWidget + QGraphicsView +
pyqtgraph
+
+
+ + +
diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..fd4d4cf19a882fe2a46751d56fd11c5dddcdf6c1 GIT binary patch literal 18861 zcmZ@<1yfwj5?$Qg-6goYLvVKp?(XhR2u^Sb?jGD7g1ZFQ0E_G5y#0Q_>#eQ5RderD z&yIB8b4~*Q5a7>$1_2-kWXJ&kxZvv;Rb?3zL_$RHC5oJ^l=^?I|9#w>Qx zSmgc*!M}Sq@O(Fl#CBo`AIX&g6(oz+-+9sCTTT(;rKXQc)C$RiOoU8|G?T~a3JBkh z_yJ($%iO7R96>EJ|s)KWB zM9l^-fqTkSGL2mUVZ%?*YfHuQ>KNuGc)0oCx{sWQ8Pmaa9k2=p&&5CxDfwlWzU|q z@%30%R^$VF;n?dkdaM(h%u#2b%<9U4GSZ>G0S)!|4&`wMH(nfg2Q!ihFSdRgw(U|!{QLIj|fB@cJ`wTc1u9s8J7>}x4x z&?$s3be+su(Da0+l9-mKd{D&Y7NkK{c(#7Wr)|*pR<@wYh1uC$4TjQCafXAzuk@l- z{Vl8hM`r(SY>-^`xrhaw;6p}ft!EF_U<`0MCvpUjl03 znuST<>$(x;69^y+zEqs_&Q`zA7xrVL08U?IeKq9|2Uetq6n7)z^Qj76>T7pJ4}>lz>1e|i(TF!9(>oiKC)!?3p9n1k)OJ+UB8G1G^ zt)AH5l>wl{u&GGA$f)D7a#HFKm87Nk^Hna(jviSZD)OsKHz6#V!)J$4A_J64-sLg0 z$wtDYg<&51*3S@GXa+m(IapO6=+;ZNgCque#*83Ke`u3J7*;~1@j4x04Wv1zGiQZ) zjeH%~5bU}#pb;j07^n3VUkT(7`GUGVVD0(AL5owC3u2X_Hz$N{b2iIgLP%RSdYLf9 zQ6$wOIW}mbw#>#8+nOEdw6UX7Kj8}|8*PVm8TI;$Kwhkn>1E!J8VsCsWd$es94 zL1+?gh}N0E)>Vf{bn%$U5#4SJ^#?)Egh9eF45)&Qvp63s#C}ap&=nJ(T@hnxV&X&i zCJHqw=HSTBY2E1_(+{!hQT*Zy3)vOTE&$!rAh^B_{KOa)FFmF*Q}y(3lO-2=mZ+oZ zSJ3d}c&M$${d>03^&X*l+*bTP=26)a?@37&b9h4jcIyQ-^t1XUl_x7iN$GekT*udo zln@k2I63e4c+iVC!+C_O%X>xmHU?ki`D z>4nBvVt#R%xQdXhF;o`9dSA4W7@Th`sNRvRPR%Jx(SGQh32_$WHB~ zmmiv+xe=sjn|@SF-L)}%Yzn6fEp*)JPKjS9^=uuikI*8yF&t$Y25&%aA>E=7O%Mr*qHK5UBfTnse7iWu$Trq7|k<5a-twiKW ze%4i6VJcgQ`3_;(8g`XIH$_zoEX$LBsCXLTH4XY;RxRVD3kwl|IRz&TRZBu5nH=-@ z&&w`A?0|OpfxcuaO#k?Ay(GvIUS9H-pI!L?NCF zeU2KvYA&O;W8*>Ac)LBs5?lN9u#*pRlK~q?h!k6h%N5PUnY5z0(I8S}12;jYOeCBH zm#Q4DBM?7Y@ZD#ZCyq#c5b3p~a=K8p`*BO(`_bz(rEEQ_k+Dguh=((RQ3a(l~B!r)*JdTo&(y2pvkHtK>%Oz(lfo({n`Y7+Yl+| zwNYZ`yXiG=aNUKNv_NiGL4MczJ;I^xhy)BSQ!aAL@m@y#kUtsG0o>E{H^)=unFz`i$EJe*`(fc>E5P#%~WaKIwp=s_bZ8G z2J@A%-7r~QB;HQQi}WOgL`%uHehz1blGnX9=OMJ9HPKf62hVF@R|60$ zQ$+3Cbcx(De@c%J#-JL$C`+lR8#q;aYGivA;Pc-DRxw-*oT*7Da3Rk2LXy>;lZ_f1 zGS?2DLyf^xU%$6g!j?#MCyev8o8GqoMi!OPOJ;=-swlcFhNY(gjElAoh zlIP?j@v<&7wors+BKB6>Hh?4&rENDG|v0i0_?0^h}0?1ykKrpNv<JCMQcZlrOVax^~EElqAIcyYwzY&z~A@Nl%v-jqp};Cw`&Knw7;TC z0vyfGZSZvp;wsC?OUk`NJREB^i=zgT8FZU4i~>k{?=5zyL*l;D>PSPuuK$#c8F%(kM}N)8hmrYg2LU*?ilo5y?f~L(MIDT z@^{e1HlW#Hd#|D2E>YTD#>ZP|pF_QuN1d|~HWK^ugUM*)BXe*;JqTTPwc6*6zKy9n z3Fp5Yn)e}=enl0zWpbi8b&y&qhL0eXif0aDoGTBo8bug)7kHeA^DA$>P}jTafkY`; z?e-Ki{|a*9c8b8xr5mJK4)l0`|Duv7#}wJp5_8`B=PDe6fMM$mlwT2QZY-h2?XXp< zfu&>KD>-`QK`5dRHKsCw)CKbLIfM{)>LRe3Ie8uZe01}9)Ue;j1z46?eQ>$>I507tD6ZrF!i_F1*%*&_8v<1|;r&M>ODt z`Zs$ji1p~gzLXo0t2iFv4LxsDqhja^qeut^+JcGwH#F!QxUqZSS_1t63KqLSSkADp z@(2(-^3(8)pTiH^aE{#EY&FJ;>EENw6zoF7>aXizD0{lqa1^~lO0*Y4#U7aYLK4?6 z5W_@@jXjkI%i2@6d>I@F_h#GUKe+NsvqWG^P1~vRJ$VlOFUK2FhDX3lsf_6j<|AbP<8oFgd2JALRkTC*jwFb8|&4v$=aSk+H~P(NVu)HMf;h?h3IEI zXs?u4N=!Jejs}oedeC#kxHmY`nlI?yiAcv_r?s&?zv;!Bgx_N7f_jJ=vTrUx81PxV zE$BL#FmT%RLz`~VbhOnR*sMq3vWU`fznEfurBYEJz0-q27Et0!EWDtm+AEDGM5uKr zN(WV@61{ivJ?fVz8d2-e2UR02e_%Sm0O2}cm{p~KIgYUM-A0Z%SKz9|)GH~?5c9i~ z>61yjCr^zoTAxp;w|3iCN}!x~`;se>aLm~PRD`5zAS7uATJMczra(t9K`i zhvZAOr_x)o{#dNl*kdnqv6CQ3)@_^ag?&{M^67`m^H|jDhm*$OJ#*QwmX;IEZZ7|d z)y%qo0opC^3yT+|q2%;Pa^DVS78gCnh(F2n2SQhJwRw|i*J_ySNU%|$MIm1tKqsYD zMqxqy-V4{d-p{${`}fghax2x*v*+>}c!g;Od3?4Du`Y>+I}j{_#QI4}NaP0xkt6I2aNqp=8mhk1;wBL{AA^u2MeC-{@527S7UE3)&4f`sS(j zDL+=-TjUuq<)V(Emxhl*!m}3f(iSlNM zAdsv;`OCpqK~I-`1BpR(YvbPwIa;NbQ?T+Bh(LoUrq|(D&G{1D_GsrZj2g4D(S?#P z5D+EhW3-M|0zS+rqZ8a0eUFbhrZ)x^d?+|{W|6F`gzb$?FblsqTBiBDjRJRqUTGaR zva)}6))~R(CI4!NGufaFiSx*0tGgl!qJ|Xo8rZlRT5+7notl_?yYsET$o;(C6d$Xk zn~|Nl{1Dt7pKbo^Ogl3fbMkf3r98l0e`uygP7V=Y?|MiDF_he1&vUXv)f9F@?#?&&^ z{jF5PC?pxK`2{~;ilyZS`$Wn!QD1S8+qR~!Q2Mg(S01OvL`OzR?xeEyG^tJv=XD51 zo&GfhHoJDJ7TgGm@MW5=SuDUwRK$KKgnuBLFLFRL0!yUnUy18xHFWl1(twLQbi~0i zBGXZyKSV2ycJ0*zjzZaK2nV+ws~Lgt`3~WqkEuo7+Gyi+;274b_ZFf2{WW)xzUI&# zPMst&8VqKdJP1t3x>Dho-#+IeFDP%ab`A{vup^;MAVW$NUHUi^(M1q_A&C1E9pjo8 z3M4!tNK}q@5;s%}U$Cyi&+88DaSPVXZRnAIq25z`9)h^RDvPLIT4!_Nf;S@vL%O#eyx{#q$aIK|X;Kt|v%J8uIeHX}r5zXr6sNMw#3r za+f!A9W17sYrHUpzokDv!)FKb-%MZY{k{2#h3NNic~SSuP*!=%QZ;>6U!!bo*#dtT zcVC=YSPnpeWF?q(ql?VlTm>2F@l_z5T;{I0qT(V7+ZD$m z`~0+Il$NlRcL!&JQ4=rG1@#`8$`*d?ptUF8t+`$&TfLw~+isx^C4@ zJ?{C=2Q0E9owq2l`uOvsu%B@jLAGQathprCR2JeQZRr%7cOvCLb0y)QSkJ~}uf#oR zR1M$5DisqTwad2)sdp&8YS}-UNw?S_rTC>lAdH$`z zkvGM<%gJuFZ=g*G<7bK2QMBuquInM|=941}F3>t91`HFO3MTtKBV?v$9ym(JcUrb2~pBB#9p@zT0^7@P*RizO`XsDE0Rl zqJ-HfQv_~;Fpxb1O7DChNE5ZndT)4n20P;${K?Mw0ax`9`EX?k8Y_9#zZTQPbvM#) zj4Q%T@SS=gL_YBqv*$kS+b%96^eo`5YGFe>|_01r0? zVN2g3!xIi|36PdLB>IW|@yLpe?|`Im6{!<&eU8)+$csD`@#d3;*L6oVc(dIV>-y+C!Bb>g^ddwp0u&iaE%$=Gxl+eSlSXbi`W{Hu> zqr4%J-{wEeY3Q0b9lfWH{;@CjG;xf%s6x*W_Ic;7+w|3Km!EkdfEoJ@Dazy)8lSy8 z9|KUKqc8sJvH3JK6Tc`YCtpzWOS=7voYn)8v7@Mdrw0Pp%9+|h%^xWD+~p!p6*zs0 zFs{Bh&VAyhJP{I;}u^O{!CS>nY`-#;6@aM@>?>QYuWXsZ9IlXQ-eG}hS) z584eyR1WMWbTZg%;#hh>VOaayi^Ss;IcWASB&gv@5ERUmTwWb7N-8eHQOgc(tcw_Y zU;0?uBmQmHl5B91Rm_TTDi4$MW74G)#u~aAT`?zxV|1qnn$0v98cTse>*vmIo(;=C zV%d)mSM?!b^BmWY4ToN}Ze#(-XKjNC65Q}NuU$Scw(N3Sv)J(x72?}sUugo`^~Fou zGOEY6i}(@eP>8AyrdxNt{g%egD5>{1+xuq6alZsTAqahZUk~!cOA!3){($1rjQL2i zFq=T4kx?v5ogR4#vbSH$t2XDOtY^p6Do#7Y8$2cjsiX=y}$aoV+LP{DG*P1s|7-~A#c zZmj@{KbrVE{U5iPyR9>CZUf9a-=o2VnKUrlnU%4O;>c?!6&gW1=P%V}=de06G z(H==)pn`b=!l-&F{&vuy^G?JKv`mvXsZL;gY;q?{)v{|LODeq~;Gj>ZUaagOM!{E@cl2K6?sgT4VhU@(_NAU-cAm;c zBamy7N{?YfyX4g*8PW6i?nFwp+h1Zj* znelo0rKQ2j#R)zpQCPS{`T`P@qfyUL8Fq7Ey0;mm3|K7hpN`%A8J~?VV(h-+P1iBYxb^tM zPIxsRwwJ5j)$}#k{6Y zy07kY7Kn0>61$$EIq@e%W#@=^j65{4>DUqg)r6DQV_>|al)ppkb&kLup0G}s7*#+E ze2#V9N?~@y5OUQAI0^Nek0dm%Q$f_@MWIG;9&4>$&)daU`p5f7QT{OG;y!&fkxjf) z4u&Mc5Tie&b}t?1hnSe1ow9j9?-i6={lWQ1RX3jsKb@}Gg_cRbpkw}5YFq5Bd4pBB zow$7Bz|xJt%7b4{Te)+LEH>igZjNyz5pL3t6COa%zZsMeji{iasQ> z)Z{!`QjJ!U^W~(4oD+b@+)?P<182E>y^C3D-x}SmHgWp+VtVFOQ5gZHJkx*3uHPYK zsHyS`9p&4!;n6>+xy_ztCO%h0M8sy27mIK+M5^c(NesYJIBm9>klHo8*vw3; z0M$hrh}98ig5Uu-1~&=SF_agU?DphC#AURw zeg2PV!`Ky3nz7|Bv}>8J?u0&9p!%KpgHeUX&km;PLR)(oH)3tAgn4ezcjgld8zm+~ z+8fkROVZa7bvR0|PsLdM^*vC<+t@q2Xuf@-tD=^cGf}jnuBR8e{B``N+w6kbC)f8A`I48 z<((s6u0hkd4^?aZEiC3Z-ihsyHv%VS@}!}5DP&QXbGWa23+R^!P2(&ks)E1CGeapo zFZY)WD#7ZG6IuWKzmPu?>|>h`y>O}sAZVa{IHO>DuU#LQK6<8y-vDN_&JWiORDW|Jqd(Jzm>d)3h` zWkK4mj)rO!_-oE*{>x-d))(P8VHp7Uz3KdK${uMFOmWJeWN&9#X+T~6mw(%hNZY!o zNdNkIKLD3<1vo2nh1UwN$LBFOg&G8-BMVA{7*TYHCW^*dg-<2D)7o*_;IzkwdCiPoTG0_EQPO7V~@Wf z)KYF#87xU!Ye6 z6+!0_W6SN;cDcD({VM;$Jxw2q$71YkUUaGV(CV)gOl<}<*F<8d(SyFA!jFp#U~^kv zdJIvFO_64PSdqMs55n7chUs{DO4rPXq@BTU#|(L4Vca;P(!uPwc6BR|P_JNOPtp9^ z-^Xjy>lMaz$hWgZYH5BuDfhM(-ob}f1E@IZJn8ay%B~doOirbN!;Yri-Y%*An4%&H z#*gUg!V1k`DnSL%itxIk)!Sxy=QCzHOz?w}&u(ujnVoTBU-;`~*lNb94I_3!^^r={ zXl`(T(Z!BChv}h7MQtcJt97nXg_t2}6C>|=Kby|2Ltr;2j3Yt9W1(ks+6khsL#MDP zWgJ;P#-Z@YIJ>|l?tsx~p=(Cm_N|80wVqWFFXzSVKPndD!UGZ={*BCyVxx*4VCEex ze{(sY%yiG|4v~uQYep6^s;(OTVNNKRuk*{gpNx)nF|@-PqI`BJ6!GcUd4Qzt@dw3~ zFJvYgsg)5Nj#hT}P&Dlow1KjiIIqVaF#>Bg__Nmj8&1_=wb|#T+H9^1(@lI~Cxa5R z3`2iUo}ikj$#IrFmow|9Y5h#k-xQq)0)=b_H5q4qSC}oDWO;DqGkhE2Vza|vQaYWz z?&>dz5~T7pkK2zE2n&}#(uqvAa}Nd2QU$7JW_yrJapX@31Y~aw^3sX|8c7|{HiMnb zHj*@VH%WIOLsG(m-jLru94$|u18d7GXzj=^`5lK(WjzX&8C%DYB%_D6+D?!IFVBZ( zqlgGg&D?j{G)bc;fT9?rYD4{OTz1ub*hOhw_%+SI<395- z`DiQ`1U%P~^kTw4jzMzL|Mf5XI|=)oji&gbjV3qhSasUE!8Ouf*GeH@wk2oykR-?$g~^Og%cP8Cjj^{M`WdEJ%w3wAmL zFaIxbK18%_7+z-0w$)ROpWXOW9;@+euWKLW?K+IDD~DT(&a6Y-3jd< z^d9|rWxnZ%-P?!YSCiBCS6_q%hR4RRuc$s*{C2h2hpRQ~ih z2OU!N`~avAK>?1=SbN}B9K-i95h8H;D6Z*A>HE?G*RPbL-Kt>%=ud#rG4 znpqBddg_?jqB4ZucxTudC$b)b1X357Gvf3$5}B&91bzcaajb&h{^l>1+X*c$q+roN zSWjw{n0HSwj2NR5M{Pw*3GgRmq-E79&Oh}A+#IavMXaus^zT#{;FQuV2o0l)3ASH3 zT~y{|o4i6~@-o*gwO}=WlEV?B#be-spS^|d8knp<^_(ut}t_8xI*j*mwnm~*l4|7DM) zM)&Oqd}3KX-R9+z4c2?RnV z=Fe@u9f)d1+OtXlF#!GHpb0klgvx&N!-mX3JL?uG@9|+_9nh;;6ei-hXRUKmZ*b4S zX!KBnedr|-y=#Ph8K@v3y3 zws0s(XI=KraR6A<5IR!zpA&&_rNO**K6s4Of{r^)P4@&c$JOv-SBY|cUnvvz_{wRO z2Jk7OB-lp^%cSEywOaPSB*UMJQlgRXdLMQ#v?wW{*|az_^H0)L%y0$q(t6&;eT=?6 zU&G}qO_wpo1eByAK(vQ&B-%bzO#~YdErrWBL^125d z9?uAz?v~eu(OC8GeJ)&pgfeQ@T%jbf>|el( z!Q3AfBIaz=Xf%Y8{G4ENs%QO4BL^T>+a_uHbWAq%a>3@;ZcaUO)iva8%d?l;clt1_ zy-@ulPQNzDAI4zlMwcXK=@OR~Go5EpDK_Wx^+vHvT z5(Y}c&BfRHtB*k(3rE?PTlGIv(=W=&I%NW%v;*K@V@*egXX*`Y91YeNr|jQF*&$EH z9X-3t!%$=rzV!2h=~4Icf0kroKpB>9k*EFQ>yfdvZVgxU%Of1&H9!36$7Tc(0dper z4S_R|A9aJ<<7V0{K_1-a?dn98J@RNP^A?7JoE4LPEYkhCQanwfp`GftHqj@hfFq(2YyC3j2HAbMgzCxAp zG1k+rK4Gh`CnTL2D9>2<$h64^_i_3e9<5=#SsaWmrd^im(qmL14D+D;n7LuKZvN00 zbN-JpV({e+rg+22gWor7^vW!6rdcId3pei!;lBJP7OZNO-g}Zm@Q<6+sJg{MV`Q&Q zEb?|Lcn{L`V}08)roU)mho_t2OEGy|?rMH~h!OHBSP#U(Y5RPWA&dC@@XX}LO28gC zb)Rl|ctzU(IT=PlUmNvf(Pb6$sk|?8EdB8`amU{bPzz>h?MOtN;wD@&>Q|L=IVtJ_ zu>ynb&wdS+KWl-E)PJp=;Z8?82f#=tg!Kp(yhzp9o>|^3_VO7Gr*szaXy(mnyG_aZ z>?&t+BawC(x1aS*LogysDg6u?=wfmCMokS z>3KT;bxEI@32Z8}O%Q7YSxTCR(^c?sLWp0hbbZT}DE|nI3pu#12uwpw9AdS#C<< z=hu%Qnv`2zvjrg?q_1=W$CMH(qz!wPY=hp7$Ez>fD}?G0hXyIvnhmEph)4#e<2)=) z{knk#OFw(Cz9q;ESBk_*apMjoH4{al;e08BS&}>9Dc5Qj^(68pggjA9O&xsPV#ovo z;u8!HcQwce3eXE&1P1lwAAD24&6fRyLsudsw-Wg4E)6wQvyxlNOY>dsD4%BXNA81__8U)W=^^h`YL>-g%F z(&H!CBL1_e#HVM(vQo^bIDK+WVaZ{yy`y~{kRV|t6{?|H1rqZ4@>+W z0yIajKoe9hC_9En26>;CZmg2I3vo*iYb-^iQBIfEJm2MkyAtc!Z3+&@MPq z#Xo#_x#UfL7a`iINIa{-46+!im#?}X(+HCwo_RX0H_j5Zty8#N565`YQvSd|26on^ zBz5JZMUrG)&?~w(!!&U&YYPi1EC*`P*$yKjU76Pno2{U zY(us^qQ0wNSX23UyZdj`M@5qHqvJ17zl9zC*5TZ)5Oq0CF)(l&jg(SG3&Uyst|h=N zhdR+-GqhASh+(Uzq67wgJT=}to6I05n|h@q2Oq+>c=BHMP=v5Za#bOBA>n;R9ssjY zO~>4)hOVcUE(X)ajc`OaSt!cjh zykVti%R#0lURfii*St&Mw5HKiH?B9aq%UdgG!tTq7xzlnzNx8M^Fb!8FoGM3rdgq# zBB!{|lPai-8Y>Q@QHIMcD_eUU$eZJ7OgAiaxsy|7q}~NS!$3BQKoXp~8FF$6(4GEs zVebZElvwtA_fbg#Z}pDC#ltl#Jok#$t58f%T;##JrTuAA_7S8{guoR}ORe5Clc#0CB<%F;KvfF<$$cPud+ z75v)xxRdxOY)KTJFaQsH(2^={u#7giih*J40Pr}6w{1Hc@-2buwZzE=t5izR@DXjv z@<>(P=4ME)6F-3-yX@eqt~rp)(|7%<$U%+(kPWk_PU!dp(Ox-g)pI4d7%W^Q3Jl9m zqiG%|C6i8ST>C89aASt{y@+&oY{sSi*B-f#xz?y4eTjS4?&6P$N3z09|{>B*MIWXY*~!9lJyfq zJz8Po4AmQ@uk3+ojsuleoa_tTJ`_d#FKIiq)qBf&Y%FA>ai=-v)+X=;I>jZ(K~EZd z?$R$1Q0{ewM%!vEKOmon3T#n@K;PiM?nMWqK`HV(8%O?yyw^ToL#bY!I3i=`T7he% z!XVLcT;1YPoC;UPsxg3?)Xr?=fi_7}U+W^{aY!FPB_hVV?~1ZtcdbucZ?J&SRr7ht zP<7pm>j2MUs+cEt_Y17UcA>$iSixP%@BJP?AKGsyJHnT0or0?MxE}CLq+@4S^jmF3 z(mgK%U3$lstb<{N;ZhuZLEwPMY3>Z)Um(*Mgw=gph8;5&=XN;U1zgGVaA*?_+`N@AA@B33<@3RvDS+!1 z?PdJxHkm>iEZy1sA!_7!84Y&BX=o(&dIhe*>E~hhZw@oZsV>HtjjN2@waD1(iEp&z zHeg3Ku17hZZroM<GJ(I(#%K9AD(^AsUT6&(KrlKSjvX)uCI9t#v2{c4LYbG!#COxbu?tw4_Jo|~+K zi&)>+tFXI+U~B$DGfp=afPJgu{J;xF0xME8lYtRuWx$r>7nx%Ao7svjdCo=n?oS+)}*RT77}C{xiYR&P`CweC;DB+7vs zW7uP+Vsrv~a@XL56>!|u5SUT)?n`CXr!XAFynCjCH)lT%-3a`r5Al)PpGN+Dy=ux(&K zlH+5h4$nmemxEI)>TT3tRnzRU6zp!v+!{QiNYqX@ggp=NyQ4$UowiX7TS%n#%R1-F zJQmtO344Q6@yzAoLMOT78U^k~RA8j4k?Ey1>wjAUdZjLUryB|m)#g*w*FkYcP)&<0 zHnZv~R2^PsPjDy1m$TqE;tz$dv_>k&PEf?JV`o~0NvlvYKMMD>|NLPCwPppqrnA=jGvOA0pa`wcSJCyW1wzI>TGr~ATR;IRi|R959x_)xEFDs1)q5U zj=}gGlfN?H`qsEW6P~!qSeO@qW{wS$Z>nCzEGt9#K?J>T(9pT>px$s*waUx>Z5I7^ zR`(E?EP{qW(Q}SxKosBw>4!pL%_f0({^t28ZAEqX`&U&pm_l@}rEy_p6^(u-s-!23 z9W94^S!f>OrJc8-vAq~L6Dfny-4{nyUC5tfo_jzt8GzH4IJMcoNQ#i4Fj-w`BZDHv zE>-x3f^&MX0%n{OF#&}LIUG)QU~FT+B=|0c!{y={Xk>to-1}x6$|{1`WW^OcJ;AMxLdZkkK{b-lpWvT(iVC5b4-Ss- z1^FiD_?d#=61V)15V&BphJTW_x8gkAvgb2j6+PC}@w()3Mp|nWa zY#DR==$-q2lMe}>IXh}xUM@R+U7*599xH_%A-L>BAq@mbCv?B;7&nP~!Zgv@ax1St z8Eyesb!Y5#q%g ziMO|bMuAGubwMW7kcIG5y{M=-4544VecbXl-ud<#>Q31UX~6E1xLXaz*K5F)Ol5W{npNyt^ki4uDa3k0ZN-n^O&J{{H>8l%-Shc_Kl zY+XTrBvH`XMO(9ecerd{zLDRQ33Cz7nuL+}FrMT92hkqHbeNyCUfT~mPT|(UD2wiC zjwggh9FIC`D0fx^lKD~7f(0dt7O70oZ)!Zy0Q46;m8nx5#r5#xe9~}`X7T~|!%aYF>_TvVw!7?Z1f0bN&JkxC-Ml-{- zoHxlFb7ncTIUjSZoWdSTs0?A`k?Fza7-bP2Ddee%Ld~HpgyF^0)5BCUnq!P4&z8`z z@&5X}|G%I2{r~>szQ6b9zQ5P?{an|D&xLx7%uV^_?YLkFhiXT>^-SsYoAG?-0`Fo6 z)Z@^l&(^J?pD&?~k!CVW>gKBCo5CNd{}83f*`FNbIf&cgMx4fV%9r6pvxg-&+{PwP zYf8I4H>gss*;XT#wtE3M*z(BoNS|>?=5k2pS18;>bvu=Kky#pGj}n>PF_}UI+EE*? z%H(~WxT=0PG8|RI+#UY)XUlDW{4^(UCYz01g*2-M4px5+v5MATH6DhQXwyqP!?>z~ zxP3JUPc?kt*a?})$YTD)%jTtY1OOJyDs4rn##zXHN1t89!jVU_7X#C^Es3ub2Ic9B zDp`}Q8=WO}p{#?k+9o3TXf<+A+y#mV$+WiOw+Ntf`*G)^3CTHTKLjE0$|`1s1a0Ys#54ysfVNW6GwI2ChWMwo?E%a@Dv(ARLtypJ;G)Z zfhb>TYnV0s`UZ=m9mS99Wi&4YJ=vc(1}2`v)rpP1%;Zjp78KWxkMW)NqnnAD1GPHl ztkD!}+&Bw)6WG_QVZxVo3fCvLhyxleqbtunm@ikr1a1f%qqwkx9k$1g!UTz%YSaIAzcN%p-Uv<@#Iy9|>hakXcoK0JiF$bA zE@$0eJww{5SWYORd)%D9y?{#5JBbb!0?v?6dE!1uORT<^Jw6sKzh#k2G}L`TcF@|b zsdKMfy!{BJN83*H@O;qZ&C$?DxA)*E9U1YUr5VU~+}+(M2whdTktM`<1qr^o!i)Wg z8JezJIFj{ngjUDM2PrUsxtRRYi+8Q~VK62o=zcEkfF((Bbwhye0e`2O{&)}2r%oL^ zz)m|>fBqM7{~JV0w| zP)TH8eklEPuM^f^nu%TP>FPOLtx4EvG&{)YK9DPA`hyAVs3FhAznvDY)rggJz7#L( z(r|5LHNUEB$hdFIA8I_qwNW`|Czq?i-VvOVoKGzD^ot+ypIV0n^hr_yhN1(1`>Nlo zVi51{s;ZPz>~Tppee>p$3UFPK@@-O9SNkiF^i_`T=Q1miqhz1qu32Q@5dM7B+@0ww z`deBdme%k9RU^{_gNkkzyuD6Mj`^0}T05X0q1kz_GkBmtVsfyE40<{dRO^+iMO)#| z{^X$gI)>MWS#tt&?;r?e@G<_6A>a7|`ML*+HYm8clBDSKOP+J(->xjH*HL>Pm|%Rs#9c-1zkCx50f6&df;HaEGDMTu z*Za{8m%Z3NRh!5LAO8@#G-&wmNSi~75<||;PSUsS19Cg0PgRK=0QaRO*W*G{@63&J z`T9*s6?-FGXNF+merp4k!+yZ%O-m**PgZpcy=f!o1=B(~!r0b;9Q1Dzm-t42iaRiX zHyOz|l5l0)DNGRa7Y6(4P#hN2`pi$W+a_j8WR`zeR6|Va(7a~LPUpo4R#W)lllLLi zeP<%`4=?&CAvtUu3NL}7XCXNQ@hdWwwv@{Kj%z2DgZrv$T{IAg=;J1qfCbU#%Ri%P zCW~9#*I@B{!1epPolOz;U16tI6twT&CTbtd8v#7OXyB=;VHKcCHPNp`6CbUfKP%y? zzZm4IepZ?+2^zzr^gY|R5Qx`;;ZMHed2utfHpE-+?-@u}Gd68+Rc9B>fBbH@nb%&J z78`WW&>lI^?7_QE%viH}(e)>i4N{fWF=&a6wK`a)*U6pjmh`eMvKB_vSQm(mD*6yW zGdn_UoOj|*&k}b+wIcbws%OUNT!(np8h1i4$ot8+C6tXa0j7-aqQ-R1_g}COTkEwc zZnStp_56p#*(Zk|{5jR|Rc>JFy~XVsA^4*XGCOW&OgrV=`k(<#l|5+cw)+MC&owG< zAB&{*5G4cf-u4OM84Fw5GT#avF%pWaC*YHl+%efN@iC)yVVR_^Big6?ZuVTK&1AW` z#id4^t*s6|H_=i{KZU;K0u;#SDRVkqHuYO;6Cne^N*8*s)QF~ZfB~iKOgi$QobE3= zR(`H&_Z3q1j)x1=YU$?u9k8w#po>(0=!!%Q&K8CO^#8`1)qE9fPEg2Rhy7WG!lf2_ z@t?kEoN%|ts9R9|aLTki*gw^KU$ z2R^K_nv3TNB9Sq^**(V~QV!=XuRCMSyv#-4*WGIde($1p6GZP6!J|v{2Fj*ExgqDN z9U{zWnt|3D!A3o2L#RO4`;> 24), int(((param & 0xFF0000)) >> 16), + int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if type_param == int: + if size == 4: + param_in_bytes = [int(((param & 0xFF000000)) >> 24), int(((param & 0xFF0000)) >> 16), + int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if size == 3: + param_in_bytes = [0, int(((param & 0xFF0000)) >> 16), + int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if size == 2: + param_in_bytes = [0, 0, int(((param & 0xFF00) >> 8)), + int((param & 0xFF))] + if size == 1: + param_in_bytes = [0, 0, 0, int((param & 0xFF))] + + for _ in param_in_bytes[::-1]: + init_mes.insert(index, _) + + mes = bytes(init_mes) + return mes + + +def state_packet(send_mode, serial, param_code, param, size, type_param): + """ + Отправка пакета параметров + :param send_mode: режим передачи данных 433 - 0(Bluetooth - 1) + :param serial: канал передачи + :param param_code: код для идентификации параметра для передачи(задано протоколом взаимодействия) + :param param: передаваемое значение + :param size: размер передаваемого значения + :param type_param: тип передаваемого значения + """ + if serial.isOpen(): + serial.writeData(create_mess(send_mode, param_code, param, size, type_param)) + + +class DataClass: + """ + Класс модель данных для отображения на графике + """ + + def __init__(self): + # переменные для работы с потоком данных с устройства + self.data_rx = [] # список в котором храним данные + self.draw_cnt = np.uint16(0) # x-координата точки в текущий момент + self.data_length = 0 # кол-во отсчетов для отрисовки графика по x-координате + self.pac_count = 0 # счетчик для распознавания пакетов + self.counter = [] # счетчик небитых пакетов + + # переменные для работы с записанным файлом + self.i = 0 # счетчик для чтения файла + self.chunk = [] # считанный массив из файла + + # переменные для отрисовки графиков + + # Сигнал по которому определяют ЧСС ЭКГ сигнал если у нас кардиограф + # Правый желудочек (используется основной канал) + self.ECG_RA = [] # np.zeros(self.data_length, np.int16) + # позитивно отраженный сигнал + self.ECG_RA_pos_sig = [] # np.zeros(self.data_length, np.int16) + # динамический порог рассчитываемый в зависимости от нижнего и верхнего порогов + self.ECG_RA_din_threshold = [] # np.zeros(self.data_length, np.int16) + # нижний порог порог для метода треугольников при его пересечении считаем ЧСС событие + self.ECG_RA_min_threshold = np.int16(threshold_MIN) + # верхний порог для метода треугольников выше него мы не берём данные + self.ECG_RA_max_threshold = np.int16(threshold_MAX) + self.Last_RR_poz_rel = [] # время до прошлого события + self.text_list = {} # список label на графике для динамического обновления + self.last_period = [] # мгновенный период для отрисовки label + self.rr_now = [] # есть ли удар сердца в текущий момент + self.last_QRS = [] # тип последнего события Vsense = 0 нормальное сокращение + # Vpace = 1 пришлось стимулировать сейчас не используем + # Vnoise = 2 шумы в QRS комплексе(пока не реализовали) + + # переменные для отрисовки label (обозначение на графике событий) + self.V_print = 0 # необходимость отображения события + self.draw_cnt_last = 0 # x-координата события + self.draw_last_period = 0 # мгновенный период для отображения + self.draw_last_QRS = 0 # тип последнего события для отображения + + # переменные для передачи параметров стимулятору и отображения текущих значений + self.dc_cut = 1 # вывод сигнала с постоянной составляющей или без + self.channel = 3 # активный канал передачи + self.signal = 0 # вывод сигнала с фильтром или без + self.sd_card = 0 # запись на sd-карту + self.hv_volt = 0 # напряжение на конденсаторе + self.bat_volt = 0 # напряжение на батарее + self.bat_pers = 0 # напряжение на батарее в процентах + self.spi_pot_set = 0 # коэффициент усиления + self.Work_Mode_pacemaker = 0 # текущий режим работы стимулятора + self.redet_num = 0 # размер буфера редетекции + self.redet_bad = 0 # порог буфера редетекции + self.standby_timer = 0 # таймаут отключения + self.hv_blind_time = 0 # время слепоты после разряда + self.max_energy = 0 # максимальная энергия + self.min_energy = 0 # минимальная энергия + self.hv_step_number = 0 # кол-во ступеней высоковольтной терапии + self.fibr_max_tres = 0 # порог корзины фибриляций + self.tachy_2_tres = 0 # пороговое значение для тахикардии 2ст + self.tachy_1_tres = 0 # пороговое значение для тахикардии 1ст + self.fibr_tres = 0 # пороговое значение для фибрилляции + self.max_search_time = 135 # длительность поиска максимума + self.square_coef = 0.75 # порог чувствительности Т волны + self.square_time = 350 # длительность защиты от T волны + self.max_treshold = 8.0 # максимальная чувствительность + self.min_treshold = 2.0 # минимальная чувствительность + self.max_time = 1250 # максимальный размер RR интервала + self.fibr_cnt = 0 # кол-во событий фибрилляции (прогресс-бар) + self.norm_cnt = 0 # кол-во нормальных сокращений (прогресс-бар) + self.tachy_1_cnt = 0 # кол-во событий тахикардии 1ст (прогресс-бар) + self.tachy_2_cnt = 0 # кол-во событий тахикардии 2ст (прогресс-бар) + self.hv_step_cnt = 0 # текущая ступень ВВ терапии + self.last_period_stat = 0 # мгновенный период + self.filt_period = 0 # усредненный период + self.now_energy = 0 # текущая энергия + self.Vn_cnt = 0 # счетчик Vn + self.Vp_cnt = 0 # счетчик Vp + self.Vs_cnt = 0 # счетчик Vs + self.sub_mode = 0 # текущий подрежим терапии + self.terapy_now = 0 # текущий режим терапии + + def clear_data(self): + """ + Очистить модель хранения данных + """ + self.__init__() + # self.data_rx.clear() + # self.data_length = 0 + # self.counter = np.zeros(self.data_length, np.int16) # np.zeros(self.data_length, np.int16) + # self.pac_count = 0 + # self.draw_cnt = 0 + # + # self.i = 0 + # self.chunk = [] + # self.update_data(self.data_length) + + # # Сигнал по которому определяют ЧСС ЭКГ сигнал если у нас кардиограф и производная фильрованного ФПГ если часы + # # Правый желудочек (используется основной канал) + # self.ECG_RA = [] + # + # # позитивно отраженный сигнал + # self.ECG_RA_pos_sig = [] # np.zeros(self.data_length, np.int16) + # + # # динамический порог рассчитываемый в зависимости от нижнего и верхнего порогов + # self.ECG_RA_din_threshold = [] # np.zeros(self.data_length, np.int16) + # + # # нижний порог порог для метода треугольников при его пересечении считаем ЧСС событие + # self.ECG_RA_min_threshold = np.int16(threshold_MIN) + # + # # верхний порог для метода треугольников выше него мы не берём данные + # self.ECG_RA_max_threshold = np.int16(threshold_MAX) + + # счетчик пакетов для вывод на график(визуальный контроль) + + def update_data(self, data_length): + """ + Создание нулевых массивов для хранения в них даных для отображения + :param data_length: длина массива (сколько значений поместится в окно) + """ + self.ECG_RA = np.zeros(data_length, np.float32) + self.ECG_RA_pos_sig = np.zeros(data_length, np.float32) + self.ECG_RA_din_threshold = np.zeros(data_length, np.float32) + self.counter = np.zeros(data_length, np.float32) + self.ECG_RA_min_threshold = np.zeros(data_length, np.float32) + self.ECG_RA_max_threshold = np.zeros(data_length, np.float32) + self.Last_RR_poz_rel = np.zeros(data_length, np.uint16) + self.last_QRS = np.zeros(data_length, np.uint16) + self.rr_now = np.zeros(data_length, np.uint16) + self.last_period = np.zeros(data_length, np.uint16) + + def parse_list(self, data_in): + """ + Расшифровка полученной серии данных + :param data_in: ссылка на объект модели данных + """ + self.data_rx = self.data_rx + list(data_in) + + if len(self.data_rx) >= NORMAL_LENGTH * 2: # парсить имеет смысл при длинне пакета больше минимальной + while self.pac_count <= len(self.data_rx) - NORMAL_LENGTH: + if (self.data_rx[self.pac_count] == Start_byte) and ( + self.data_rx[self.pac_count + NORMAL_LENGTH - 1] == Stop_byte) \ + and ((len(self.data_rx) - self.pac_count) >= NORMAL_LENGTH + 0): + self.draw_cnt += 1 + if self.draw_cnt >= self.data_length: + self.draw_cnt = 0 + self.text_list = {} + + self.parse_part_from_1_to_20_byte() + + if self.draw_cnt > 1: + self.ECG_RA_min_threshold[self.draw_cnt] = self.ECG_RA_min_threshold[self.draw_cnt - 1] + self.ECG_RA_max_threshold[self.draw_cnt] = self.ECG_RA_max_threshold[self.draw_cnt - 1] + self.last_period[self.draw_cnt] = self.last_period[self.draw_cnt - 1] + else: + self.ECG_RA_min_threshold[self.draw_cnt] = self.ECG_RA_min_threshold[self.data_length - 1] + self.ECG_RA_max_threshold[self.draw_cnt] = self.ECG_RA_max_threshold[self.data_length - 1] + self.last_period[self.draw_cnt] = self.last_period[self.data_length - 1] + + self.parse_part_from_23_to_30_byte() + + if self.rr_now[self.draw_cnt] == 1: + self.V_print = 1 + self.draw_cnt_last = self.draw_cnt + self.draw_last_period = self.last_period[self.draw_cnt] + + # в конце парсинга шагаем по массиву + self.pac_count += NORMAL_LENGTH - 1 + + del self.data_rx[0:self.pac_count] + self.pac_count = 0 + self.pac_count += 1 + + def parse_part_from_1_to_20_byte(self): + """ + Парсинг с 1 по 20 байт + """ + self.counter[self.draw_cnt] = shift_or(int.from_bytes(self.data_rx[self.pac_count + 1], "big"), + int.from_bytes(self.data_rx[self.pac_count + 2], "big"), + int.from_bytes(self.data_rx[self.pac_count + 3], "big"), + int.from_bytes(self.data_rx[self.pac_count + 4], "big")) + + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 5], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 6], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 7], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 8], "big", signed=False))) + self.ECG_RA_din_threshold[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 9], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 10], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 11], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 12], "big", signed=False))) + self.ECG_RA_pos_sig[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 13], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 14], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 15], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 16], "big", signed=False))) + self.ECG_RA[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + self.Last_RR_poz_rel[self.draw_cnt] = shift_or_16( + int.from_bytes(self.data_rx[self.pac_count + 17], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 18], "big")) + + int1 = int.from_bytes(self.data_rx[self.pac_count + 19], "big") + self.terapy_now = int1 & 0x3 + self.sub_mode = (int1 & 0x1C) >> 2 + + int1 = int.from_bytes(self.data_rx[self.pac_count + 20], "big") + self.last_QRS[self.draw_cnt] = int1 & 0x3 + self.draw_last_QRS = int1 & 0x3 + self.rr_now[self.draw_cnt] = (int1 & 0x4) >> 2 + self.Work_Mode_pacemaker = (int1 & 0x60) >> 5 + self.signal = (int1 & 0x80) >> 7 + + def parse_part_from_23_to_30_byte(self): + """ + Парсинг с 23 по 30 байт (передача значений в зависимости от остатка от деления счетчика) + """ + if self.counter[self.draw_cnt] % 10 == 0: + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False))) + self.ECG_RA_min_threshold[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + self.min_treshold = self.ECG_RA_min_threshold[self.draw_cnt] + int1 = (shift_or(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + self.ECG_RA_max_threshold[self.draw_cnt] = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + self.max_treshold = self.ECG_RA_max_threshold[self.draw_cnt] + + if self.counter[self.draw_cnt] % 10 == 1: + int1 = ( + shift_or(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False))) + self.square_coef = struct.unpack('f', int1.to_bytes(4, 'big'))[0] + + if self.counter[self.draw_cnt] % 10 == 2: + self.square_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + self.max_search_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 3: + int1 = int.from_bytes(self.data_rx[self.pac_count + 24], "big") + self.sd_card = (int1 & 0x4) >> 2 + self.channel = int1 & 0x3 + self.dc_cut = (int1 & 0x8) >> 3 + self.spi_pot_set = int.from_bytes(self.data_rx[self.pac_count + 25], "big") + self.bat_pers = int.from_bytes(self.data_rx[self.pac_count + 26], "big") + self.bat_volt = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False))) + self.hv_volt = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 4: + self.last_period[self.draw_cnt] = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False))) + self.last_period_stat = self.last_period[self.draw_cnt] + self.max_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 5: + self.Vs_cnt = ( + shift_or_24(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False))) + self.Vp_cnt = ( + shift_or_24(int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 29], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", signed=False))) + self.Vn_cnt = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + )) + + if self.counter[self.draw_cnt] % 10 == 6: + self.fibr_tres = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False) + )) + self.tachy_2_tres = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 26], "big", signed=False) + )) + self.tachy_1_tres = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", signed=False) + )) + + if self.counter[self.draw_cnt] % 10 == 7: + self.fibr_cnt = int.from_bytes(self.data_rx[self.pac_count + 25], "big") + self.tachy_2_cnt = int.from_bytes(self.data_rx[self.pac_count + 26], "big") + self.tachy_1_cnt = int.from_bytes(self.data_rx[self.pac_count + 27], "big") + self.norm_cnt = int.from_bytes(self.data_rx[self.pac_count + 28], "big") + self.fibr_max_tres = int.from_bytes(self.data_rx[self.pac_count + 29], "big") + self.filt_period = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False))) + + if self.counter[self.draw_cnt] % 10 == 8: + self.now_energy = (shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 27], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", + signed=False))) / 10 + self.min_energy = (int.from_bytes(self.data_rx[self.pac_count + 26], "big", + signed=False)) // 10 + self.max_energy = (shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 29], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", + signed=False))) // 10 + int1 = int.from_bytes(self.data_rx[self.pac_count + 23], "big") + self.hv_step_cnt = int1 & 0xF + self.hv_step_number = (int1 & 0xF0) >> 4 + + if self.counter[self.draw_cnt] % 10 == 9: + self.hv_blind_time = ( + shift_or_16(int.from_bytes(self.data_rx[self.pac_count + 23], "big", signed=False), + int.from_bytes(self.data_rx[self.pac_count + 24], "big", signed=False))) + self.standby_timer = ( + shift_or(int.from_bytes(self.data_rx[self.pac_count + 27], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 28], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 29], "big", + signed=False), + int.from_bytes(self.data_rx[self.pac_count + 30], "big", + signed=False) + )) // 1000 + int1 = int.from_bytes(self.data_rx[self.pac_count + 25], "big", signed=False) + self.redet_bad = int1 & 0xF + self.redet_num = (int1 & 0xF0) >> 4 diff --git a/project/main.py b/project/main.py new file mode 100644 index 0000000..ecd3ace --- /dev/null +++ b/project/main.py @@ -0,0 +1,15 @@ +""" +Запуск программы + Два режим работы: + 1. чтение из com-порта, отображение, запись данных, передача настраиваемых параметров в стимулятор, + управелние перезагрузкой + 2. чтение данных из файла и отображение +""" + +import project.view + + +app = project.view.app +window = project.view.Ui() +window.show() +app.exec_() diff --git a/project/model.py b/project/model.py new file mode 100644 index 0000000..902a9f4 --- /dev/null +++ b/project/model.py @@ -0,0 +1,58 @@ +"""Получение сырых данных из различных источников. Работа с файлами""" +import datetime +import os.path + +# from PyQt5.QtSerialPort import QSerialPort + + +# def on_read(): +# +# +# +# # создание соединения с устройством для передачи данных +# serial = QSerialPort() +# serial.setBaudRate(115200) +# serial.readyRead.connect(on_read) + + +def get_data_bin(file_name, data_in): + """ + Получение данных из бинарного файла + :param file_name: файл + :param data_in: объект модели данных для отрисовки + :return: массив данных + """ + f = open(file_name, 'rb') + size = os.path.getsize(file_name) + chunk = f.read(size) + data_in.data_length = int(size / 32) + data_in.update_data(data_in.data_length) + f.close() + return chunk + + +def write_file(): + """ + Запись файла + """ + # создание папки data(если её нет) + if not os.path.isdir("data"): + os.mkdir("data") + + file_name = datetime.datetime.now().strftime('%d%m%y%H%M%S') + file = open('data/' + file_name + '.txt', 'wb') + return file, file_name + + +def write_in_file(file, rx): + file.write(rx) + + +def close_file(file): + """ + Закончить запись и закрыть файл + """ + if file != "": + file.close() + + return "" diff --git a/project/view.py b/project/view.py new file mode 100644 index 0000000..7eaf853 --- /dev/null +++ b/project/view.py @@ -0,0 +1,626 @@ +"""Отображение данных""" + +import sys +import os.path +import datetime +import time + +import numpy as np + +from PyQt5.QtCore import QIODevice +from PyQt5.QtSerialPort import QSerialPortInfo, QSerialPort + +from PyQt5.QtGui import * +from PyQt5 import QtWidgets, uic, QtGui +from PyQt5.QtWidgets import QFileDialog +from PyQt5 import QtCore +from pyqtgraph import TextItem +import pyqtgraph as pg +import project.controller as my_data +from project import mes + +# выбор режима чения из файла или из com-порта +MODE_COM_PORT = False +MODE_FILE = False + +# объявили экземпляры класса, для обработки данных из файла или из com-порта +data_IN_FILE = my_data.DataClass() +data_IN_COM = my_data.DataClass() + +# создание приложения +app = QtWidgets.QApplication(sys.argv) + + +class Ui(QtWidgets.QMainWindow): + """Класс отображения данных""" + + def __init__(self): + # создание графического отображения + super(Ui, self).__init__() + uic.loadUi('design_viewer.ui', self) + self.setWindowTitle("Pacemaker viewer") + self.setWindowIcon(QtGui.QIcon("logo.ICO")) + + self.counter_com_port = [] # координата x для вывода графиков + + self.point = 0 # координата, откуда начинать отрисовку, если используется полоса прокрутки?? + self.file = "" # файл + self.file_name = "" # наименование файла + + # Создание графиков + self.graph_init() + + def set_up_controls(): + """ + Настройка элементов управления в визуале + """ + self.OpenB.setVisible(False) + self.CloseB.setVisible(False) + self.Play.setVisible(False) + self.Pause.setVisible(False) + self.horizontalScrollBar.setVisible(False) + self.horizontalScrollBar.valueChanged.connect(self.on_scroll) + self.radioButton_work_mode_com_port.toggled.connect(self.check_work_mode_viewer) + self.OpenB.clicked.connect(self.on_open) + self.CloseB.clicked.connect(self.on_close) + self.Write_file.clicked.connect(self.write_file) + self.Close_file.clicked.connect(self.close_file) + self.Read.clicked.connect(self.on_start) + self.Stop.clicked.connect(self.on_stop) + self.Restart.clicked.connect(self.restart) + self.SD_card_Box.currentTextChanged.connect(self.set_SD_card) + self.Send_mode_Box.currentTextChanged.connect(self.set_send_mode) + self.ComL.addItems(self.update_list()) + self.horizontalScrollBar.valueChanged.connect(self.on_scroll) + self.RA_scale_BOX.currentTextChanged.connect(self.on_scale_change) + self.Low_V_spinBox.valueChanged.connect(self.set_fibr_cnt_max) + self.Low_V_spinBox_3.valueChanged.connect(self.set_tachy_2_cnt_max) + self.Low_V_spinBox_4.valueChanged.connect(self.set_tachy_1_cnt_max) + + self.RA_max_time_ms_BOX.editTextChanged.connect(self.set_RA_max_time_ms_BOX) + self.RA_min_sensitivity_BOX.editTextChanged.connect(self.set_RA_min_sensitivity_BOX) + self.RA_max_sensitivity_BOX.editTextChanged.connect(self.set_RA_max_sensitivity_BOX) + self.RA_square_time_ms_BOX.editTextChanged.connect(self.set_RA_square_time_ms_BOX) + self.RA_square_coeff_BOX.editTextChanged.connect(self.set_RA_square_coeff_BOX) + self.RA_all_time_ms_BOX.editTextChanged.connect(self.set_RA_all_time_ms_BOX) + self.Signal_Box.currentIndexChanged.connect(self.set_Signal_Box) + self.Channel_Box.currentIndexChanged.connect(self.set_Channel_Box) + self.DC_cut_Box.currentIndexChanged.connect(self.set_DC_cut_Box) + + self.High_Tf_spinBox.valueChanged.connect(self.set_High_Tf_spinBox) + self.High_Tt2_spinBox.valueChanged.connect(self.set_High_Tt2_spinBox) + self.High_Tt1_spinBox.valueChanged.connect(self.set_High_Tt1_spinBox) + self.Low_V_spinBox.valueChanged.connect(self.set_Low_V_spinBox) + + self.Work_Mode_pacemaker.editTextChanged.connect(self.set_Work_Mode_pacemaker) + self.hv_step_number_spinBox.valueChanged.connect(self.set_hv_step_number_spinBox) + self.min_energy_spinBox.valueChanged.connect(self.set_min_energy_spinBox) + self.max_energy_spinBox.valueChanged.connect(self.set_max_energy_spinBox) + self.hv_blind_time_spinBox.valueChanged.connect(self.set_hv_blind_time_spinBox) + self.standby_timer_spinBox.valueChanged.connect(self.set_standby_timer_spinBox) + self.redet_num_spinBox.valueChanged.connect(self.set_redet_num_spinBox) + self.redet_bad_spinBox.valueChanged.connect(self.set_redet_bad_spinBox) + self.Spi_spot_set_spinBox.valueChanged.connect(self.set_Spi_spot_set_spinBox) + + set_up_controls() + + # создание соединения с устройством для передачи данных + self.serial = QSerialPort() + self.serial.setBaudRate(115200) + self.serial.readyRead.connect(self.on_read) + + self.send_mode = 0 + + # Создание таймера для отрисовки графиков + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.timerEvent) + self.timer.start(20) + + # Создание таймера для отображения параметров стимулятора + self.timer_LS = QtCore.QTimer() + self.timer_LS.timeout.connect(self.timerEvent_param) + self.timer_LS.start(2000) + + def write_file(self): + """ + Открытие на запись файла + """ + self.file, file_name = my_data.write_file() + self.Write_file.setStyleSheet('background-color: green; color: white') + self.Fname_w_label.setText(os.path.abspath(file_name)) + + def close_file(self): + """ + Закончить запись и закрыть файл + """ + self.Fname_w_label.setText('') + self.Write_file.setStyleSheet('background-color: grey; color: black') + self.file = my_data.close_file(self.file) + + def restart(self): + """ + Перезагрузка устройства + """ + param = 1 + my_data.state_packet(self.send_mode, self.serial, 0x1D, param, 1, int) + + def timerEvent_param(self): + """Запуск по таймеру, для синхронизации параметров стимулятора""" + if MODE_COM_PORT: + self.synchronise_param(data_IN_COM) + if MODE_FILE: + self.synchronise_param(data_IN_FILE) + # отрисовка + app.processEvents() + + def synchronise_param(self, data_in): + """Синхронизация параметров стимулятора""" + self.SD_card_Box.setCurrentIndex(int(data_in.sd_card)) + self.Signal_Box.setCurrentIndex(int(data_in.signal)) + self.Channel_Box.setCurrentIndex(int(data_in.channel)) + self.DC_cut_Box.setCurrentIndex(int(data_in.dc_cut)) + self.RA_max_time_ms_BOX.setEditText(str(data_in.max_search_time)) + self.RA_min_sensitivity_BOX.setEditText(str(data_in.min_treshold)) + self.RA_max_sensitivity_BOX.setEditText(str(data_in.max_treshold)) + self.RA_square_time_ms_BOX.setEditText(str(data_in.square_time)) + self.RA_square_coeff_BOX.setEditText(f'{data_in.square_coef: .2f}') + self.RA_all_time_ms_BOX.setEditText(str(data_in.max_time)) + self.High_Tf_spinBox.setValue(data_in.fibr_tres) + self.High_Tt2_spinBox.setValue(data_in.tachy_2_tres) + self.High_Tt1_spinBox.setValue(data_in.tachy_1_tres) + self.Low_V_spinBox.setValue(data_in.fibr_max_tres) + self.hv_step_number_spinBox.setValue(data_in.hv_step_number) + self.min_energy_spinBox.setValue(data_in.min_energy) + self.max_energy_spinBox.setValue(data_in.max_energy) + self.hv_blind_time_spinBox.setValue(data_in.hv_blind_time) + self.standby_timer_spinBox.setValue(data_in.standby_timer) + self.redet_num_spinBox.setValue(data_in.redet_num) + self.redet_bad_spinBox.setValue(data_in.redet_bad) + self.Work_Mode_pacemaker.setCurrentIndex(data_in.Work_Mode_pacemaker) + self.Spi_spot_set_spinBox.setValue(data_in.spi_pot_set) + + def timerEvent(self): + if MODE_COM_PORT: + self.draw_data(data_IN_COM) + if MODE_FILE: + scale = self.get_scale() + data_IN_FILE.data_length = scale * my_data.DATA_RATE + data_IN_FILE.parse_list(data_IN_FILE.chunk[data_IN_FILE.i:data_IN_FILE.i + 640]) + data_IN_FILE.i += 640 + self.graph_1.setXRange(self.counter_com_port[0], self.counter_com_port[0] + scale) + self.graph_2.setXRange(self.counter_com_port[0], self.counter_com_port[0] + scale) + self.graph_3.setXRange(self.counter_com_port[0], self.counter_com_port[0] + scale) + self.draw_data(data_IN_FILE) + + # отрисовка + app.processEvents() + + def on_start(self): + """ + Старт обменя с com-портом + """ + global MODE_COM_PORT + data_IN_COM.clear_data() + self.serial.setPortName(self.ComL.currentText()) + self.serial.open(QIODevice.ReadWrite) + self.serial.flush() + self.serial.write(mes.encode()) + self.on_close() + MODE_COM_PORT = True + self.graph_init() + scale = self.get_scale() + data_IN_COM.data_length = scale * my_data.DATA_RATE + data_IN_COM.update_data(data_IN_COM.data_length) + self.counter_com_port = list(map(lambda x: x / my_data.DATA_RATE, range(0, data_IN_COM.data_length))) + + def graph_init(self): + """ + Создние графиков + """ + # Создание графиков + self.curve10 = self.graph_1.plot(pen=(0, 0, 255)) + self.curve20 = self.graph_2.plot(pen=(0, 0, 0)) + pen_din_tres = pg.mkPen(color=(255, 0, 0), width=1) + self.curve21 = self.graph_2.plot(pen=pen_din_tres) + pen_peak = None + self.curve22 = self.graph_2.plot(pen=pen_peak) + self.curve23 = self.graph_2.plot(pen=pen_peak) + pen_min_tres = pg.mkPen(color=(0, 255, 0), width=2, style=QtCore.Qt.DashLine) + pen_max_tres = pg.mkPen(color=(0, 0, 255), width=2, style=QtCore.Qt.DashLine) + self.curve24 = self.graph_2.plot(pen=pen_min_tres) + self.curve25 = self.graph_2.plot(pen=pen_max_tres) + self.curve26 = self.graph_2.plot(pen=(0, 0, 0)) + self.curve30 = self.graph_3.plot(pen=(0, 0, 255)) + + def edit_graph(graph): + """ + Форматирование полей графиков + :param graph: поле графика + """ + graph.showGrid(x=True, y=True) + graph.setBackground('w') + # делаем поле графика нечувствительным к мышке + graph.setMouseEnabled(x=False, y=False) + graph.hideButtons() + graph.getPlotItem().setMenuEnabled(False) + graph.addLegend(enableMouse=False) + + edit_graph(self.graph_1) + edit_graph(self.graph_2) + edit_graph(self.graph_3) + + def graph_clear(self): + """ + Очистка графиков + """ + self.graph_1.clear() + self.graph_2.clear() + self.graph_3.clear() + + def on_read(self): + """ + Чтение серии данных + """ + rx = self.serial.readAll() + if self.file != "": + my_data.write_in_file(self.file, rx) + data_IN_COM.parse_list(rx) + + def draw_data(self, data_in): + """ + Отрисовка данных + :param data_in: модель данных + """ + self.curve10.setData(self.counter_com_port, data_in.ECG_RA) + self.curve20.setData(self.counter_com_port, data_in.ECG_RA_pos_sig) + self.curve21.setData(self.counter_com_port, data_in.ECG_RA_din_threshold) + self.curve26.setData(self.counter_com_port, np.zeros(len(data_in.ECG_RA))) + self.curve24.setData(self.counter_com_port, data_in.ECG_RA_min_threshold) + self.curve25.setData(self.counter_com_port, data_in.ECG_RA_max_threshold) + self.curve30.setData(self.counter_com_port, data_in.Last_RR_poz_rel) + self.Vs_cnt_label.setText(str(data_in.Vs_cnt)) + self.Vp_cnt_label.setText(str(data_in.Vp_cnt)) + self.Vn_cnt_label.setText(str(data_in.Vn_cnt)) + self.U_batt_label.setText(str(float(data_in.bat_volt / 1000))) + self.U_batt_p_label.setText(str(data_in.bat_pers)) + self.U_cap_label.setText(str(float(data_in.hv_volt / 100))) + self.now_energy_label.setText(str(data_in.now_energy)) + self.filt_period_label.setText(str(data_in.filt_period)) + self.last_period_label.setText(str(data_in.last_period_stat)) + self.hv_step_cnt_label.setText(str(data_in.hv_step_cnt)) + + self.progressBar_f.setValue(data_in.fibr_cnt) + self.progressBar_t2.setValue(data_in.tachy_2_cnt) + self.progressBar_t1.setValue(data_in.tachy_1_cnt) + self.progressBar_n.setValue(data_in.norm_cnt) + + if data_in.terapy_now == 0: + self.terapy_nowB.setStyleSheet('background-color: green; color: white') + self.terapy_nowB.setText("Поиск корзин") + if data_in.terapy_now == 1: + self.terapy_nowB.setStyleSheet('background-color: red; color: white') + self.terapy_nowB.setText("Терапия фибрилляции") + + if data_in.terapy_now == 2: + self.terapy_nowB.setStyleSheet('background-color: yellow; color: black') + self.terapy_nowB.setText("Терапия сильной тахикардии") + + if data_in.terapy_now == 3: + self.terapy_nowB.setStyleSheet('background-color: yellow; color: black') + self.terapy_nowB.setText("Терапия слабой тахикардии") + + if data_in.sub_mode == 0: + self.sub_modeB.setStyleSheet('background-color: green; color: white') + self.sub_modeB.setText("Терапия неактивна") + if data_in.sub_mode == 1: + self.sub_modeB.setStyleSheet('background-color: blue; color: white') + self.sub_modeB.setText("Зарядка конденсатора") + + if data_in.sub_mode == 2: + self.sub_modeB.setStyleSheet('background-color: yellow; color: black') + self.sub_modeB.setText("Редетекция") + + if data_in.sub_mode == 3: + self.sub_modeB.setStyleSheet('background-color: red; color: white') + self.sub_modeB.setText("Стимуляция") + if data_in.sub_mode == 4: + self.sub_modeB.setStyleSheet('background-color: grey; color: black') + self.sub_modeB.setText("Терапия не сработала") + + # так удаляются элементы + # стирание элементов спереди + all_items = self.graph_1.items() # Получаем все элементы + text_items = [item for item in all_items if isinstance(item, TextItem)] + for text_item in text_items: + if data_in.draw_cnt / my_data.DATA_RATE - 0.02 < text_item.pos().x() and data_in.draw_cnt / my_data.DATA_RATE + 0.1 > text_item.pos().x(): + self.graph_1.removeItem(text_item) + + if data_in.V_print: + data_in.V_print = 0 + if data_in.draw_last_QRS == 0: + self.text = pg.TextItem('Vs\nT:' + str(data_in.draw_last_period)) + self.text.setColor('green') + self.text.setFont(QFont('Arial', 7)) + self.text.setPos(data_in.draw_cnt_last / my_data.DATA_RATE, (0 if data_in.dc_cut else 15)) + self.graph_1.addItem(self.text) + if data_in.draw_last_QRS == 1: + self.text = pg.TextItem('Vp\nT:' + str(data_in.draw_last_period)) + self.text.setColor('red') + self.text.setPos(data_in.draw_cnt_last / my_data.DATA_RATE, (0 if data_in.dc_cut else 15)) + self.graph_1.addItem(self.text) + + def on_stop(self): + self.close_file() + global MODE_COM_PORT + MODE_COM_PORT = False + self.graph_clear() + # Создание графиков + self.graph_init() + self.serial.flush() + self.serial.close() + data_IN_COM.clear_data() + self.terapy_nowB.setStyleSheet('background-color: lightGray; color: black') + self.terapy_nowB.setText("") + self.sub_modeB.setStyleSheet('background-color: lightGray; color: black') + self.sub_modeB.setText("") + self.last_period_label.setText("") + self.filt_period_label.setText("") + self.Vs_cnt_label.setText("") + self.Vp_cnt_label.setText("") + self.Vn_cnt_label.setText("") + self.progressBar_f.setValue(0) + self.progressBar_t2.setValue(0) + self.progressBar_t1.setValue(0) + self.progressBar_n.setValue(0) + self.hv_step_cnt_label.setText("") + self.now_energy_label.setText("") + self.HR_label.setText("") + self.OpenB.setEnabled(True) + self.CloseB.setEnabled(True) + + def on_open(self): + """ + Открыть файл для чтения + """ + global MODE_FILE + self.on_close() + file, _ = QFileDialog.getOpenFileName(self, 'Open File') + if file: + data_IN_FILE.clear_data() + self.file_name = file + self.Fname_label.setText(self.file_name) + my_data.get_data_bin(self.file_name, data_IN_FILE) + self.counter_com_port = list(map(lambda x: x / my_data.DATA_RATE, range(0, data_IN_FILE.data_length))) + MODE_FILE = True + + def draw_file(self): + scale = self.get_scale() + size = scale * my_data.DATA_RATE + for x in range(self.point, data_IN_FILE.data_length, 20): + self.curve10.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA[x: x + size]) + self.curve20.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_pos_sig[x: x + size]) + self.curve21.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_din_threshold[x: x + size]) + self.curve26.setData(self.counter_com_port[x: x + size], np.zeros(len(self.counter_com_port[x: x + size]))) + self.curve24.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_min_threshold[x: x + size]) + self.curve25.setData(self.counter_com_port[x: x + size], data_IN_FILE.ECG_RA_max_threshold[x: x + size]) + self.curve30.setData(self.counter_com_port[x: x + size], data_IN_FILE.Last_RR_poz_rel[x: x + size]) + app.processEvents() + time.sleep(0.05) + + def re_scale(self): + scale = self.get_scale() + if len(data_IN_FILE.counter) != 0: + position = self.horizontalScrollBar.value() + self.point = int((len(data_IN_FILE.counter) / my_data.DATA_RATE) * (position / 1000)) + + def on_scroll(self): + self.re_scale() + + def on_close(self): + """ + Закрыть открытый для чтения файл + """ + global MODE_FILE + MODE_FILE = False + # очистка графиков + self.graph_clear() + # Пересоздание графиков + self.graph_init() + # очистка модели + data_IN_FILE.clear_data() + + self.Fname_label.setText('') + + def get_scale(self): + scale = int(self.RA_scale_BOX.currentText()) + return scale + + def on_scale_change(self): + scale = self.get_scale() + data_IN_COM.data_length = scale * my_data.DATA_RATE + data_IN_COM.update_data(data_IN_COM.data_length) + + if MODE_FILE: + self.draw_file() + + all_items = self.graph_1.items() # Получаем все элементы + text_items = [item for item in all_items if isinstance(item, TextItem)] + for text_item in text_items: + self.graph_1.removeItem(text_item) + self.counter_com_port = list(map(lambda x: x / my_data.DATA_RATE, range(0, data_IN_COM.data_length))) + + def update_list(self): + """Создание списка доступных для ввода/вывода портов""" + portlist = [] # создадим пустой список в который и будем всё записывать + ports = QSerialPortInfo().availablePorts() # доступные порты для обмена данными + for port in ports: + portlist.append(port.portName()) + return portlist + + def check_work_mode_viewer(self): + """ + Выбор режима работы (чтение данных из файла/из com-порта), в случае чего некоторые элементы отображения + становятся видимыми и доступными, а другие наоборот становятся недоступными. + """ + if self.radioButton_work_mode_com_port.isChecked(): + self.OpenB.setVisible(False) + self.CloseB.setVisible(False) + self.Play.setVisible(False) + self.Pause.setVisible(False) + self.Fname_label_com_port.setVisible(True) + self.Fname_w_label.setVisible(True) + self.ComL.setVisible(True) + self.Read.setVisible(True) + self.Stop.setVisible(True) + self.Write_file.setVisible(True) + self.Close_file.setVisible(True) + self.Restart.setVisible(True) + self.horizontalScrollBar.setVisible(False) + else: + self.on_stop() + self.OpenB.setVisible(True) + self.CloseB.setVisible(True) + self.Play.setVisible(True) + self.Pause.setVisible(True) + self.Fname_label_com_port.setVisible(False) + self.Fname_w_label.setVisible(False) + self.ComL.setVisible(False) + self.Read.setVisible(False) + self.Stop.setVisible(False) + self.Write_file.setVisible(False) + self.Close_file.setVisible(False) + self.Restart.setVisible(False) + self.horizontalScrollBar.setVisible(True) + + def set_send_mode(self): + """Установка режима передачи данных (Bluetooth/433)""" + self.send_mode = int(self.Send_mode_Box.currentIndex()) + + # настройка параметров работы стимулятора с помощью программы + def set_DC_cut_Box(self): + """Настройка вывода сигнала с/без постоянной составляющей""" + param = int(self.DC_cut_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x21, param, 1, int) + + def set_Signal_Box(self): + """Настройка вывода сигнала с/без фильтром(а)""" + param = int(self.Signal_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x1E, param, 1, int) + + def set_Channel_Box(self): + """Установка активного канала передачи""" + param = int(self.Channel_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x1F, param, 1, int) + + def set_SD_card(self): + """Установка режима записи на карту памяти""" + param = int(self.SD_card_Box.currentIndex()) + my_data.state_packet(self.send_mode, self.serial, 0x20, param, 1, int) + + def set_Spi_spot_set_spinBox(self): + """Установка коэффициента усиления сигнала""" + param = int(self.Spi_spot_set_spinBox.value()) + my_data.state_packet(self.send_mode, self.serial, 0x1C, param, 1, int) + + def set_Work_Mode_pacemaker(self): + """Установка текущего режима работы стимулятора(без/с стимуляцией или в режиме принудительной стимуляции)""" + param = int(self.Work_Mode_pacemaker.currentIndex()) # << 20 + my_data.state_packet(self.send_mode, self.serial, 0x05, param, 2, int) + + def set_RA_max_time_ms_BOX(self): + """Установка длительности поиска максимума""" + param = int(self.RA_max_time_ms_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x0D, param, 2, int) + + def set_RA_min_sensitivity_BOX(self): + """Установка минимальной чувствительности""" + param = float(self.RA_min_sensitivity_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x01, param, 4, float) + + def set_RA_max_sensitivity_BOX(self): + """Установка максимальной чувствительности""" + param = float(self.RA_max_sensitivity_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x02, param, 4, float) + + def set_RA_square_time_ms_BOX(self): + """Установка длительности защиты от Т волны""" + param = int(self.RA_square_time_ms_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x07, param, 2, int) + + def set_RA_square_coeff_BOX(self): + """Установка порога чувствительности Т волны""" + param = float(self.RA_square_coeff_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x03, param, 4, float) + + def set_RA_all_time_ms_BOX(self): + """Установка максимального размера RR-интервала""" + param = int(self.RA_all_time_ms_BOX.currentText()) + my_data.state_packet(self.send_mode, self.serial, 0x09, param, 2, int) + + def set_High_Tf_spinBox(self): + """Установка длительности RR-интервала для определения события как фибрилляции""" + param = self.High_Tf_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x10, param, 2, int) + + def set_High_Tt2_spinBox(self): + """Установка длительности RR-интервала для определения события как тахикардии 2ст""" + param = self.High_Tt2_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x11, param, 2, int) + + def set_High_Tt1_spinBox(self): + """Установка длительности RR-интервала для определения события как тахикардии 1ст""" + param = self.High_Tt1_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x12, param, 2, int) + + def set_Low_V_spinBox(self): + """Установка порога корзины фибрилляции""" + param = self.Low_V_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x13, param, 1, int) + + def set_hv_step_number_spinBox(self): + """Установка количества ступеней BB - терапии""" + param = self.hv_step_number_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x14, param, 1, int) + + def set_min_energy_spinBox(self): + """Установка минимальной энергии стимулятора""" + param = self.min_energy_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x15, param, 2, int) + + def set_max_energy_spinBox(self): + """Установка максимальной энергии стимулятора""" + param = self.max_energy_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x16, param, 2, int) + + def set_hv_blind_time_spinBox(self): + """Установка времени слепоты после разряда""" + param = self.hv_blind_time_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x18, param, 2, int) + + def set_standby_timer_spinBox(self): + """Установка таймаута отключения""" + param = self.standby_timer_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x1B, param, 4, int) + + def set_redet_num_spinBox(self): + """Установка размера буффера редетекции""" + param = self.redet_num_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x19, param, 1, int) + + def set_redet_bad_spinBox(self): + """Установка порога буффера редетекции""" + param = self.redet_bad_spinBox.value() + my_data.state_packet(self.send_mode, self.serial, 0x1A, param, 1, int) + + def set_fibr_cnt_max(self): + """Визуализация корзины фибрилляции""" + # + отправить стимулятору + self.progressBar_f.setMaximum(self.Low_V_spinBox.value()) + + def set_tachy_2_cnt_max(self): + """Визуализация корзины тахикардии 2ст""" + self.progressBar_t2.setMaximum(self.Low_V_spinBox_3.value()) + + def set_tachy_1_cnt_max(self): + """Визуализация корзины тахикардии 1ст""" + self.progressBar_t1.setMaximum(self.Low_V_spinBox_4.value()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7448fd8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# python12 +PyQt5~=5.15.11 +pyqtgraph~=0.13.7 +numpy~=2.2.3 +