Автоматизация для Фотоплетизмографа
В ответ на пожелания небезразличных к нашим устройствам людей прикрутить к фотоплетизмографу интерфейс для программного доступа к записям плетизмограмм, я все-таки добрался до этой задачи и сделал кое-что полезное.
Надеюсь, добавленные интерфейсы к фотоплетизмографу удовлетворят большинство запросов. Я попытался сделать их максимально простыми, доступными для использования даже программистами с небольшим опытом. Для той же цели подготовил примеры применения этих интерфейсов в программах на С# и в старенькой Делфи 7 (объектный Паскаль). Пример на С++ не писал и не привожу в надежде на то, что люди, избравшие этот язык, разберутся самостоятельно. Впрочем, COM сервер фотоплетизмографа реализует двойной интерфейс, то есть поддерживает диспетчеризацию, поэтому его можно встраивать в любое приложение - хоть в Эксель, хоть в веб-страницы.
Содержание
Чтобы воспользоваться новыми возможностями, нужно обновить программу Фотоплетизмограф до версии не ниже 1.32.
Описание интерфейсов
Привожу код IDL:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 |
[ uuid(06CCA034-4AD3-456C-8A63-F5B27DD26767), version(1.0), helpstring("Pulsograph COM library (c)2013 cpp.in.ua") ] library Pulse { importlib("stdole2.tlb"); interface ICoDevice; coclass CoDevice; interface ICoRecord; coclass CoRecord; [ uuid(50D29AE0-1F0D-48D5-8147-774101A51554), version(1.0), helpstring("Interface for access the device"), dual, oleautomation ] interface ICoDevice: IDispatch { [id(0x000000C9), helpstring("Checks whether the device is online")] HRESULT _stdcall IsConnected([out, retval] long* RetIsCon); [id(0x000000CA), helpstring("Request connection to the device")] HRESULT _stdcall Connect([out, retval] long* RetIsCon); [id(0x000000CB), helpstring("Disconnecting the device")] HRESULT _stdcall Disconnect(void); [id(0x000000CC), helpstring("Read ADC data")] HRESULT _stdcall GetRawData([out, retval] SAFEARRAY(long) * RetData); [id(0x000000CD), helpstring("Get current heart rate")] HRESULT _stdcall GetHeartRate([out, retval] float* RetHR); [id(0x000000CE), helpstring("Get current carrier frequency signal level")] HRESULT _stdcall GetDcOffset([out, retval] long* RetOffs); [id(0x000000CF), helpstring("Get current control value of AC carrier amplifier")] HRESULT _stdcall GetHfAmp([out, retval] long* RetAmp); [id(0x000000D0), helpstring("Get current control value of LF pulse wave amplifier")] HRESULT _stdcall GetLfAmp([out, retval] long* RetAmp); }; [ uuid(94AC0D9B-039A-467E-91AB-2F6CD6C9B7A1), version(1.0), helpstring("Dispatch interface for accessing pletismograph record"), dual, oleautomation ] interface ICoRecord: IDispatch { [id(0x000000C9), helpstring("Open existing record")] HRESULT _stdcall OpenFile([in] BSTR InFileName, [out, retval] long* RetIsOk); [id(0x000000CA), helpstring("Get length of the record (in seconds)")] HRESULT _stdcall GetLength([out, retval] float* RetLength); [id(0x000000CB), helpstring("Change current read position in record")] HRESULT _stdcall GoToPos([in] float InPos, [out, retval] float* RetPos); [id(0x000000CC), helpstring("Check current read position")] HRESULT _stdcall GetPos([out, retval] float* RetPos); [id(0x000000CD), helpstring("Get data samples from the record")] HRESULT _stdcall ReadSamples([in] long InCount, [out, retval] SAFEARRAY(long) * RetData); [id(0x000000CE), helpstring("Read heart rate near the current position")] HRESULT _stdcall GetHeartRate([out, retval] float* RetHR); }; [ uuid(2A04AC47-567C-4EF7-A826-E12E00C01E07), version(1.0), helpstring("CoDevice Object") ] coclass CoDevice { [default] interface ICoDevice; }; [ uuid(4BC48BAE-A164-4C7B-B25C-034AD7E4556C), version(1.0), helpstring("CoRecord Object") ] coclass CoRecord { [default] interface ICoRecord; }; }; |
[ uuid(06CCA034-4AD3-456C-8A63-F5B27DD26767), version(1.0), helpstring("Pulsograph COM library (c)2013 cpp.in.ua") ] library Pulse { importlib("stdole2.tlb"); interface ICoDevice; coclass CoDevice; interface ICoRecord; coclass CoRecord; [ uuid(50D29AE0-1F0D-48D5-8147-774101A51554), version(1.0), helpstring("Interface for access the device"), dual, oleautomation ] interface ICoDevice: IDispatch { [id(0x000000C9), helpstring("Checks whether the device is online")] HRESULT _stdcall IsConnected([out, retval] long* RetIsCon); [id(0x000000CA), helpstring("Request connection to the device")] HRESULT _stdcall Connect([out, retval] long* RetIsCon); [id(0x000000CB), helpstring("Disconnecting the device")] HRESULT _stdcall Disconnect(void); [id(0x000000CC), helpstring("Read ADC data")] HRESULT _stdcall GetRawData([out, retval] SAFEARRAY(long) * RetData); [id(0x000000CD), helpstring("Get current heart rate")] HRESULT _stdcall GetHeartRate([out, retval] float* RetHR); [id(0x000000CE), helpstring("Get current carrier frequency signal level")] HRESULT _stdcall GetDcOffset([out, retval] long* RetOffs); [id(0x000000CF), helpstring("Get current control value of AC carrier amplifier")] HRESULT _stdcall GetHfAmp([out, retval] long* RetAmp); [id(0x000000D0), helpstring("Get current control value of LF pulse wave amplifier")] HRESULT _stdcall GetLfAmp([out, retval] long* RetAmp); }; [ uuid(94AC0D9B-039A-467E-91AB-2F6CD6C9B7A1), version(1.0), helpstring("Dispatch interface for accessing pletismograph record"), dual, oleautomation ] interface ICoRecord: IDispatch { [id(0x000000C9), helpstring("Open existing record")] HRESULT _stdcall OpenFile([in] BSTR InFileName, [out, retval] long* RetIsOk); [id(0x000000CA), helpstring("Get length of the record (in seconds)")] HRESULT _stdcall GetLength([out, retval] float* RetLength); [id(0x000000CB), helpstring("Change current read position in record")] HRESULT _stdcall GoToPos([in] float InPos, [out, retval] float* RetPos); [id(0x000000CC), helpstring("Check current read position")] HRESULT _stdcall GetPos([out, retval] float* RetPos); [id(0x000000CD), helpstring("Get data samples from the record")] HRESULT _stdcall ReadSamples([in] long InCount, [out, retval] SAFEARRAY(long) * RetData); [id(0x000000CE), helpstring("Read heart rate near the current position")] HRESULT _stdcall GetHeartRate([out, retval] float* RetHR); }; [ uuid(2A04AC47-567C-4EF7-A826-E12E00C01E07), version(1.0), helpstring("CoDevice Object") ] coclass CoDevice { [default] interface ICoDevice; }; [ uuid(4BC48BAE-A164-4C7B-B25C-034AD7E4556C), version(1.0), helpstring("CoRecord Object") ] coclass CoRecord { [default] interface ICoRecord; }; };
Плетизмограф предоставляет два СОМ -интерфейса ICoDevice и ICoRecord, и реализует их коклассами CoDevice и CoRecord. Они доступные из других программ по ProgId Pulse.CoDevice и Pulse.CoRecord.
ICoDevice
предоставляет доступ к устройству и позволяет:
- Узнать, подключено ли устройство IsConnected(). Если подключено, то результат (типа long) не равен нулю.
- Затребовать подключение к устройству Connect(). Если успешно, то результат (типа long) не равен нулю.
- Попросить отключиться от устройства Disconnect().
- Получить "сырые" данные АЦП с помощью GetRawData(). Возвращает массив типа SAFEARRAY(long), содержащий свежеполученные от устройства и ожидающие во временном буфере отсчеты АЦП. Массив одномерный, количество элементов равно числу записанных в него отсчетов. Амплитудные значения отсчетов - от 0 до 1023, частота дискретизации 100 Гц. Вызывать этот метод чаще 10 раз в секунду смысла нету, но и реже одного раза в 20 секунд тоже нельзя (если данные долго не забирать, они выбрасываются и в буфер не попадают).
- Узнать текущую частоту сердечных сокращений по мнению Фотоплетизмографа GetHeartRate(). Результат типа float представляет мгновенную ЧСС, выраженную в количестве ударов в минуту.
- Узнать уровень постоянной составляющей после амплитудного детектора сигнала несущей 5кГц GetDcOffset(). Результат типа long, значения в диапазоне от 0 до 1023. Рабочий диапазон - от 300 до 500.
- Узнать управляющий код на цифровом резисторе аттенюатора усилителя несущей частоты 5кГц GetHfAmp(). Результат типа long, значения в диапазоне от 1 до 255.
- Узнать управляющий код на цифровом резисторе аттенюатора усилителя инфранизкой частоты (пульсовой волны) GetLfAmp(). Результат типа long, значения в диапазоне от 1 до 255.
ICoRecord
предназначен для извлечения данных из файлов записей плетизмограмм, и позволяет:
- Открыть файл записи для чтения OpenFile(BSTR FileName). В качестве аргумента требует строку с полным именем файла записи. Результат (типа long) не равен нулю, если запись удалось прочесть и корректно интерпретировать.
- Узнать длительность записи, выраженную в секундах GetLength(), возвращает float.
- Потребовать изменить текущую позицию чтения в записи GoToPos(float ToPos). В качестве аргумента нужно сообщить желаемую позицию от начала записи, выраженную в секундах. Началу записи соответствует метка времени 0. В качестве результата (типа float) возвращает реально установленную позицию.
- Узнать текущую позицию чтения записи, выраженную в секундах от начала записи GetPos(). Возвращает результат типа float.
- Прочитать значения амплитуды из записи ReadSamples(long InCount). В качестве аргумента - желаемое количество отсчетов (но не более 10 000 за раз). В результате получаем SAFEARRAY(long), содержащий значения в диапазоне от -1000 до +1000. Количество элементов массива соответствует реально считанному из записи количеству отсчетов. После каждого вызова происходит автоматическое смещение позиции чтения к концу записи на считанное число отсчетов.
- Узнать частоту сердечных сокращений, вычисленную Фотоплетизмографом, в текущей позиции чтения GetHeartRate(). Результат типа float представляет мгновенную ЧСС, выраженную в количестве ударов в минуту.
Примеры
Пример на С# (MVS 2012)
Проект для примера использования COM интерфейсов плетизмографа из программы на С# можно скачать по данной ссылке.
Исходный код:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 152 153 154 155 |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Runtime.InteropServices; namespace C_Sharp_Test { public partial class TestForm : Form { Pulse.ICoDevice pdev; Pulse.ICoRecord prec; int ipos; public TestForm() { InitializeComponent(); pdev = null; prec = null; } private void ComCreateButton_Click(object sender, EventArgs e) { if (pdev == null) { pdev = new Pulse.CoDevice(); if (pdev != null) PaintTimer.Enabled = pdev.IsConnected() != 0; } if (prec == null) { prec = new Pulse.CoRecord(); } if (pdev != null) { TextB.Text = "CoDevice OK\r\n"; } else { TextB.Text = "CoDevice FAILED\r\n"; } if (prec != null) { TextB.Text += "CoRecord OK"; } else { TextB.Text += "CoRecord FAILED"; } } private void ComDestroyButton_Click(object sender, EventArgs e) { if (pdev != null) { Marshal.ReleaseComObject(pdev); pdev = null; } if (prec != null) { Marshal.ReleaseComObject(prec); prec = null; } } private void ConnectButton_Click(object sender, EventArgs e) { if (pdev == null) return; if(pdev.IsConnected() == 0) pdev.Connect(); else pdev.Disconnect(); PaintTimer.Enabled = pdev.IsConnected() != 0; } private void PaintTimer_Tick(object sender, EventArgs e) { if (pdev == null) return; // process ADC data Array sa = pdev.GetRawData(); int len = sa.GetLength(0); System.Windows.Forms.DataVisualization.Charting.Series ps = TestChart.Series.FindByName("TestSeries"); for(int i = 0; i < len; i++){ ps.Points.AddXY(ipos * 0.01, sa.GetValue(i)); ipos++; } if (ipos > 1000){ ps.Points.Clear(); ipos = 0; } // display numerical values TextB.Text = "HR " + pdev.GetHeartRate() + " \r\nDC offset " + pdev.GetDcOffset() + " \r\nHF amp " + pdev.GetHfAmp() + " \r\nLF amp " + pdev.GetLfAmp(); } private void RecOpenButton_Click(object sender, EventArgs e) { if (prec == null) return; pdev.Disconnect(); PaintTimer.Enabled = false; if (RecOpenDialog.ShowDialog() == DialogResult.OK) { int res = prec.OpenFile(RecOpenDialog.FileName); if (res == 0) { TextB.Text = "Can't open " + RecOpenDialog.FileName; return; } TextB.Text = "OpenFile OK " + RecOpenDialog.FileName; RecPosTB.Maximum = (int)prec.GetLength() + 1; RecPosTB.Value = 0; RecPosTB_ValueChanged(sender, e); } } private void RecPosTB_ValueChanged(object sender, EventArgs e) { if (prec == null) return; prec.GoToPos(RecPosTB.Value); Array sa = prec.ReadSamples(500); int len = sa.GetLength(0); System.Windows.Forms.DataVisualization.Charting.Series ps = TestChart.Series.FindByName("TestSeries"); ipos = 0; ps.Points.Clear(); for (int i = 0; i < len; i++) { ps.Points.AddXY(ipos * 0.01, sa.GetValue(i)); ipos++; } // display numerical values TextB.Text = "HR " + prec.GetHeartRate() + " \r\nPos (s) " + prec.GetPos(); } } } |
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Runtime.InteropServices; namespace C_Sharp_Test { public partial class TestForm : Form { Pulse.ICoDevice pdev; Pulse.ICoRecord prec; int ipos; public TestForm() { InitializeComponent(); pdev = null; prec = null; } private void ComCreateButton_Click(object sender, EventArgs e) { if (pdev == null) { pdev = new Pulse.CoDevice(); if (pdev != null) PaintTimer.Enabled = pdev.IsConnected() != 0; } if (prec == null) { prec = new Pulse.CoRecord(); } if (pdev != null) { TextB.Text = "CoDevice OK\r\n"; } else { TextB.Text = "CoDevice FAILED\r\n"; } if (prec != null) { TextB.Text += "CoRecord OK"; } else { TextB.Text += "CoRecord FAILED"; } } private void ComDestroyButton_Click(object sender, EventArgs e) { if (pdev != null) { Marshal.ReleaseComObject(pdev); pdev = null; } if (prec != null) { Marshal.ReleaseComObject(prec); prec = null; } } private void ConnectButton_Click(object sender, EventArgs e) { if (pdev == null) return; if(pdev.IsConnected() == 0) pdev.Connect(); else pdev.Disconnect(); PaintTimer.Enabled = pdev.IsConnected() != 0; } private void PaintTimer_Tick(object sender, EventArgs e) { if (pdev == null) return; // process ADC data Array sa = pdev.GetRawData(); int len = sa.GetLength(0); System.Windows.Forms.DataVisualization.Charting.Series ps = TestChart.Series.FindByName("TestSeries"); for(int i = 0; i < len; i++){ ps.Points.AddXY(ipos * 0.01, sa.GetValue(i)); ipos++; } if (ipos > 1000){ ps.Points.Clear(); ipos = 0; } // display numerical values TextB.Text = "HR " + pdev.GetHeartRate() + " \r\nDC offset " + pdev.GetDcOffset() + " \r\nHF amp " + pdev.GetHfAmp() + " \r\nLF amp " + pdev.GetLfAmp(); } private void RecOpenButton_Click(object sender, EventArgs e) { if (prec == null) return; pdev.Disconnect(); PaintTimer.Enabled = false; if (RecOpenDialog.ShowDialog() == DialogResult.OK) { int res = prec.OpenFile(RecOpenDialog.FileName); if (res == 0) { TextB.Text = "Can't open " + RecOpenDialog.FileName; return; } TextB.Text = "OpenFile OK " + RecOpenDialog.FileName; RecPosTB.Maximum = (int)prec.GetLength() + 1; RecPosTB.Value = 0; RecPosTB_ValueChanged(sender, e); } } private void RecPosTB_ValueChanged(object sender, EventArgs e) { if (prec == null) return; prec.GoToPos(RecPosTB.Value); Array sa = prec.ReadSamples(500); int len = sa.GetLength(0); System.Windows.Forms.DataVisualization.Charting.Series ps = TestChart.Series.FindByName("TestSeries"); ipos = 0; ps.Points.Clear(); for (int i = 0; i < len; i++) { ps.Points.AddXY(ipos * 0.01, sa.GetValue(i)); ipos++; } // display numerical values TextB.Text = "HR " + prec.GetHeartRate() + " \r\nPos (s) " + prec.GetPos(); } } }
Пример для Delphi 7
Проект для примера использования COM интерфейсов фотоплетизмографа из программы на Паскале можно скачать по данной ссылке.
Исходный код:
1 2 3 4 5 6 7 8 9 10 11 12 13 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
unit MainUnit; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComObj, ActiveX, ExtCtrls, ComCtrls; type TMainForm = class(TForm) CreateComButton: TButton; DestroyComButton: TButton; ConnectButton: TButton; PaintTimer: TTimer; PBPanel: TPanel; PB: TPaintBox; LogMemo: TMemo; RecPosTB: TTrackBar; RecOD: TOpenDialog; RecOpenButton: TButton; procedure CreateComButtonClick(Sender: TObject); procedure DestroyComButtonClick(Sender: TObject); procedure ConnectButtonClick(Sender: TObject); procedure PaintTimerTimer(Sender: TObject); procedure RecOpenButtonClick(Sender: TObject); procedure RecPosTBChange(Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; pdev, prec: Variant; ind: Integer; implementation {$R *.dfm} procedure TMainForm.CreateComButtonClick(Sender: TObject); begin if not VarIsClear(pdev) then exit; try pdev := CreateOleObject('Pulse.CoDevice'); prec := CreateOleObject('Pulse.CoRecord'); except // something wrong LogMemo.Text := 'Can`t create COM objects!'; exit; end; LogMemo.Text := 'Create COM objects is OK'; if not VarIsClear(pdev) then PaintTimer.Enabled := pdev.IsConnected <> 0; end; procedure TMainForm.DestroyComButtonClick(Sender: TObject); begin if not VarIsClear(pdev) then begin pdev := Unassigned; end; if not VarIsClear(prec) then begin prec := Unassigned; end; end; procedure TMainForm.ConnectButtonClick(Sender: TObject); begin if VarIsClear(pdev) then exit; if pdev.IsConnected then pdev.Disconnect else pdev.Connect; if pdev.IsConnected then PaintTimer.Enabled := true; end; procedure TMainForm.PaintTimerTimer(Sender: TObject); var psa : PSafeArray; cnt, i, y, pbx, pby : Integer; mx, my : Real; begin if VarIsClear(pdev) then exit; psa := PSafeArray(TVarData( pdev.GetRawData ).VArray); SafeArrayGetUBound(psa, 1, cnt); // paint the data if ind = 0 then begin PB.Canvas.FillRect(PB.Canvas.ClipRect); end; mx := PB.Width / 500; my := PB.Height / 1024; for i := 0 to cnt do begin SafeArrayGetElement(psa, i, y); pbx := Round(ind * mx); pby := PB.Height - Round(my * y); if ind = 0 then PB.Canvas.MoveTo(pbx, pby); PB.Canvas.LineTo(pbx, pby); ind := ind + 1; end; if ind * mx > PB.Width then ind := 0; LogMemo.Clear; LogMemo.Lines.Add('HR ' + FloatToStr(pdev.GetHeartRate)); LogMemo.Lines.Add('DC offset ' + IntToStr(pdev.GetDcOffset)); LogMemo.Lines.Add('HF amp ' + IntToStr(pdev.GetHfAmp)); LogMemo.Lines.Add('LF amp ' + IntToStr(pdev.GetLfAmp)); end; procedure TMainForm.RecOpenButtonClick(Sender: TObject); var len : real; begin if not VarIsClear(pdev) then begin pdev.Disconnect; PaintTimer.Enabled := false; end; if VarIsClear(prec) or not RecOD.Execute then exit; if prec.OpenFile(RecOD.FileName) = 0 then begin LogMemo.Text := 'Can`t open ' + RecOD.FileName; exit; end else LogMemo.Text := 'Open OK ' + RecOD.FileName; len := prec.GetLength; RecPosTB.Max := Round(len); RecPosTB.Position := 0; RecPosTBChange(Sender); end; procedure TMainForm.RecPosTBChange(Sender: TObject); var psa : PSafeArray; cnt, i, y, pbx, pby : Integer; mx, my : Real; begin if VarIsClear(prec) then exit; prec.GoToPos(RecPosTB.Position); psa := PSafeArray(TVarData( prec.ReadSamples(500) ).VArray); SafeArrayGetUBound(psa, 1, cnt); // paint the data ind := 0; PB.Canvas.FillRect(PB.Canvas.ClipRect); mx := PB.Width / 500; my := PB.Height / 2000; for i := 0 to cnt do begin SafeArrayGetElement(psa, i, y); pbx := Round(ind * mx); pby := PB.Height - Round(my * (y + 1000)); if ind = 0 then PB.Canvas.MoveTo(pbx, pby); PB.Canvas.LineTo(pbx, pby); ind := ind + 1; end; LogMemo.Clear; LogMemo.Lines.Add('HR ' + FloatToStr(prec.GetHeartRate)); LogMemo.Lines.Add('Pos (s) ' + IntToStr(prec.GetPos)); end; end. |
unit MainUnit; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComObj, ActiveX, ExtCtrls, ComCtrls; type TMainForm = class(TForm) CreateComButton: TButton; DestroyComButton: TButton; ConnectButton: TButton; PaintTimer: TTimer; PBPanel: TPanel; PB: TPaintBox; LogMemo: TMemo; RecPosTB: TTrackBar; RecOD: TOpenDialog; RecOpenButton: TButton; procedure CreateComButtonClick(Sender: TObject); procedure DestroyComButtonClick(Sender: TObject); procedure ConnectButtonClick(Sender: TObject); procedure PaintTimerTimer(Sender: TObject); procedure RecOpenButtonClick(Sender: TObject); procedure RecPosTBChange(Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; pdev, prec: Variant; ind: Integer; implementation {$R *.dfm} procedure TMainForm.CreateComButtonClick(Sender: TObject); begin if not VarIsClear(pdev) then exit; try pdev := CreateOleObject('Pulse.CoDevice'); prec := CreateOleObject('Pulse.CoRecord'); except // something wrong LogMemo.Text := 'Can`t create COM objects!'; exit; end; LogMemo.Text := 'Create COM objects is OK'; if not VarIsClear(pdev) then PaintTimer.Enabled := pdev.IsConnected <> 0; end; procedure TMainForm.DestroyComButtonClick(Sender: TObject); begin if not VarIsClear(pdev) then begin pdev := Unassigned; end; if not VarIsClear(prec) then begin prec := Unassigned; end; end; procedure TMainForm.ConnectButtonClick(Sender: TObject); begin if VarIsClear(pdev) then exit; if pdev.IsConnected then pdev.Disconnect else pdev.Connect; if pdev.IsConnected then PaintTimer.Enabled := true; end; procedure TMainForm.PaintTimerTimer(Sender: TObject); var psa : PSafeArray; cnt, i, y, pbx, pby : Integer; mx, my : Real; begin if VarIsClear(pdev) then exit; psa := PSafeArray(TVarData( pdev.GetRawData ).VArray); SafeArrayGetUBound(psa, 1, cnt); // paint the data if ind = 0 then begin PB.Canvas.FillRect(PB.Canvas.ClipRect); end; mx := PB.Width / 500; my := PB.Height / 1024; for i := 0 to cnt do begin SafeArrayGetElement(psa, i, y); pbx := Round(ind * mx); pby := PB.Height - Round(my * y); if ind = 0 then PB.Canvas.MoveTo(pbx, pby); PB.Canvas.LineTo(pbx, pby); ind := ind + 1; end; if ind * mx > PB.Width then ind := 0; LogMemo.Clear; LogMemo.Lines.Add('HR ' + FloatToStr(pdev.GetHeartRate)); LogMemo.Lines.Add('DC offset ' + IntToStr(pdev.GetDcOffset)); LogMemo.Lines.Add('HF amp ' + IntToStr(pdev.GetHfAmp)); LogMemo.Lines.Add('LF amp ' + IntToStr(pdev.GetLfAmp)); end; procedure TMainForm.RecOpenButtonClick(Sender: TObject); var len : real; begin if not VarIsClear(pdev) then begin pdev.Disconnect; PaintTimer.Enabled := false; end; if VarIsClear(prec) or not RecOD.Execute then exit; if prec.OpenFile(RecOD.FileName) = 0 then begin LogMemo.Text := 'Can`t open ' + RecOD.FileName; exit; end else LogMemo.Text := 'Open OK ' + RecOD.FileName; len := prec.GetLength; RecPosTB.Max := Round(len); RecPosTB.Position := 0; RecPosTBChange(Sender); end; procedure TMainForm.RecPosTBChange(Sender: TObject); var psa : PSafeArray; cnt, i, y, pbx, pby : Integer; mx, my : Real; begin if VarIsClear(prec) then exit; prec.GoToPos(RecPosTB.Position); psa := PSafeArray(TVarData( prec.ReadSamples(500) ).VArray); SafeArrayGetUBound(psa, 1, cnt); // paint the data ind := 0; PB.Canvas.FillRect(PB.Canvas.ClipRect); mx := PB.Width / 500; my := PB.Height / 2000; for i := 0 to cnt do begin SafeArrayGetElement(psa, i, y); pbx := Round(ind * mx); pby := PB.Height - Round(my * (y + 1000)); if ind = 0 then PB.Canvas.MoveTo(pbx, pby); PB.Canvas.LineTo(pbx, pby); ind := ind + 1; end; LogMemo.Clear; LogMemo.Lines.Add('HR ' + FloatToStr(prec.GetHeartRate)); LogMemo.Lines.Add('Pos (s) ' + IntToStr(prec.GetPos)); end; end.
Удачи! 😉