Автоматизация для Фотоплетизмографа

В ответ на пожелания небезразличных к нашим устройствам людей прикрутить к фотоплетизмографу интерфейс для программного доступа к записям плетизмограмм, я все-таки добрался до этой задачи и сделал кое-что полезное.

Надеюсь, добавленные интерфейсы к фотоплетизмографу удовлетворят большинство запросов. Я попытался сделать их максимально простыми, доступными для использования даже программистами с небольшим опытом.  Для той же цели подготовил примеры применения этих интерфейсов в программах на  С# и в старенькой Делфи 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.

Удачи! 😉

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *