mrDarker
2025-09-05 2cd08ebfa438de28261f35f13527d23e8e469dee
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
 
// SGMeasurementDlg.h: 头文件
//
 
#pragma once
 
#include "PLCSignalListener.h"
 
#include <map>
#include <vector>
#include <numeric>
#include <algorithm>
 
// CSGMeasurementDlg 对话框
class CSGMeasurementDlg : public CDialogEx
{
// 构造
public:
    CSGMeasurementDlg(CWnd* pParent = nullptr);    // 标准构造函数
 
// 对话框数据
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_SGMEASUREMENT_DIALOG };
#endif
 
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
 
// 实现
protected:
    HICON m_hIcon;
 
    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    afx_msg void OnTimer(UINT_PTR nIDEvent);
    afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct);
    afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
    afx_msg void OnClose();
    afx_msg LRESULT OnTrayIconClick(WPARAM wParam, LPARAM lParam);
    afx_msg void OnTrayRestore();
    afx_msg void OnTrayExit();
    afx_msg void OnBnClickedButtonConnect();
    afx_msg void OnBnClickedButtonDisconnect();
    afx_msg void OnBnClickedButtonReceiveFromCtrl();
    afx_msg void OnBnClickedButtonSendToCtrl();
    afx_msg void OnBnClickedButtonClearStore();
    afx_msg void OnBnClickedButtonStartStore();
    afx_msg void OnBnClickedButtonStopStore();
    afx_msg void OnBnClickedButtonClearLog();
    DECLARE_MESSAGE_MAP()
 
private:
    /**
     * @brief 退出程序时的清理操作。
     *
     * 此函数在程序退出前被调用,用于执行必要的资源释放与状态恢复,
     * 包括断开设备连接、移除托盘图标等。
     * 最终销毁主窗口并退出应用。
     */
    void ExitApplication();
 
    /**
     * @brief 更新界面控件的可用状态。
     *
     * 根据设备连接状态 `bConnected` 和存储状态 `bStoring` 动态控制界面上按钮、
     * 编辑框、下拉框等控件的可用/禁用状态。
     *
     * @param bConnected 设备当前是否已连接,TRUE 表示已连接,FALSE 表示未连接。
     * @param bStoring 当前是否正在进行数据存储,TRUE 表示正在存储。
     */
    void UpdateControlStatus(BOOL bConnected, BOOL bStoring = FALSE);
 
    /**
     * @brief 启动批量日志追加,暂停自动滚动,提高性能。
     */
    void AppendLogLineBatchBegin();
 
    /**
     * @brief 结束批量日志追加,恢复自动滚动并刷新显示。
     */
    void AppendLogLineBatchEnd();
 
    /**
     * @brief 限制富文本框中的最大行数,避免日志过多导致性能问题。
     *
     * @param maxLines 最大保留的行数,默认值为 100。
     */
    void TrimRichEditLineLimit(int maxLines = 100);
 
    /**
     * @brief 向日志窗口追加一条带颜色样式的日志行。
     *
     * @param content 日志内容。
     * @param color 字体颜色,默认为黑色。
     */
    void AppendLogLineRichStyled(const CString& strContent, COLORREF color = RGB(0, 0, 0));
 
    /**
     * @brief 高亮日志中所有匹配指定字符串的部分。
     *
     * @param strSearch 要匹配的字符串。
     * @param clrHighlight 高亮颜色,默认为橙色(RGB(255, 165, 0))。
     */
    void HighlightAllMatches(const CString& strSearch, COLORREF clrHighlight = RGB(255, 165, 0));
 
    /**
     * @brief 以格式化方式输出指定通道的测量数据样本到日志。
     *
     * 每行输出固定数量(如 7)个浮点数据,带时间戳、通道编号标识,便于调试查看。
     *
     * @param nOutNo 输出端口编号,用于标识日志来源,如 OUT1、OUT2 等。
     * @param vecBuffer 测量数据缓存,将被逐行打印到日志中。
     */
    void PrintSampleData(int nOutNo, const std::vector<float>& vecBuffer);
 
    /**
     * @brief 尝试连接到测量设备。
     *
     * @return true 表示连接成功,false 表示连接失败。
     */
    bool ConnectToDevice();
 
    /**
     * @brief 断开与测量设备的连接。
     *
     * @return true 表示断开成功,false 表示断开失败。
     */
    bool DisconnectFromDevice();
 
    /**
     * @brief 原地清除无效值(如 -999.0f)并更新数据。
     *
     * @param nOutNo 输出通道编号,用于日志记录。
     * @param vecData 输入输出数据容器,内部将被就地裁剪。
     * @param fInvalid 无效值的判断阈值,默认值为 -999.0f。
     */
    void CleanInvalidValuesInPlace(int nOutNo, std::vector<float>& vecData, float fInvalid = -999.0f);
 
    /**
     * @brief 将有效数据切割成两段玻璃数据(Glass1 与 Glass2)。
     *
     * @param nOutNo 输出通道编号,用于日志记录。
     * @param validData 输入的有效数据(应已裁剪边界)。
     * @param vecGlass1 输出第一段玻璃数据。
     * @param vecGlass2 输出第二段玻璃数据。
     * @param fJumpThreshold 跳变阈值,用于检测数据的明显断点。
     * @param nWindow 跳变判断的前后窗口宽度。
     * @param nValleyMargin valley 点之后多少个点作为实际切割点。
     * @param nMinGlass1Count 最少有效点数限制。
     *
     * @return true 表示切割成功,false 表示失败(例如数据不足或无明显跳变)。
     */
    bool SplitGlassSegments(int nOutNo, const std::vector<float>& validData,
        std::vector<float>& vecGlass1, std::vector<float>& vecGlass2,
        float fJumpThreshold = 0.2f, int nWindow = 3, int nValleyMargin = 0,
        int nMinGlassCount = 10);
 
    /**
     * @brief 对数据按整数部分进行分组,保留数据量最多的一组(排除异常/干扰)。
     *
     * @param nOutNo 输出通道编号,用于日志记录。
     * @param vecInput 原始浮点数据(已裁剪无效值)。
     * @param vecOutput 输出被保留的主要分组数据(最多的那组)。
     *
     * @return true 表示成功过滤出主分组,false 表示所有数据都被过滤或为空。
     */
    bool FilterDominantGroup(int nOutNo, const std::vector<float>& vecInput, std::vector<float>& vecOutput);
 
    /**
     * @brief 从输入数据中提取一个固定长度的稳定区间。
     *
     * @param nOutNo 输出通道编号,用于日志记录。
     * @param vecIn 输入原始数据。
     * @param vecOut 输出提取的稳定区域数据。
     * @param nFixedCount 稳定区域的点数要求(必须固定数量)。
     * @param fMaxDelta 允许的最大波动范围(最大值 - 最小值)。
     *
     * @return true 表示成功找到稳定区间,false 表示未找到。
     */
    bool ExtractStableRegionFixed(int nOutNo, 
        const std::vector<float>& vecIn,
        std::vector<float>& vecOut,
        int nFixedCount = 5, float fMaxDelta = 0.05f);
 
    /**
     * @brief 计算两片玻璃稳定区域的平均值和偏移量。
     *
     * @param vecGlass1 第一段稳定数据。
     * @param vecGlass2 第二段稳定数据。
     * @param fAvg1 返回第一段的平均值。
     * @param fAvg2 返回第二段的平均值。
     * @param fOffset 返回两段的偏移值(绝对值差)。
     *
     * @return true 表示计算成功,false 表示输入无效(如空数据)。
     */
    bool CalcGlassOffset(const std::vector<float>& vecGlass1,
        const std::vector<float>& vecGlass2,
        float& fAvg1, float& fAvg2, float& fOffset);
 
    /**
     * @brief 初始化设备端的数据存储缓冲区。
     *
     * 调用底层接口清除当前存储区内容,为新一轮的数据采集做准备。
     * 必须在设备连接成功且未进行数据存储时调用。
     *
     * @return true 表示初始化成功;false 表示失败(可能是设备未连接或调用接口错误)。
     */
    bool InitDataStorage();
 
    /**
     * @brief 启动设备端数据采集和存储。
     *
     * 调用此函数后设备开始采集并缓存数据。
     * 通常配合触发模式进行采集。
     *
     * @return true 表示启动成功;false 表示启动失败。
     */
    bool StartDataStorage();
 
    /**
     * @brief 停止数据采集并从设备获取当前存储数据。
     *
     * 调用后设备停止采集,并尝试读取指定端口的数据,供后续分析处理。
     *
     * @return true 表示停止并读取数据成功;false 表示失败(如设备未响应或数据无效)。
     */
    bool StopDataStorage();
 
    /**
     * @brief 分析指定端口的存储数据,并提取两段玻璃数据与稳定区,计算偏移。
     *
     * @param nOutNo     输出端口编号(1~4)
     * @return float     成功返回计算出的偏移量,失败返回 -1.0f
     */
    float AnalyzeStoredData(int nOutNo);
 
    // === 系统状态与运行数据 ===
 
    /**
     * @brief 当前是否已连接到传感器控制器
     */
    bool m_bConnected;
 
    /**
     * @brief 当前是否正在进行数据存储
     */
    bool m_bSaving;
 
    /**
     * @brief 四个输出端口的当前计算结果(如偏移量或厚度等)
     */
    double m_dOutValues[4];
 
    // === 存储设置相关 ===
 
    /**
     * @brief 是否使用硬件触发信号(0=否,1=是)
     */
    int m_nUseTrigger;
 
    /**
     * @brief 每次采集的数据点数量
     */
    int m_nSavePointCount;
 
    /**
     * @brief 输出端口选择下拉框控件(用于设置采集端口)
     */
    CComboBox m_comboOutputPort;
 
    // === 跳变检测与玻璃识别参数(SplitGlassSegments 使用) ===
 
    /**
     * @brief 跳变阈值(例如 0.2mm),用于检测两片玻璃之间的高度跳变
     */
    float m_fJumpThreshold;
 
    /**
     * @brief 跳变检测窗口半宽度(用于差分计算)
     */
    int m_nJumpWindow;
 
    /**
     * @brief valley 点右移的偏移量,用于最终确定分割点
     */
    int m_nValleyMargin;
 
    /**
     * @brief 第一片玻璃最少所需的数据点数
     */
    int m_nMinGlass1Count;
 
    // === 稳定区域提取参数(ExtractStableRegionFixed 使用) ===
 
    /**
     * @brief 每段稳定区域要提取的固定数据点数
     */
    int m_nFixedCount;
 
    /**
     * @brief 最大允许波动范围(例如 0.05mm),判断是否为“稳定”区域
     */
    float m_fMaxDelta;
 
    // === 日志控件 ===
 
    /**
     * @brief 富文本控件,用于输出带颜色的日志信息
     */
    CRichEditCtrl m_editLog;
 
    // === 托盘图标管理 ===
 
    /**
     * @brief 托盘图标相关数据结构(NOTIFYICONDATA)
     */
    NOTIFYICONDATA m_trayIconData;
 
    /**
     * @brief 托盘图标的唯一 ID
     */
    UINT m_nTrayIconID;
 
    /**
     * @brief 标记托盘图标是否已成功创建
     */
    BOOL m_bTrayIconCreated;
 
    /**
     * @brief 标记程序是否通过托盘图标退出
     */
    BOOL m_bExitingFromTray;
 
    // === PLC 信号监听器 ===
 
    /**
     * @brief PLC 信号监听器实例,用于处理 PLC 信号事件
     */
    CPLCSignalListener m_plcListener;
};