849 lines
32 KiB
C#
849 lines
32 KiB
C#
using AnQing.Commons;
|
||
using AnQing.Models;
|
||
using Modbus.Device;
|
||
using MvCamCtrl.NET;
|
||
using RGD.DBUtility;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Data;
|
||
using System.Data.SqlClient;
|
||
using System.Drawing;
|
||
using System.Drawing.Imaging;
|
||
using System.IO;
|
||
using System.IO.Ports;
|
||
using System.Linq;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Controls;
|
||
using System.Windows.Controls.Primitives;
|
||
using System.Windows.Input;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using System.Windows.Threading;
|
||
using ZXing;
|
||
using ZXing.Common;
|
||
using static System.Net.Mime.MediaTypeNames;
|
||
using Application = System.Windows.Application;
|
||
using Color = System.Drawing.Color;
|
||
using PixelFormat = System.Drawing.Imaging.PixelFormat;
|
||
|
||
namespace AnQing.ViewModel
|
||
{
|
||
public class MianWindowViewModel : INotifyPropertyChanged
|
||
{
|
||
#region 属性
|
||
/// <summary>
|
||
/// 图片识别区域
|
||
/// </summary>
|
||
public List<Rectangle> possibleRegions = new List<Rectangle>();
|
||
/// <summary>
|
||
/// 摄像头1连接状态
|
||
/// </summary>
|
||
private bool video1State;
|
||
public bool Video1State
|
||
{
|
||
get { return video1State; }
|
||
set
|
||
{
|
||
video1State = value;
|
||
OnPropertyChanged("Video1State");
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 摄像头2连接状态
|
||
/// </summary>
|
||
private bool video2State;
|
||
public bool Video2State
|
||
{
|
||
get { return video2State; }
|
||
set
|
||
{
|
||
video2State = value;
|
||
OnPropertyChanged("Video2State");
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 识别状态
|
||
/// </summary>
|
||
private bool readerState;
|
||
public bool ReaderState
|
||
{
|
||
get { return readerState; }
|
||
set
|
||
{
|
||
readerState = value;
|
||
OnPropertyChanged("ReaderState");
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 识别到的数据信息
|
||
/// </summary>
|
||
private string readerText;
|
||
public string ReaderText
|
||
{
|
||
get { return readerText; }
|
||
set
|
||
{
|
||
readerText = value;
|
||
OnPropertyChanged("ReaderText");
|
||
}
|
||
}
|
||
private string readerText_temp;
|
||
/// <summary>
|
||
/// 识别时间
|
||
/// </summary>
|
||
private string readerTextTime;
|
||
public string ReaderTextTime
|
||
{
|
||
get { return readerTextTime; }
|
||
set
|
||
{
|
||
readerTextTime = value;
|
||
OnPropertyChanged("ReaderTextTime");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图像一
|
||
/// </summary>
|
||
private ImageSource _imageSource;
|
||
public ImageSource ImageSource
|
||
{
|
||
get { return _imageSource; }
|
||
set
|
||
{
|
||
if (_imageSource != value)
|
||
{
|
||
if (_imageSource is BitmapSource oldBitmapSource)
|
||
{
|
||
DisposeBitmapSource(oldBitmapSource);
|
||
}
|
||
_imageSource = value;
|
||
OnPropertyChanged(nameof(ImageSource));
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 释放 BitmapSource 资源,避免 GDI 资源泄漏
|
||
/// </summary>
|
||
private void DisposeBitmapSource(BitmapSource bitmapSource)
|
||
{
|
||
if (bitmapSource == null) return;
|
||
|
||
if (bitmapSource is IDisposable disposable)
|
||
{
|
||
disposable.Dispose();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图像二
|
||
/// </summary>
|
||
private ImageSource _imageSource2;
|
||
public ImageSource ImageSource2
|
||
{
|
||
get { return _imageSource2; }
|
||
set
|
||
{
|
||
if (_imageSource2 != value)
|
||
{
|
||
_imageSource2 = value;
|
||
OnPropertyChanged(nameof(ImageSource2));
|
||
}
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 事件
|
||
/// <summary>
|
||
/// 摄像触发采集
|
||
/// </summary>
|
||
private ICommand buttonGatherCommand;
|
||
public ICommand ButtonGatherCommand => buttonGatherCommand ??= new RelayCommand(TriggerExec);
|
||
|
||
|
||
/// <summary>
|
||
/// 释放
|
||
/// </summary>
|
||
private ICommand buttonCloseCommand;
|
||
public ICommand ButtonCloseCommand => buttonCloseCommand ??= new RelayCommand(CloseCamera);
|
||
#endregion
|
||
|
||
#region 相机初始化
|
||
//图像一
|
||
MyCamera.MV_CC_DEVICE_INFO_LIST m_stDeviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
|
||
public MyCamera m_MyCamera = new MyCamera();
|
||
bool m_bGrabbing = false;
|
||
//图像二
|
||
public MyCamera m_MyCamera2 = new MyCamera();
|
||
bool m_bGrabbing2 = false;
|
||
/// <summary>
|
||
/// 关闭程序需要关闭摄像头
|
||
/// </summary>
|
||
private void CloseCamera()
|
||
{
|
||
// ch:关闭设备 | en:Close Device
|
||
m_MyCamera.MV_CC_CloseDevice_NET();
|
||
m_MyCamera.MV_CC_DestroyDevice_NET();
|
||
}
|
||
/// <summary>
|
||
/// 初始化打开摄像头
|
||
/// </summary>
|
||
private void OpenCamera()
|
||
{
|
||
string targetIp = "192.168.2.11"; // 摄像头IP
|
||
string targetIp2 = "192.168.3.13"; // 摄像头2 IP
|
||
|
||
bool ip1 = false, ip2 = false;
|
||
System.GC.Collect();
|
||
m_stDeviceList.nDeviceNum = 0;
|
||
|
||
int nRet1 = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref m_stDeviceList);
|
||
if (nRet1 != 0)
|
||
{
|
||
MessageBox.Show("MV_CC_EnumDevices_NET 初始化失败: " + nRet1);
|
||
return;
|
||
}
|
||
|
||
// ch:获取选择的设备信息 | en:Get selected device information
|
||
MyCamera.MV_CC_DEVICE_INFO device = new MyCamera.MV_CC_DEVICE_INFO();//相机一
|
||
MyCamera.MV_CC_DEVICE_INFO device2 = new MyCamera.MV_CC_DEVICE_INFO();//相机二
|
||
for (int i = 0; i < m_stDeviceList.nDeviceNum; i++)
|
||
{
|
||
MyCamera.MV_CC_DEVICE_INFO device_temp = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
|
||
|
||
if (device_temp.nTLayerType == MyCamera.MV_GIGE_DEVICE)
|
||
{
|
||
MyCamera.MV_GIGE_DEVICE_INFO_EX gigeInfo = (MyCamera.MV_GIGE_DEVICE_INFO_EX)MyCamera.ByteToStruct(device_temp.SpecialInfo.stGigEInfo, typeof(MyCamera.MV_GIGE_DEVICE_INFO_EX));
|
||
|
||
string deviceIp = $"{(gigeInfo.nCurrentIp >> 24) & 0xFF}.{(gigeInfo.nCurrentIp >> 16) & 0xFF}.{(gigeInfo.nCurrentIp >> 8) & 0xFF}.{gigeInfo.nCurrentIp & 0xFF}";
|
||
if (deviceIp == targetIp) // 相机一
|
||
{
|
||
device = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
|
||
ip1 = true;
|
||
}
|
||
if (deviceIp == targetIp2) // 相机二
|
||
{
|
||
device2 = (MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(m_stDeviceList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
|
||
ip2 = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ch:打开设备 | en:Open device
|
||
if (null == m_MyCamera)
|
||
{
|
||
m_MyCamera = new MyCamera();
|
||
if (null == m_MyCamera)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
if (null == m_MyCamera2)
|
||
{
|
||
m_MyCamera2 = new MyCamera();
|
||
if (null == m_MyCamera2)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
int nRet = m_MyCamera.MV_CC_CreateDevice_NET(ref device);
|
||
if (MyCamera.MV_OK != nRet)
|
||
{
|
||
return;
|
||
}
|
||
nRet = m_MyCamera.MV_CC_OpenDevice_NET();
|
||
if (MyCamera.MV_OK != nRet)
|
||
{
|
||
m_MyCamera.MV_CC_CloseDevice_NET();
|
||
m_MyCamera.MV_CC_DestroyDevice_NET();
|
||
|
||
//// ch:关闭设备 | en:Close Device
|
||
m_MyCamera2.MV_CC_CloseDevice_NET();
|
||
m_MyCamera2.MV_CC_DestroyDevice_NET();
|
||
return;
|
||
}
|
||
|
||
int nRet_2 = m_MyCamera2.MV_CC_CreateDevice_NET(ref device2);
|
||
if (MyCamera.MV_OK != nRet_2)
|
||
{
|
||
return;
|
||
}
|
||
nRet_2 = m_MyCamera2.MV_CC_OpenDevice_NET();
|
||
if (MyCamera.MV_OK != nRet_2)
|
||
{
|
||
m_MyCamera.MV_CC_CloseDevice_NET();
|
||
m_MyCamera.MV_CC_DestroyDevice_NET();
|
||
|
||
//// ch:关闭设备 | en:Close Device
|
||
m_MyCamera2.MV_CC_CloseDevice_NET();
|
||
m_MyCamera2.MV_CC_DestroyDevice_NET();
|
||
return;
|
||
}
|
||
|
||
// ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)
|
||
if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
|
||
{
|
||
int nPacketSize = m_MyCamera.MV_CC_GetOptimalPacketSize_NET();
|
||
if (nPacketSize > 0)
|
||
{
|
||
nRet = m_MyCamera.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize);
|
||
if (nRet != MyCamera.MV_OK)
|
||
{
|
||
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nRet);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nPacketSize);
|
||
}
|
||
}
|
||
if (device2.nTLayerType == MyCamera.MV_GIGE_DEVICE)
|
||
{
|
||
int nPacketSize = m_MyCamera2.MV_CC_GetOptimalPacketSize_NET();
|
||
if (nPacketSize > 0)
|
||
{
|
||
nRet_2 = m_MyCamera2.MV_CC_SetIntValueEx_NET("GevSCPSPacketSize", nPacketSize);
|
||
if (nRet_2 != MyCamera.MV_OK)
|
||
{
|
||
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nRet_2);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("MV_CC_SetIntValueEx_NET 初始化失败: " + nPacketSize);
|
||
}
|
||
}
|
||
|
||
// ch:设置采集连续模式 | en:Set Continues Aquisition Mode
|
||
m_MyCamera.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
|
||
m_MyCamera.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON);
|
||
m_MyCamera.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE);
|
||
m_MyCamera2.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
|
||
m_MyCamera2.MV_CC_SetEnumValue_NET("TriggerMode", (uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_ON);
|
||
m_MyCamera2.MV_CC_SetEnumValue_NET("TriggerSource", (uint)MyCamera.MV_CAM_TRIGGER_SOURCE.MV_TRIGGER_SOURCE_SOFTWARE);
|
||
|
||
//bnGetParam_Click(null, null);//获取相机焦距等参数
|
||
|
||
// ch:标志位置位true | en:Set position bit true
|
||
m_bGrabbing = true;
|
||
m_bGrabbing2 = true;
|
||
|
||
// ch:开始采集 | en:Start Grabbing
|
||
int nRet2 = m_MyCamera.MV_CC_StartGrabbing_NET();
|
||
if (MyCamera.MV_OK != nRet2)
|
||
{
|
||
m_bGrabbing = false;
|
||
MessageBox.Show("MV_CC_StartGrabbing_NET 初始化失败: " + nRet2);
|
||
return;
|
||
}
|
||
int nRet2_2 = m_MyCamera2.MV_CC_StartGrabbing_NET();
|
||
if (MyCamera.MV_OK != nRet2_2)
|
||
{
|
||
m_bGrabbing2 = false;
|
||
MessageBox.Show("MV_CC_StartGrabbing_NET 初始化失败: " + nRet2_2);
|
||
return;
|
||
}
|
||
if (ip1)
|
||
Video1State = true;
|
||
|
||
if (ip2)
|
||
Video2State = true;
|
||
}
|
||
|
||
private List<string> ReadImageGuid_Temp = new List<string> ();
|
||
public string ReaderImagePath_Temp;
|
||
/// <summary>
|
||
/// 抓拍图片
|
||
/// </summary>
|
||
public void ReceiveThreadProcess()
|
||
{
|
||
MyCamera.MV_FRAME_OUT stFrameInfo = new MyCamera.MV_FRAME_OUT();
|
||
int nRet = m_MyCamera.MV_CC_GetImageBuffer_NET(ref stFrameInfo, 1000);
|
||
if (nRet == MyCamera.MV_OK)
|
||
{
|
||
try
|
||
{
|
||
int width = stFrameInfo.stFrameInfo.nWidth;
|
||
int height = stFrameInfo.stFrameInfo.nHeight;
|
||
int stride = width; // Mono8: 每个像素1字节,stride = width
|
||
|
||
// 直接利用内存地址创建 Bitmap 对象
|
||
Bitmap bitmap = new Bitmap(width, height, stride, PixelFormat.Format8bppIndexed, stFrameInfo.pBufAddr);
|
||
|
||
// 设置灰度调色板
|
||
ColorPalette palette = bitmap.Palette;
|
||
for (int i = 0; i < 256; i++)
|
||
{
|
||
palette.Entries[i] = Color.FromArgb(i, i, i);
|
||
}
|
||
bitmap.Palette = palette;
|
||
|
||
DateTime start = DateTime.Now;
|
||
var reader = new BarcodeReader
|
||
{
|
||
AutoRotate = true,
|
||
Options = new DecodingOptions
|
||
{
|
||
TryHarder = true, // 深度解析模式
|
||
PureBarcode = false, // 是否纯条码(无文字干扰)
|
||
AssumeCode39CheckDigit = false, // 是否验证Code39校验位
|
||
PossibleFormats = new List<BarcodeFormat>
|
||
{
|
||
BarcodeFormat.CODE_128
|
||
//BarcodeFormat.EAN_13,
|
||
//BarcodeFormat.EAN_8,
|
||
//BarcodeFormat.UPC_A,
|
||
//BarcodeFormat.UPC_E,
|
||
//BarcodeFormat.CODE_39,
|
||
//BarcodeFormat.ITF,
|
||
//BarcodeFormat.CODABAR
|
||
},
|
||
CharacterSet = "UTF-8"
|
||
}
|
||
};
|
||
|
||
foreach (var region in possibleRegions)
|
||
{
|
||
Bitmap cropped = CropBarcodeRegion(bitmap, region);
|
||
var result = reader.Decode(cropped);
|
||
cropped.Dispose();
|
||
cropped = null;
|
||
if (result != null)
|
||
{
|
||
readerText_temp = result.Text;
|
||
ReaderText = result.Text;
|
||
ReaderState = false;
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
readerText_temp = "";
|
||
}
|
||
}
|
||
if (string.IsNullOrEmpty(readerText_temp))
|
||
{
|
||
ReaderText = "未检测到条码!";
|
||
ReaderState = true;
|
||
}
|
||
DateTime end = DateTime.Now;
|
||
TimeSpan duration = end - start;
|
||
ReaderTextTime = duration.TotalSeconds.ToString() + " 秒";
|
||
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
// 转换为 WPF BitmapImage 用于 Image 控件显示
|
||
ImageSource = Common.BitmapToBitmapSource2(bitmap);
|
||
});
|
||
string guid = Guid.NewGuid().ToString();
|
||
ReaderImagePath_Temp = AppDomain.CurrentDomain.BaseDirectory + "ImageList\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\" + guid + ".jpeg";
|
||
string imgpath_after = "";
|
||
string sql = @"
|
||
IF NOT EXISTS (SELECT 1 FROM SCAN_MIAN WHERE goodcode = @goodcode)
|
||
BEGIN
|
||
INSERT INTO SCAN_MIAN (time, goodcode, img, img_after, ex)
|
||
VALUES (@time, @goodcode, @img, @img_after, @ex)
|
||
END";
|
||
|
||
SqlParameter[] parameters = {
|
||
new SqlParameter("@time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
|
||
new SqlParameter("@goodcode", readerText_temp),
|
||
new SqlParameter("@img", ReaderImagePath_Temp),
|
||
new SqlParameter("@img_after", imgpath_after),
|
||
new SqlParameter("@ex", guid)
|
||
};
|
||
|
||
DbHelperSQL.ExecuteSql(sql, parameters);
|
||
DbHelperSQL.ExecuteSql($"INSERT INTO SCAN_MIAN (time, goodcode, img, img_after,ex) VALUES ('{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}', '{readerText_temp}', '{ReaderImagePath_Temp}', '{imgpath_after}','{guid}');");
|
||
ReadImageGuid_Temp.Add(guid);
|
||
// 直接将图像数据保存到本地(例如保存为 JPEG 格式)
|
||
SaveBitmapToFile(bitmap, ReaderImagePath_Temp);
|
||
|
||
bitmap.Dispose();
|
||
bitmap = null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show("图像处理失败: " + ex.Message);
|
||
}
|
||
finally
|
||
{
|
||
m_MyCamera.MV_CC_FreeImageBuffer_NET(ref stFrameInfo);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("获取图像失败,错误码:" + nRet);
|
||
}
|
||
}
|
||
|
||
public string ReaderImagePath_Temp2;
|
||
/// <summary>
|
||
/// 抓拍图片 左相机
|
||
/// </summary>
|
||
public void ReceiveThreadProcess2()
|
||
{
|
||
MyCamera.MV_FRAME_OUT stFrameInfo = new MyCamera.MV_FRAME_OUT();
|
||
int nRet = m_MyCamera2.MV_CC_GetImageBuffer_NET(ref stFrameInfo, 1000);
|
||
if (nRet == MyCamera.MV_OK)
|
||
{
|
||
try
|
||
{
|
||
int width = stFrameInfo.stFrameInfo.nWidth;
|
||
int height = stFrameInfo.stFrameInfo.nHeight;
|
||
int stride = width; // Mono8: 每个像素1字节,stride = width
|
||
|
||
// 直接利用内存地址创建 Bitmap 对象
|
||
Bitmap bitmap = new Bitmap(width, height, stride, PixelFormat.Format8bppIndexed, stFrameInfo.pBufAddr);
|
||
|
||
// 设置灰度调色板
|
||
ColorPalette palette = bitmap.Palette;
|
||
for (int i = 0; i < 256; i++)
|
||
{
|
||
palette.Entries[i] = Color.FromArgb(i, i, i);
|
||
}
|
||
bitmap.Palette = palette;
|
||
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
// 转换为 WPF BitmapImage 用于 Image 控件显示
|
||
ImageSource2 = Common.BitmapToBitmapSource2(bitmap);
|
||
});
|
||
ReaderImagePath_Temp2 = AppDomain.CurrentDomain.BaseDirectory + "ImageList\\" + DateTime.Now.ToString("yyyy-MM-dd") + "\\" + Guid.NewGuid() + ".jpeg";
|
||
//DbHelperSQL.ExecuteSql($"update SCAN_MIAN set img_after='{ReaderImagePath_Temp2}' WHERE id = (SELECT id FROM ( SELECT id FROM SCAN_MIAN ORDER BY id DESC OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY ) AS subquery)");
|
||
DbHelperSQL.ExecuteSql($"update SCAN_MIAN set img_after='{ReaderImagePath_Temp2}' WHERE ex = '{ReadImageGuid_Temp[0]}'");
|
||
ReadImageGuid_Temp.Remove(ReadImageGuid_Temp[0]);
|
||
// 直接将图像数据保存到本地(例如保存为 JPEG 格式)
|
||
SaveBitmapToFile(bitmap, ReaderImagePath_Temp2);
|
||
|
||
bitmap.Dispose();
|
||
bitmap = null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show("图像处理失败: " + ex.Message);
|
||
}
|
||
finally
|
||
{
|
||
m_MyCamera.MV_CC_FreeImageBuffer_NET(ref stFrameInfo);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("获取图像失败,错误码:" + nRet);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 直接将图像保存为文件
|
||
/// </summary>
|
||
/// <param name="bitmap"></param>
|
||
/// <param name="filePath"></param>
|
||
private void SaveBitmapToFile(Bitmap bitmap, string filePath)
|
||
{
|
||
// 使用 MemoryStream 保存图像,减少内存占用
|
||
using (MemoryStream ms = new MemoryStream())
|
||
{
|
||
bitmap.Save(ms, ImageFormat.Jpeg); // 保存为 JPEG 格式
|
||
File.WriteAllBytes(filePath, ms.ToArray());
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 触发拍照指令 右相机
|
||
/// </summary>
|
||
public void TriggerExec()
|
||
{
|
||
string sql = @"
|
||
IF NOT EXISTS (SELECT 1 FROM SCAN_MIAN WHERE goodcode = @goodcode)
|
||
BEGIN
|
||
INSERT INTO SCAN_MIAN (time, goodcode, img, img_after, ex)
|
||
VALUES (@time, @goodcode, @img, @img_after, @ex)
|
||
END";
|
||
|
||
SqlParameter[] parameters = {
|
||
new SqlParameter("@time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
|
||
new SqlParameter("@goodcode", "123"),
|
||
new SqlParameter("@img", "321"),
|
||
new SqlParameter("@img_after", "imgpath_after"),
|
||
new SqlParameter("@ex", "guid")
|
||
};
|
||
DbHelperSQL.ExecuteSql(sql, parameters);
|
||
//// ch:触发命令 | en:Trigger command
|
||
//int nRet = m_MyCamera.MV_CC_SetCommandValue_NET("TriggerSoftware");
|
||
//if (MyCamera.MV_OK != nRet)
|
||
//{
|
||
// //ShowErrorMsg("Trigger Software Fail!", nRet);
|
||
//}
|
||
//if (m_bGrabbing)
|
||
//{
|
||
// Task.Run(() => {
|
||
// ReceiveThreadProcess();
|
||
// });
|
||
//}
|
||
//TriggerExec_Left();
|
||
}
|
||
/// <summary>
|
||
/// 触发拍照指令 左相机
|
||
/// </summary>
|
||
public void TriggerExec_Left()
|
||
{
|
||
// ch:触发命令 | en:Trigger command
|
||
int nRet = m_MyCamera2.MV_CC_SetCommandValue_NET("TriggerSoftware");
|
||
if (MyCamera.MV_OK != nRet)
|
||
{
|
||
//ShowErrorMsg("Trigger Software Fail!", nRet);
|
||
}
|
||
if (m_bGrabbing2)
|
||
{
|
||
Task.Run(() => {
|
||
ReceiveThreadProcess2();
|
||
});
|
||
}
|
||
}
|
||
public static Bitmap CropBarcodeRegion(Bitmap original, Rectangle roi)
|
||
{
|
||
// 确保裁剪区域在图片范围内
|
||
roi.Intersect(new Rectangle(0, 0, original.Width, original.Height));
|
||
|
||
// 进行裁剪
|
||
return original.Clone(roi, original.PixelFormat);
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region 通讯数据
|
||
private SerialPort _serialPort;
|
||
private IModbusSerialMaster _modbusMaster;
|
||
private DispatcherTimer _timer;
|
||
|
||
private bool _leftCameraTriggered;
|
||
public bool LeftCameraTriggered
|
||
{
|
||
get => _leftCameraTriggered;
|
||
set { _leftCameraTriggered = value; OnPropertyChanged("LeftCameraTriggered"); }
|
||
}
|
||
|
||
private bool _rightCameraTriggered;
|
||
public bool RightCameraTriggered
|
||
{
|
||
get => _rightCameraTriggered;
|
||
set { _rightCameraTriggered = value; OnPropertyChanged("RightCameraTriggered"); }
|
||
}
|
||
/// <summary>
|
||
/// 初始化连接PLC
|
||
/// </summary>
|
||
private void InitializeModbus()
|
||
{
|
||
try
|
||
{
|
||
// 创建并打开串口
|
||
_serialPort = new SerialPort("COM10", 115200, Parity.None, 8, StopBits.One);
|
||
_serialPort.ReadTimeout = 1000; // 设置串口读取超时为1秒
|
||
_serialPort.WriteTimeout = 1000; // 设置串口写入超时为1秒
|
||
_serialPort.Open();
|
||
|
||
// 创建 Modbus 主站
|
||
_modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort);
|
||
_modbusMaster.Transport.Retries = 3;
|
||
_modbusMaster.Transport.WaitToRetryMilliseconds = 100;
|
||
_modbusMaster.Transport.SlaveBusyUsesRetryCount = true;
|
||
Console.WriteLine("MODBUS 连接成功!");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine("MODBUS 连接失败: " + ex.Message);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 线程 监听接收信息
|
||
/// </summary>
|
||
private void StartPolling()
|
||
{
|
||
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
|
||
_timer.Tick += (s, e) => ReadPlcData();
|
||
_timer.Start();
|
||
}
|
||
private async Task<ushort[]> ReadHoldingRegistersWithRetry(byte slaveId, ushort startAddress, ushort numRegisters)
|
||
{
|
||
int retryCount = 3;
|
||
int waitTimeMs = 100;
|
||
ushort[] result = null;
|
||
|
||
for (int i = 0; i < retryCount; i++)
|
||
{
|
||
try
|
||
{
|
||
result = _modbusMaster.ReadHoldingRegisters(slaveId, startAddress, numRegisters);
|
||
return result; // 成功读取则返回结果
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Modbus 读取失败: {ex.Message}");
|
||
if (i < retryCount - 1) // 如果不是最后一次重试
|
||
{
|
||
await Task.Delay(waitTimeMs); // 等待一段时间后重试
|
||
}
|
||
}
|
||
}
|
||
|
||
return result; // 如果超过重试次数还没成功,可以返回 null 或处理失败逻辑
|
||
}
|
||
|
||
/// <summary>
|
||
/// 信息接收方法
|
||
/// </summary>
|
||
private void ReadPlcData()
|
||
{
|
||
try
|
||
{
|
||
//return;
|
||
byte slaveId = 3; // 站号3
|
||
|
||
//// 读取左相机寄存器VW2000(Modbus地址0)
|
||
//ushort[] leftCameraRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 0, 1);
|
||
|
||
//// 读取右相机寄存器VW2002(Modbus地址1)
|
||
//ushort[] rightCameraRegisters = _modbusMaster.ReadHoldingRegisters(slaveId, 1, 1);
|
||
|
||
// 一次性读取左相机和右相机的寄存器
|
||
ushort[] registers = _modbusMaster.ReadHoldingRegisters(slaveId, 0, 2);
|
||
|
||
// 解析左相机信号(VW2000的第0位)
|
||
LeftCameraTriggered = (registers[0] & 0x0001) != 0;
|
||
|
||
// 解析右相机信号(VW2002的第1位)
|
||
RightCameraTriggered = (registers[1] & 0x0002) != 0;
|
||
|
||
// 触发拍照
|
||
if (RightCameraTriggered) TakePhoto("右相机");
|
||
if (LeftCameraTriggered) TakePhoto("左相机");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine("PLC 读取失败: " + ex.Message);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 向上位机Y5/7发送右相机OK信号至PLC
|
||
/// </summary>
|
||
/// <param name="isActive">true=激活信号,false=关闭信号</param>
|
||
public void SendRightCameraOkSignal()
|
||
{
|
||
try
|
||
{
|
||
byte slaveId = 3; // 站号3
|
||
ushort coilAddress = 7; // Y5/7的Modbus VW 2014
|
||
short value = 1;
|
||
_modbusMaster.WriteSingleRegister(slaveId, coilAddress,(ushort)value);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine("PLC信号发送失败: " + ex.Message);
|
||
}
|
||
}
|
||
|
||
private DateTime lastLeftTriggerTime = DateTime.MinValue; // 左相机上次触发时间
|
||
private DateTime lastRightTriggerTime = DateTime.MinValue; // 右相机上次触发时间
|
||
|
||
/// <summary>
|
||
/// 获取哪个相机要触发
|
||
/// </summary>
|
||
/// <param name="camera"></param>
|
||
|
||
private void TakePhoto(string camera)
|
||
{
|
||
DateTime currentTime = DateTime.Now;
|
||
|
||
if (camera == "左相机")
|
||
{
|
||
// 判断左相机触发的间隔时间
|
||
if ((currentTime - lastLeftTriggerTime).TotalSeconds < 1)
|
||
{
|
||
Console.WriteLine("左相机触发太快,请稍等...");
|
||
return;
|
||
}
|
||
|
||
// 更新左相机的触发时间
|
||
lastLeftTriggerTime = currentTime;
|
||
|
||
Console.WriteLine("左相机触发" + currentTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||
TriggerExec_Left();
|
||
}
|
||
else if (camera == "右相机")
|
||
{
|
||
// 判断右相机触发的间隔时间
|
||
if ((currentTime - lastRightTriggerTime).TotalSeconds < 1)
|
||
{
|
||
Console.WriteLine("右相机触发太快,请稍等...");
|
||
return;
|
||
}
|
||
|
||
// 更新右相机的触发时间
|
||
lastRightTriggerTime = currentTime;
|
||
|
||
Console.WriteLine("右相机触发" + currentTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||
TriggerExec();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
public void Dispose()
|
||
{
|
||
// 停止定时器
|
||
_timer?.Stop();
|
||
|
||
// 释放 ModbusMaster 和串口资源
|
||
_modbusMaster?.Dispose();
|
||
if (_serialPort?.IsOpen == true) _serialPort.Close();
|
||
}
|
||
#endregion
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
public MianWindowViewModel()
|
||
{
|
||
GetPossibleRegions();
|
||
|
||
////ch: 初始化SDK | en: Initialize SDK
|
||
//MyCamera.MV_CC_Initialize_NET();
|
||
|
||
////相机初始化
|
||
//OpenCamera();
|
||
|
||
//plc 通讯初始化
|
||
//InitializeModbus();
|
||
//StartPolling();
|
||
}
|
||
/// <summary>
|
||
/// 获取裁切范围
|
||
/// </summary>
|
||
public void GetPossibleRegions()
|
||
{
|
||
possibleRegions.Clear();
|
||
DataView dv = DbHelperSQL.Query("select * from IMAGEINFO").Tables[0].DefaultView;
|
||
foreach (DataRowView rowView in dv)
|
||
{
|
||
possibleRegions.Add(new Rectangle(int.Parse(rowView["x"].ToString()), int.Parse(rowView["y"].ToString()), int.Parse(rowView["width"].ToString()), int.Parse(rowView["height"].ToString())));
|
||
}
|
||
}
|
||
public event PropertyChangedEventHandler PropertyChanged;
|
||
protected void OnPropertyChanged(string propertyName)
|
||
{
|
||
if (PropertyChanged != null)
|
||
{
|
||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
||
}
|
||
}
|
||
}
|
||
}
|