C#WPF与多PLC通讯、心跳、时间同步

        项目背景:在产线中,经常出现PLC触摸屏的时间与真实时间不一致的情况,因此需要定时进行时间同步。

        实现功能:1、通过S7实现PC与PLC通讯;2、通过创建自定义控件来实现多PLC通讯(将PLC实例到自定义控件中,通过控件来控制PLC通断);3、定时发送心跳信号。

        关键代码如下:(文章末尾有源码地址)

        1、主窗体代码

    public partial class MainWindow : Window
    {
        /// <summary>
        /// 时间同步线程
        /// </summary>
        Thread threadMain;

        /// <summary>
        /// 控件合集
        /// </summary>
        private List<InfoCard> controls = new List<InfoCard>();

        private FloatingWindow floatingWindow;

        /// <summary>
        /// 加载窗体
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
            Init();
            this.StateChanged += MainWindow_StateChanged;
            this.ShowInTaskbar = false;
            threadMain = new Thread(ThreadMainMothed);
            threadMain.Start();

            this.WindowState = WindowState.Maximized;

            // 最小化时显示悬浮窗,隐藏主窗口
            this.Hide();
            if (floatingWindow == null)
            {
                floatingWindow = new FloatingWindow(this);
                // 设置悬浮窗的位置等属性
            }
            floatingWindow.Show();
        }

        /// <summary>
        /// 窗体状态变化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainWindow_StateChanged(object sender, EventArgs e)
        {
            if (this.WindowState == WindowState.Minimized)
            {
                // 最小化时显示悬浮窗,隐藏主窗口
                this.Hide();
                if (floatingWindow == null)
                {
                    floatingWindow = new FloatingWindow(this);
                    // 设置悬浮窗的位置等属性
                }
                floatingWindow.Show();
            }
            else if (this.WindowState == WindowState.Normal)
            {
                // 恢复正常时隐藏悬浮窗,显示主窗口
                if (floatingWindow != null)
                {
                    floatingWindow.Close();
                    floatingWindow = null;
                }
                this.Show();
                this.Activate();
            }
        }

        /// <summary>
        /// 窗体移动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Border_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                this.DragMove();
            }
        }

        /// <summary>
        /// 窗体关闭
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            //这是最彻底的退出方式,不管什么线程都被强制退出,把程序结束的很干净。
            System.Environment.Exit(0);
            //this.Close();
        }

        DataModel.WorkLine workLine = new DataModel.WorkLine();

        /// <summary>
        /// 初始化加载控件及数据
        /// </summary>
        private void Init()
        {
            controls = new List<InfoCard>();
            InfoCard infoCard = new InfoCard();

            string path = AppDomain.CurrentDomain.BaseDirectory + "DeviceService.xml";

            try
            {
                XmlSerializer xmlSerializer = new XmlSerializer(workLine.GetType());
                using (FileStream fileStream = new FileStream(path, FileMode.Open))
                {
                    workLine = (DataModel.WorkLine)xmlSerializer.Deserialize(fileStream);
                    fileStream.Close();
                }

                foreach (DataModel.Station station in workLine.Stations)
                {
                    infoCard = new InfoCard();
                    infoCard.Icon = PackIconMaterialKind.Laptop;
                    infoCard.StationName = station.StationName;
                    infoCard.IPAddress = station.IPAddress;
                    infoCard.FillColor = Brushes.Green;
                    infoCard.ValueWidth = 30;
                    infoCard.PLCConnect();
                    if (infoCard.IsConnected) { }
                    controls.Add(infoCard);
                }
            }
            catch (Exception ex) { }

            // 将控件集合绑定到ItemsControl
            itemsControl.ItemsSource = controls;
        }

        /// <summary>
        /// 窗体最小化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Mini_Click(object sender, RoutedEventArgs e)
        {
            if (this.WindowState == WindowState.Minimized)
            {
                this.Hide();
            }
            else
            {
                this.WindowState = WindowState.Minimized;
            }
        }

        EnumTimeSync enumTimeSync = EnumTimeSync.Empty;


        /// <summary>
        /// 时间同步线程对应的主要方法
        /// </summary>
        private void ThreadMainMothed()
        {
            while (true)
            {
                try
                {
                    for (int i = 0; i < controls.Count; i++)
                    {
                        if (!controls[i].IsConning)
                        {
                            this.Dispatcher.Invoke(() =>
                            {
                                if (!controls[i].IsConnected)
                                {
                                    controls[i].OpenPLC();
                                }

                                foreach (var item in workLine.DataPoints)
                                {
                                    switch (item.PointID)
                                    {
                                        case "TimeSync":
                                            enumTimeSync = EnumTimeSync.TimeSync; break;
                                        case "TimeSign":
                                            enumTimeSync = EnumTimeSync.TimeSign; break;
                                        case "HeartBeat":
                                            enumTimeSync = EnumTimeSync.HeartBeat; break;
                                        default:
                                            enumTimeSync = EnumTimeSync.Empty; break;
                                    }

                                    controls[i].MethodEntry(enumTimeSync, int.Parse(item.DBNumber), int.Parse(item.StartByte), 2);
                                }
                            });
                        }
                    }
                }
                catch (Exception ex) { }

                Thread.Sleep(100);
            }
        }

        /// <summary>
        /// 手动同步时间
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Sync_Click(object sender, RoutedEventArgs e)
        {
            for (int i = 0; i < controls.Count; i++)
            {
                if (!controls[i].IsConning)
                {
                    this.Dispatcher.Invoke(() =>
                    {
                        controls[i].TimeSyncManual(10100, 0);
                    });

                }
            }
        }
    }

        2、悬浮窗口代码

public partial class FloatingWindow : Window
    {
        Window windowMain;
        public FloatingWindow(Window window)
        {
            InitializeComponent();

            var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
            this.Left = desktopWorkingArea.Right - this.Width - 50;
            this.Top = desktopWorkingArea.Bottom - this.Height - 50;
            this.ShowInTaskbar = false;

            windowMain = window;
        }

        /// <summary>
        /// 窗体移动/双击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                this.DragMove();
            }
            if (e.ClickCount == 2)
            {
                // 在这里添加双击事件的处理逻辑
                if (windowMain != null)
                {
                    windowMain.Show();
                    windowMain.Activate();
                    windowMain.WindowState = WindowState.Normal;
                }
            }
        }

        /// <summary>
        /// 窗体永远在最前
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Deactivated(object sender, EventArgs e)
        {
            Window window = (Window)sender;
            window.Topmost = true;
        }

        /// <summary>
        /// MenuItem的点击事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem menuItem = e.Source as MenuItem;
            if (menuItem != null)
            {
                if (menuItem.Header.ToString().Equals("主界面"))
                {
                    // 在这里添加双击事件的处理逻辑
                    if (windowMain != null)
                    {
                        windowMain.Show();
                        windowMain.Activate();
                        windowMain.WindowState = WindowState.Normal;
                    }
                }
                else if (menuItem.Header.ToString().Equals("备用"))
                {

                }
                else if (menuItem.Header.ToString().Equals("退出"))
                {
                    //这是最彻底的退出方式,不管什么线程都被强制退出,把程序结束的很干净。
                    System.Environment.Exit(0);
                }
            }
        }
    }

        3、自定义控件代码

public partial class InfoCard : UserControl
    {
        public InfoCard()
        {
            InitializeComponent();
            stopwatchHeartBeat.Start();
        }

        #region 属性

        private byte iHart = 1;

        private int iOldHour = -1;

        public string StationName
        {
            get { return (string)GetValue(StationNameProperty); }
            set { SetValue(StationNameProperty, value); }
        }
        public static readonly DependencyProperty StationNameProperty = DependencyProperty.Register("StationName", typeof(string), typeof(InfoCard));

        public string IPAddress
        {
            get { return (string)GetValue(IPAddressProperty); }
            set { SetValue(IPAddressProperty, value); }
        }
        public static readonly DependencyProperty IPAddressProperty = DependencyProperty.Register("IPAddress", typeof(string), typeof(InfoCard));

        public string Percentage
        {
            get { return (string)GetValue(PercentageProperty); }
            set { SetValue(PercentageProperty, value); }
        }
        public static readonly DependencyProperty PercentageProperty = DependencyProperty.Register("Percentage", typeof(string), typeof(InfoCard));


        public int Value
        {
            get { return (int)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(InfoCard));

        public PackIconMaterialKind Icon
        {
            get { return (PackIconMaterialKind)GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }
        public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(PackIconMaterialKind), typeof(InfoCard));

        public SolidColorBrush FillColor
        {
            get { return (SolidColorBrush)GetValue(FillColorProperty); }
            set { SetValue(FillColorProperty, value); }
        }
        public static readonly DependencyProperty FillColorProperty = DependencyProperty.Register("FillColor", typeof(SolidColorBrush), typeof(InfoCard));

        public int ValueWidth
        {
            get { return (int)GetValue(ValueWidthProperty); }
            set { SetValue(ValueWidthProperty, value); }
        }
        public static readonly DependencyProperty ValueWidthProperty = DependencyProperty.Register("ValueWidth", typeof(int), typeof(InfoCard));

        private bool ifConning = false;

        #endregion

        #region S7PLC

        static Plc plc;

        /// <summary>
        /// 连接PLC
        /// </summary>
        public void PLCConnect()
        {
            try
            {
                OpenPLC();
            }
            catch (Exception ex) { }
            PLCStationRefresh();
        }

        /// <summary>
        /// 断开PLC连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Disconnect_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (plc != null)
                {
                    plc.Close();
                }
            }
            catch (Exception ex) { }
            PLCStationRefresh();

        }

        /// <summary>
        /// 打开PLC连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btn_Connect_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                OpenPLC();
            }
            catch (Exception ex) { }
            PLCStationRefresh();
        }


        /// <summary>
        /// PLC状态刷新
        /// </summary>
        private void PLCStationRefresh()
        {
            if (plc != null && plc.IsConnected)
            {
                pro_bar.IsIndeterminate = true;
                FillColor = Brushes.Green;
            }
            else
            {
                pro_bar.IsIndeterminate = false;
                FillColor = Brushes.Red;
            }
        }

        /// <summary>
        /// 打开PLC
        /// </summary>
        public async void OpenPLC()
        {
            pro_bar.IsIndeterminate = ifConning = true;
            try
            {
                if (plc == null)
                    plc = new Plc(CpuType.S71500, IPAddress, 0, 0);
                if (plc != null && !plc.IsConnected)
                    await plc.OpenAsync();
                //TimeSync();
            }
            catch (Exception ex) { }

            if (plc != null && plc.IsConnected)
                pro_bar.IsIndeterminate = true;
            else
                pro_bar.IsIndeterminate = false;
            ifConning = false;
        }

        /// <summary>
        /// 时间同步
        /// </summary>
        /// <param name="DBNum">DB块地址</param>
        /// <param name="startAdr">起始地址</param>
        public void TimeSync(int DBNum, int startAdr)
        {
            try
            {
                if (iOldHour != DateTime.Now.Hour)
                {
                    iOldHour = DateTime.Now.Hour;
                    if (plc != null && plc.IsConnected)
                    {
                        //String写入
                        string strTime = DateTime.Now.ToString("yyyyMMddHHmmss");
                        var temp = Encoding.ASCII.GetBytes(strTime);   //将val字符串转换为字符数组
                        var bytes = S7.Net.Types.S7String.ToByteArray(strTime, temp.Length);
                        plc.WriteBytes(DataType.DataBlock, DBNum, startAdr, bytes);
                        TimeSign(DBNum, 22, (byte)1);
                    }
                }
            }
            catch (Exception ex) { plc = null; }
            finally { PLCStationRefresh(); }
        }

        /// <summary>
        /// 手动时间同步
        /// </summary>
        /// <param name="DBNum">DB块地址</param>
        /// <param name="startAdr">起始地址</param>
        public void TimeSyncManual(int DBNum, int startAdr)
        {
            try
            {
                if (plc != null && plc.IsConnected)
                {
                    //String写入
                    string strTime = DateTime.Now.ToString("yyyyMMddHHmmss");
                    var temp = Encoding.ASCII.GetBytes(strTime);   //将val字符串转换为字符数组
                    var bytes = S7.Net.Types.S7String.ToByteArray(strTime, temp.Length);
                    plc.WriteBytes(DataType.DataBlock, DBNum, startAdr, bytes);
                    TimeSign(DBNum, 22, (byte)1);
                }
            }
            catch (Exception ex) { plc = null; }
            finally { PLCStationRefresh(); }
        }

        /// <summary>
        /// 时间同步信号/心跳信号
        /// </summary>
        /// <param name="DBNum">DB块地址</param>
        /// <param name="startAdr">起始地址</param>
        /// <param name="value">信号量</param>
        public void TimeSign(int DBNum, int startAdr, byte value)
        {
            try
            {
                if (plc != null && plc.IsConnected)
                {

                    plc.Write("DB" + DBNum + ".DBW" + startAdr + ".0", value);
                    if (value == 1)
                        stopwatchTimeSign.Restart();
                    else
                        stopwatchTimeSign.Stop();
                }
            }
            catch (Exception ex) { plc = null; }
            finally { PLCStationRefresh(); }
        }

        /// <summary>
        /// 获取心跳信号值
        /// </summary>
        public byte GetHartSign
        {
            get
            {
                if (iHart == 1)
                    iHart = 2;
                else
                    iHart = 1;
                return iHart;
            }
        }

        /// <summary>
        /// PLC是否连接
        /// </summary>
        public bool IsConnected { get { return plc != null && plc.IsConnected; } }

        /// <summary>
        /// PLC是否正在连接中
        /// </summary>
        public bool IsConning { get { return ifConning; } }

        Stopwatch stopwatchHeartBeat = new Stopwatch();
        Stopwatch stopwatchTimeSign = new Stopwatch();

        /// <summary>
        /// 循环程序入口
        /// </summary>
        /// <param name="enumTimeSync">触发类型</param>
        /// <param name="DBNum">DB块</param>
        /// <param name="startAdr">起始地址</param>
        /// <param name="value">值</param>
        public void MethodEntry(EnumTimeSync enumTimeSync, int DBNum, int startAdr, byte? value = null)
        {
            switch (enumTimeSync)
            {
                case EnumTimeSync.TimeSync:
                    TimeSync(DBNum, startAdr);
                    break;
                case EnumTimeSync.TimeSign:
                    if (value != null && stopwatchTimeSign.ElapsedMilliseconds > 3000)
                        TimeSign(DBNum, startAdr, (byte)value);
                    break;
                case EnumTimeSync.HeartBeat:
                    if (stopwatchHeartBeat.ElapsedMilliseconds > 3000)
                    {
                        TimeSign(DBNum, startAdr, GetHartSign);
                        stopwatchHeartBeat.Restart();
                    }
                    break;
                default:
                    break;
            }
        }


        #endregion


    }

        以上是关键代码,欢迎给位伙伴交流指正~!

        源码连接:多PLC通讯之时间同步心跳机制

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值