diff --git a/OneClick.slnx b/OneClick.slnx new file mode 100644 index 0000000..c148ece --- /dev/null +++ b/OneClick.slnx @@ -0,0 +1,3 @@ + + + diff --git a/OneClick/App.xaml b/OneClick/App.xaml new file mode 100644 index 0000000..d0f0b1f --- /dev/null +++ b/OneClick/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/OneClick/App.xaml.cs b/OneClick/App.xaml.cs new file mode 100644 index 0000000..ea262df --- /dev/null +++ b/OneClick/App.xaml.cs @@ -0,0 +1,16 @@ +using Prism.Ioc; +using System.ComponentModel; +using System.Configuration; +using System.Data; +using System.Windows; + +namespace OneClick +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/OneClick/AssemblyInfo.cs b/OneClick/AssemblyInfo.cs new file mode 100644 index 0000000..b0ec827 --- /dev/null +++ b/OneClick/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/OneClick/AxisService.cs b/OneClick/AxisService.cs new file mode 100644 index 0000000..c0a662b --- /dev/null +++ b/OneClick/AxisService.cs @@ -0,0 +1,344 @@ +using GTN; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace OneClick +{ + public class AxisService + { + + public void InitMotionCard() + { + // 初始化运动控制卡 + int sRtn = -1; + short eCATStatus = -1; + short core = 1; // 使用第一个EtherCAT主站 + try + { + sRtn = GTN.mc.GTN_Open(5, 2); + //MessageBox.Show($"运动控制卡初始化成功,返回代码{sRtn}", "信息", MessageBoxButton.OK, MessageBoxImage.Information); + sRtn = GTN.mc.GTN_InitEcatComm(core); + int waitcount = 0; + do + { + sRtn = GTN.mc.GTN_IsEcatReady(core, out eCATStatus); + //waitcount++; + if (waitcount > 50000) + { + waitcount = 0; + MessageBox.Show("运动控制卡以太网通信初始化失败,请检查连接!", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + return; + } + } while (eCATStatus != 1 || sRtn != 0); + sRtn = GTN.mc.GTN_StartEcatComm(core); + } + + catch (Exception ex) + { + MessageBox.Show($"运动控制卡初始化失败:{ex.Message},错误代码{sRtn}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + /// + /// 上使能 + /// + /// 轴号 + /// 核号 + /// returns>状态码 + public int AxisOn(short selectedindex, short core) + { + int sRtn = -1; + sRtn = mc.GTN_ClrSts(core, (short)(selectedindex), 1); + sRtn = GTN.mc.GTN_AxisOn(core, selectedindex); + return sRtn; + } + + /// + /// 下使能 + /// + /// 轴号 + /// 核号 + /// 状态码 + public int AxisOff(short selectedindex, short core) + { + int sRtn = -1; + sRtn = GTN.mc.GTN_AxisOff(core, selectedindex); + return sRtn; + } + + /// + /// 重置报警 + /// + /// 轴号 + /// 核号 + /// + public int ResetAlarm(short selectedindex, short core) + { + int sRtn = -1; + sRtn = mc.GTN_ClrSts(core, (short)(selectedindex), 1); + return sRtn; + } + + /// + /// 重置位置 + /// + /// 轴号 + /// 核号 + /// + public int ResetPosition(short selectedindex, short core) + { + int sRtn = -1; + sRtn = mc.GTN_ZeroPos(core, selectedindex, 1); + return sRtn; + } + + + /// + /// 读取轴的状态 + /// + /// 核号 + /// 轴号 + /// 轴状态 + /// + public int ReadAxisStatus(short core, short axis, out string AxisSts) + { + int sRtn = -1; + Int32 psts; + UInt32 clock; + AxisSts = ""; + // 读取轴状态 + sRtn = mc.GTN_GetSts(core, axis, out psts, 1, out clock); + + if ((psts & 0x2) != 0) + { + AxisSts += "报警\r\n"; + } + if ((psts & 0x200) != 0) + { + AxisSts += "使能\r\n"; + } + if ((psts & 0x40) != 0) + { + AxisSts += "负限位触发\r\n"; + } + if ((psts & 0x20) != 0) + { + AxisSts += "正限位触发\r\n"; + } + if ((psts & 0x400) != 0) + { + AxisSts += "正在移动\r\n"; + } + if ((psts & 0x800) != 0) + { + AxisSts += "到位\r\n"; + } + if ((psts & 0x10) != 0) + { + AxisSts += "跟随误差超限\r\n"; + } + if ((psts & 0x80) != 0) + { + AxisSts += "平滑停止IO触发\r\n"; + } + if ((psts & 0x100) != 0) + { + AxisSts += "急停触发\r\n"; + } + + + + return sRtn; + + } + + /// + /// Jog开始 + /// + /// 核号 + /// 轴号 + /// + public int JogMove(short core, short axis,int rpm) + { + int sRtn = -1; + double speed; + Int32 mask; + // 设置轴为点动模式 + sRtn = mc.GTN_PrfJog(core, axis); + if (sRtn != mc.CMD_SUCCESS) return sRtn; + + // 将参数写死 + mc.TJogPrm jog = new mc.TJogPrm + { + acc = 10, // 加速度 + dec = 10, // 减速度 + smooth = 0.0 // 平滑时间 + }; + speed = rpm*0.0667; + // 传入结构体引用 + sRtn = mc.GTN_SetJogPrm(core, axis, ref jog); + if (sRtn != mc.CMD_SUCCESS) return sRtn; + // 设置点动速度 + sRtn = mc.GTN_SetVel(1, axis, speed); + // 开始点动 + mask= (int)AxisAndCoreToMask(core, axis); + sRtn =mc.GTN_Update(core,mask); + return sRtn; + } + + /// + /// 停止运动 + /// + /// 核号 + /// 轴号 + /// + public int AxisStop(short core, short axis) + { + int sRtn = -1; + Int32 mask; + Int32 option=0; + // 停止点动 + mask = (int)AxisAndCoreToMask(core, axis); + sRtn = mc.GTN_Stop(core, mask,option); + return sRtn; + } + + + /// + /// 定时旋转 + /// + /// 核号 + /// 轴号 + /// 速度 + /// 时间 + /// + + public int TimelyRotate(short core,short axis,int rpm,int time) + { + int sRtn = -1; + sRtn = mc.GTN_PrfTrap(core, axis); + + + mc.TTrapPrm trap = new mc.TTrapPrm + { + acc = 10, // 加速度 + dec = 10, // 减速度 + velStart = 0.0, // 平滑时间 + smoothTime = 10 + }; + sRtn = mc.GTN_SetTrapPrm(core, axis, ref trap); + sRtn=mc.GTN_SetPos(core, axis, (int)((double)rpm * 0.0667*time*1000)); + sRtn=mc.GTN_SetVel(core,axis, (double)rpm*0.0667); + sRtn=mc.GTN_Update(core, (int)AxisAndCoreToMask(core, axis)); + + + return sRtn; + + } + + + /// + /// 关闭控制卡 + /// + /// 核号 + public void CloseMotionCard(short core) + { + // 关闭运动控制卡 + int sRtn = -1; + try + { + sRtn = mc.GTN_TerminateEcatComm(core); + sRtn = GTN.mc.GTN_Close(); + //MessageBox.Show($"运动控制卡关闭成功,返回代码{sRtn}", "信息", MessageBoxButton.OK, MessageBoxImage.Information); + } + catch (Exception ex) + { + MessageBox.Show($"运动控制卡关闭失败:{ex.Message},错误代码{sRtn}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); + } + + + } + + /// + /// 将给定的 core(核号) 与 axis(轴号) 转换为 32 位掩码 (mask)。 + /// 规则: + /// - core=1 映射轴 1..32 到 mask 的位 0..31(轴1 -> bit0,轴32 -> bit31) + /// - core=2 映射轴 33..64 到 mask 的位 0..31(轴33 -> bit0,轴64 -> bit31) + /// 返回值为 UInt32 掩码;当输入不合法时抛出 。 + /// + /// 核号(目前支持 1 或 2) + /// 轴号(1..64) + /// 对应的 32 位掩码 + public static uint AxisAndCoreToMask(short core, short axis) + { + if (core < 1 || core > 2) + { + throw new ArgumentOutOfRangeException(nameof(core), "目前只支持 core=1 或 core=2。"); + } + + int baseAxis = (core - 1) * 32; // core=1 -> 0, core=2 -> 32 + if (axis <= baseAxis || axis > baseAxis + 32) + { + throw new ArgumentOutOfRangeException(nameof(axis), $"轴号不在 core {core} 的有效范围 ({baseAxis + 1}..{baseAxis + 32})。"); + } + + int bitIndex = axis - baseAxis - 1; // 0..31 + return 1u << bitIndex; + } + + /// + /// 将 32 位掩码转换为该 core 下的轴列表。 + /// 规则同 。 + /// 返回的轴号为全局轴号(1..64)。 + /// + /// 核号(1 或 2) + /// 32 位掩码,bit0 对应该 core 的最低号轴 + /// 被选中轴的列表(按升序),掩码为0时返回空列表。 + public static List MaskToAxisList(short core, uint mask) + { + var axes = new List(); + + if (core < 1 || core > 2) + { + throw new ArgumentOutOfRangeException(nameof(core), "目前只支持 core=1 或 core=2。"); + } + + int baseAxis = (core - 1) * 32; // core=1 -> 0, core=2 -> 32 + + for (int bit = 0; bit < 32; bit++) + { + if ((mask & (1u << bit)) != 0) + { + short axis = (short)(baseAxis + bit + 1); + axes.Add(axis); + } + } + + return axes; + } + + /// + /// 尝试将 axis 和 core 转换为 mask,转换成功返回 true 并输出 mask;失败返回 false 并输出 0。 + /// 该方法便于在不抛出异常的情况下做验证。 + /// + /// 核号 + /// 轴号 + /// 输出掩码 + /// 是否成功 + public static bool TryAxisAndCoreToMask(short core, short axis, out uint mask) + { + mask = 0; + if (core < 1 || core > 2) return false; + int baseAxis = (core - 1) * 32; + if (axis <= baseAxis || axis > baseAxis + 32) return false; + int bitIndex = axis - baseAxis - 1; + mask = 1u << bitIndex; + return true; + } + } +} diff --git a/OneClick/CameraService.cs b/OneClick/CameraService.cs new file mode 100644 index 0000000..2faeeac --- /dev/null +++ b/OneClick/CameraService.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using Basler.Pylon; + +namespace OneClick +{ + public interface ICameraService + { + IList EnumerateDevices(); + void Open(string deviceId); + void Close(); + bool StartGrabbing(bool continuous = true); + void StopGrabbing(); + IGrabResult? GrabOnePic(int timeoutMs = 2000); + string? CurrentDeviceId { get; } + event EventHandler? GrabStarted; + event EventHandler? ImageGrabbed; + event EventHandler? GrabStopped; + event EventHandler? ConnectionLost; + } + + public class BaslerCameraService : ICameraService + { + private Camera? _camera; + private readonly PixelDataConverter _converter = new PixelDataConverter(); + + public string? CurrentDeviceId { get; private set; } + + public event EventHandler? GrabStarted; + public event EventHandler? ImageGrabbed; + public event EventHandler? GrabStopped; + public event EventHandler? ConnectionLost; + + public IList EnumerateDevices() + { + var devices = CameraFinder.Enumerate(); + var ids = new List(); + foreach (var info in devices) + { + if (info.ContainsKey(CameraInfoKey.SerialNumber)) + { + ids.Add(info[CameraInfoKey.SerialNumber]); + } + } + return ids; + } + + public void Open(string deviceId) + { + if (string.IsNullOrWhiteSpace(deviceId)) + throw new ArgumentException("豸IDΪա", nameof(deviceId)); + + Close(); + + _camera = new Camera(deviceId); + + // ע¼ + _camera.ConnectionLost += Camera_ConnectionLost; + _camera.StreamGrabber.GrabStarted += StreamGrabber_GrabStarted; + _camera.StreamGrabber.ImageGrabbed += StreamGrabber_ImageGrabbed; + _camera.StreamGrabber.GrabStopped += StreamGrabber_GrabStopped; + + _camera.Open(); + CurrentDeviceId = deviceId; + } + + public void Close() + { + try + { + if (_camera != null) + { + if (_camera.StreamGrabber.IsGrabbing) + _camera.StreamGrabber.Stop(); + } + } + catch + { + // ֹͣʧܲӰر + } + + try + { + if (_camera != null) + { + // ע¼ + _camera.ConnectionLost -= Camera_ConnectionLost; + _camera.StreamGrabber.GrabStarted -= StreamGrabber_GrabStarted; + _camera.StreamGrabber.ImageGrabbed -= StreamGrabber_ImageGrabbed; + _camera.StreamGrabber.GrabStopped -= StreamGrabber_GrabStopped; + + _camera.Close(); + _camera.Dispose(); + _camera = null; + } + } + finally + { + CurrentDeviceId = null; + } + } + + public bool StartGrabbing(bool continuous = true) + { + if (_camera == null) + throw new InvalidOperationException("δ򿪡"); + + try + { + if (continuous) + { + Configuration.AcquireContinuous(_camera, null); + _camera.StreamGrabber.Start(GrabStrategy.OneByOne, GrabLoop.ProvidedByStreamGrabber); + } + else + { + Configuration.AcquireSingleFrame(_camera, null); + _camera.StreamGrabber.Start(1, GrabStrategy.OneByOne, GrabLoop.ProvidedByStreamGrabber); + } + return true; + } + catch + { + return false; + } + } + + public void StopGrabbing() + { + if (_camera == null) return; + + try + { + _camera.StreamGrabber.Stop(); + } + catch + { + // ֹͣ쳣 + } + } + + public IGrabResult? GrabOnePic(int timeoutMs = 2000) + { + if (_camera == null) + return null; + + return _camera.StreamGrabber.GrabOne(timeoutMs); + } + + private void Camera_ConnectionLost(object? sender, EventArgs e) + { + ConnectionLost?.Invoke(this, EventArgs.Empty); + } + + private void StreamGrabber_GrabStarted(object? sender, EventArgs e) + { + GrabStarted?.Invoke(this, EventArgs.Empty); + } + + private void StreamGrabber_ImageGrabbed(object? sender, ImageGrabbedEventArgs e) + { + // ֱת UI ʾ + ImageGrabbed?.Invoke(this, e); + } + + private void StreamGrabber_GrabStopped(object? sender, GrabStopEventArgs e) + { + GrabStopped?.Invoke(this, e); + } + } +} diff --git a/OneClick/ChangeSetting.xaml b/OneClick/ChangeSetting.xaml new file mode 100644 index 0000000..260cab8 --- /dev/null +++ b/OneClick/ChangeSetting.xaml @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + +