1、利用wpf+mvvm作为上位机开发技术,通过读写分离的形式,启动一个线程来进行对DB块数据的读取显示;通过按钮事件对选中的字段向PLCDB块写入数据。
2:主要代码
2.1 View层
<hc:Window x:Class="HandyControlProject1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:hc="https://handyorg.github.io/handycontrol"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator},Path=Main}"
Title="MainWindow"
Loaded="Window_Loaded"
WindowStartupLocation="CenterScreen"
Style="{StaticResource WindowWin10}"
ShowTitle="True"
Height="450"
Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Button x:Name="button" Click="button_Click" Content="Connect" HorizontalAlignment="Left" Margin="5" VerticalAlignment="Center" Width="75" />
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="5" TextWrapping="Wrap" Text="192.168.1.140" VerticalAlignment="Center" Width="120"/>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="5" TextWrapping="Wrap" Text="" VerticalAlignment="Center"/>
</StackPanel>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" SelectedItem="{Binding SelectedTag}" ItemsSource="{Binding DB103TagList}" />
<Border Grid.Column="1" Background="Gray" BorderBrush="White" BorderThickness="1" CornerRadius="5">
<StackPanel Orientation="Vertical">
<TextBlock Foreground="Yellow" FontSize="18" Text="{Binding SelectedTag.TagName}"></TextBlock>
<TextBox Margin="0,5" x:Name="txtTagValue"></TextBox>
<Button Margin="0,5" x:Name="WriteTagValue" Content="写入" Click="WriteTagValue_Click"></Button>
</StackPanel>
</Border>
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<TextBlock x:Name="cputime"></TextBlock>
<TextBlock x:Name="debugmsg" Margin="10,0"></TextBlock>
</StackPanel>
</Grid>
</hc:Window>
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Threading.Tasks;
using CommonServiceLocator;
using HandyControlProject1.ViewModel;
using Sharp7;
namespace HandyControlProject1
{
public partial class MainWindow
{
private CancellationTokenSource TokenSource;
private Task CurrentTask;
MainViewModel Main;
S7Client client;
S7Client.S7CpuInfo s7CpuInfo;
S7Client.S7OrderCode s7OrderCode;
public MainWindow()
{
InitializeComponent();
Main= ServiceLocator.Current.GetInstance<MainViewModel>();
client = new Sharp7.S7Client();
s7CpuInfo = new S7Client.S7CpuInfo();
s7OrderCode = new S7Client.S7OrderCode();
//PDU大小为默认为480,1500为960,1200、300为240,400为480,
//此处用1500测试设置960。配置文件中的lenght长度应该小于Pdu-18,1500的length最大为942,
//如果读取的长度大于942,可以拆分为多个包读取
client.PduSizeRequested = 960;//设置pdu
//默认为PG连接,设置为基本连接
client.SetConnectionType(3);
}
private void Window_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
}
private void button_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (!client.Connected)
{
//连接Plc
var result = client.ConnectTo(this.textBox.Text, 0, 0);
if (result == 0)
{
client.GetCpuInfo(ref s7CpuInfo);
client.GetOrderCode(ref s7OrderCode);
this.textBlock.Text = ($"Connected to {this.textBox.Text},CPU类型:{s7CpuInfo.ModuleTypeName},订货号:{s7OrderCode.Code}");
this.button.Content = "断开连接";
//启动界面刷新线程
// create the cancellation token
TokenSource = new CancellationTokenSource();
CancellationToken token = TokenSource.Token;
// create the task
CurrentTask = Task.Factory.StartNew(() =>
{
while (true)
{
if (token.IsCancellationRequested)
{
setmessage("Task cancel detected");
break;
}
else
{
byte[] buffer = new byte[310];
int res= client.DBRead(3, 0, buffer.Length, buffer);
Console.WriteLine(client.ErrorText(res));
foreach (var item in Main.DB103TagList)
{
switch (item.TagType)
{
case "Int":
item.TagValue = S7.GetIntAt(buffer, (int)item.TagAddress).ToString();
break;
case "Bool":
string[] address = item.TagAddress.ToString("0.0").Split('.');
item.TagValue = S7.GetBitAt(buffer, int.Parse(address[0]), int.Parse(address[1])).ToString();
break;
default:
break;
}
}
setmessage(client.ExecutionTime.ToString());
}
Thread.Sleep(100);
}
}, token);
}
else
{
this.textBlock.Text = (client.ErrorText(result));
}
}
else
{
var result = client.Disconnect();
if (result == 0)
{
if (CurrentTask != null)
{
if (CurrentTask.Status == TaskStatus.Running) { }
{
TokenSource.Cancel();
setmessage("Task cancel");
}
}
this.textBlock.Text = ($"Disconnected from {this.textBox.Text}");
this.button.Content = "连接";
}
else
{
this.textBlock.Text = (client.ErrorText(result));
}
}
}
void setmessage(string message)
{
this.Dispatcher.Invoke(() =>
{
this.debugmsg.Text = message;
this.cputime.Text = DateTime.Now.ToString();
});
}
private void WriteTagValue_Click(object sender, System.Windows.RoutedEventArgs e)
{
if (Main.SelectedTag != null)
{
string[] address = Main.SelectedTag.TagAddress.ToString("0.0").Split('.');
int zhengshu = int.Parse(address[0]);
int xiaoshu = int.Parse(address[1]);
int gewei = int.Parse(address[0]) % 10;
switch (Main.SelectedTag.TagType)
{
case "Int":
byte[] buff = new byte[2];
int res= client.DBRead(3, zhengshu, buff.Length, buff);
if (res == 0)
{
S7.SetIntAt(buff, 0, short.Parse(this.txtTagValue.Text));
client.DBWrite(3, zhengshu, buff.Length, buff);
}
break;
case "Bool":
byte[] buffbit = new byte[1];
int resbit = client.DBRead(3, zhengshu, buffbit.Length, buffbit);
if (resbit == 0)
{
S7.SetBitAt(ref buffbit, 0, xiaoshu, this.txtTagValue.Text == "1" ? true : false);
client.DBWrite(3, zhengshu, buffbit.Length, buffbit);
}
break;
default:
break;
}
}
}
}
}
2.2 ViewModel层
using GalaSoft.MvvmLight;
using HandyControlProject1.Model;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace HandyControlProject1.ViewModel
{
/// <summary>
/// This class contains properties that the main View can data bind to.
/// <para>
/// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
/// </para>
/// <para>
/// You can also use Blend to data bind with the tool's support.
/// </para>
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class MainViewModel : ViewModelBase
{
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
////if (IsInDesignMode)
////{
//// // Code runs in Blend --> create design time data.
////}
////else
////{
//// // Code runs "for real"
////}
///
string appStartupPath = System.IO.Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
string filePath = appStartupPath+@"\DB103.csv";
FileStream fileStream = new FileStream(filePath,FileMode.Open,FileAccess.Read);
StreamReader reader = new StreamReader(fileStream, System.Text.Encoding.Default);
string str = null;
DB103TagList = new List<Tags>();
while ((str = reader.ReadLine()) != null)
{
Tags tag = new Tags();
string[] strs = str.Split(',');
if (strs.Length == 3)
{
tag.TagName = strs[0].ToString();
tag.TagType = strs[1].ToString();
tag.TagAddress = double.Parse(strs[2].ToString());
DB103TagList.Add(tag);
}
}
reader.Close();
fileStream.Close();
}
private List<Tags> _DB103TagList;
public List<Tags> DB103TagList
{
get { return _DB103TagList; }
set { _DB103TagList = value;
RaisePropertyChanged();
}
}
private Tags _SelectedTag;
public Tags SelectedTag
{
get { return _SelectedTag; }
set { _SelectedTag = value;
RaisePropertyChanged();
}
}
}
}
2.3 Model
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HandyControlProject1.Model
{
public class Tags:ObservableObject
{
private string _TagName;
public string TagName
{
get { return _TagName; }
set { _TagName = value;
RaisePropertyChanged(()=>TagName);
}
}
private string _TagType;
public string TagType
{
get { return _TagType; }
set { _TagType = value;
RaisePropertyChanged(() => TagType);
}
}
private double _TagAddress;
public double TagAddress
{
get { return _TagAddress; }
set { _TagAddress = value;
RaisePropertyChanged(() => TagAddress);
}
}
private string _TagValue;
public string TagValue
{
get { return _TagValue; }
set { _TagValue = value;
RaisePropertyChanged(() => TagValue);
}
}
}
}
3.需要注意的问题
当多线程进行数据读取和数据写入时,会出现线程交叉访问导致数据读写异常的情况,可以修改sharp7源代码在DBRead和DBWrite函数中加锁处理该问题。
private static object objlock = new object();
public int DBRead(int DBNumber, int Start, int Size, byte[] Buffer)
{
lock (objlock)
{
return ReadArea(S7Consts.S7AreaDB, DBNumber, Start, Size, S7Consts.S7WLByte, Buffer);
}
}
public int DBWrite(int DBNumber, int Start, int Size, byte[] Buffer)
{
lock (objlock)
{
return WriteArea(S7Consts.S7AreaDB, DBNumber, Start, Size, S7Consts.S7WLByte, Buffer);
}
}