Commit 620060c9 authored by 潘栩锋's avatar 潘栩锋 🚴

1. 添加 FLY.ModbusMapper

parent 39a52e19
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLY.Modbus
{
/// <summary>
/// PLC 区管理
/// </summary>
public abstract class AreaManager
{
/// <summary>
/// 现在工作中的读取计划任务
/// </summary>
public List<Plan> plan = new List<Plan>();
/// <summary>
/// 当true, plan_buffer -> plan
/// </summary>
protected bool isPlanChanged = false;
/// <summary>
/// 设置读取计划任务时的缓冲,目前线程安全
/// </summary>
protected List<Plan> plan_buffer = new List<Plan>();
/// <summary>
/// 当前执行的计划任务序号
/// </summary>
protected int currIndex = 0;
/// <summary>
/// 获取下一个plan,返回下一个plan,若已经到了最后一个,则在开始
/// 下一个之前,返回一个null
/// </summary>
/// <returns></returns>
public Plan GetNextPlan()
{
int cnt = plan.Count();
if (cnt <= 0) return null;
if (currIndex >= cnt)
{
currIndex = 0;
return null;
}
Plan p = plan.ElementAt(currIndex);
currIndex += 1;
return p;
}
/// <summary>
/// 清除doing状态
/// </summary>
public void ClearPlanState()
{
foreach (Plan p in plan)
{
p.isDoing = false;
}
currIndex = 0;
}
/// <summary>
/// 一个property 对应 N个 PLC寄存器;
/// 地址必须是从小到大排列,不能有重叠
/// </summary>
public List<DataToRegs> regs = new List<DataToRegs>();
protected PLCAddressArea area;
protected int maxOfOneRead = 50;
public AreaManager(PLCAddressArea area, int maxOfOneRead)
{
this.area = area;
this.maxOfOneRead = maxOfOneRead;
}
/// <summary>
/// 根据DataToRegs列表构建数组
/// </summary>
public void BuildArray(List<DataToRegs> DRmap)
{
List<DataToRegs> drs = DRmap.FindAll((c) =>
{
return c.dataArea == area;
});
// 分配寄存器存储空间
regs.Clear();
foreach (DataToRegs dr in drs)
{
regs.Add(dr);
}
regs.Sort((r1, r2) =>
{
if (r1.addr < r2.addr)
return -1;
else if (r1.addr > r2.addr)
return 1;
else
return 0;
});
}
/// <summary>
///
/// </summary>
public void MakePlanReal()
{
lock (plan_buffer)
{
if (isPlanChanged)
{
isPlanChanged = false;
plan.Clear();
plan.AddRange(plan_buffer);
}
}
}
/// <summary>
/// 根据当前regs的配置指定读寄存器的计划
/// 只是设置 plan_buffer
/// </summary>
public abstract void MakePlan();
/// <summary>
/// 从本地PC 属性获取value 更新到 本地PLC 寄存器,
/// 目的: 当从PLC读取寄存器值时,能比较不同,产生变化事件
/// </summary>
public void SetAllIsPlcValueChanged()
{
foreach (DataToRegs dr in regs)
{
dr.isPlcValueChanged = true;
}
}
}
/// <summary>
/// 每次向PLC读取寄存器的任务
/// </summary>
public class Plan
{
/// <summary>
/// coil or register
/// </summary>
public PLCAddressArea area;
/// <summary>
/// 地址
/// </summary>
public int addr;
/// <summary>
/// 数量
/// </summary>
public int num;
/// <summary>
/// 是否正在读取
/// </summary>
public bool isDoing;
/// <summary>
///
/// </summary>
/// <param name="area"></param>
/// <param name="addr"></param>
/// <param name="num"></param>
public Plan(PLCAddressArea area, int addr, int num)
{
this.area = area;
this.addr = addr;
this.num = num;
}
}
}
......@@ -118,7 +118,7 @@ namespace FLY.Modbus
{
Pack_Proto p = new Pack_Proto();
int rlen;
if (!p.TryParse(inBuf, startIndex, out rlen))
if (!p.TryParse(inBuf, startIndex, inBuf.Length, out rlen))
{
//处理失败
return startIndex;
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLY.Modbus
{
/// <summary>
/// property 与 PLC继电器 更新操作,与 RegsData 对应
/// </summary>
public class CoilData : AreaManager
{
public CoilData(PLCAddressArea area, int maxOfOneRead) : base(area, maxOfOneRead)
{
}
/// <summary>
/// 根据当前regs的配置指定读寄存器的计划
/// </summary>
public override void MakePlan()
{
//this.maxOfOneRead = maxOfOneRead;
if (regs == null)
{
throw new Exception("还没有执行 BuildArray()");
}
lock (plan_buffer)
{
plan_buffer.Clear();
int addr = -1;
int num = 0;
for (int i = 0; i < regs.Count(); i++)
{
if (!regs[i].isNeedUpdate)
continue;
if (addr == -1)
{
addr = regs[i].addr;
num = 1;
}
else
{
int n = regs[i].addr - addr + 1;
if (n <= maxOfOneRead)
{
num = n;
}
else
{
plan_buffer.Add(new Plan(area, addr, num));
addr = -1;
i--;
}
}
}
if (addr != -1)
{
plan_buffer.Add(new Plan(area, addr, num));
addr = -1;
}
isPlanChanged = true;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLY.Modbus
{
/// <summary>
/// 数据映射的类;
/// Data: PC 属性
/// Regs: PLC 对象(寄存器、线圈)
/// </summary>
public class DataToRegs
{
/// <summary>
/// 所属的plc
/// </summary>
public ModbusMapper mapper;
/// <summary>
/// 对应 PLC寄存器区 coil or register
/// </summary>
public PLCAddressArea dataArea;
/// <summary>
/// PLC 首地址
/// </summary>
public int addr;
/// <summary>
/// PLC 尾地址
/// </summary>
public int endAddr;
/// <summary>
/// value 的C#类型 float,int16,int32
/// </summary>
public REG_TYPE type;
/// <summary>
/// 放大倍数, value * scale 才是属性的值
/// </summary>
public double scale;
/// <summary>
/// object
/// </summary>
public object owner;
/// <summary>
/// object 的 property 名称, 只能是 数字类型都是double, 剩下就是 bool
/// </summary>
public string propertyName;
/// <summary>
/// property 的值, 没有缩小. 只有 double 与 bool
/// </summary>
public object value;
/// <summary>
/// PLC obj ,bool or UInt16[]
/// </summary>
public object plcValue;
/// <summary>
/// PLC obj 寄存器值是否改变
/// </summary>
public bool isPlcValueChanged;
/// <summary>
/// 需要从PLC读取数据更新
/// </summary>
public bool isNeedUpdate = false;
/// <summary>
///
/// </summary>
/// <param name="mapper"></param>
/// <param name="dataArea"></param>
/// <param name="addr"></param>
/// <param name="type"></param>
/// <param name="scale"></param>
/// <param name="owner"></param>
/// <param name="propertyname"></param>
public DataToRegs(ModbusMapper mapper, PLCAddressArea dataArea, int addr, REG_TYPE type, double scale, object owner, string propertyname)
{
this.mapper = mapper;
this.dataArea = dataArea;
this.addr = addr;
this.type = type;
this.scale = scale;
this.owner = owner;
this.propertyName = string.Copy(propertyname);
if (type == REG_TYPE.BOOL)
{
plcValue = false;
endAddr = addr;
}
else
{
plcValue = new UInt16[ModbusMapper.RegSize(type)];
endAddr = addr + ((UInt16[])plcValue).Count() - 1;
}
}
/// <summary>
/// PLC 对象(寄存器、线圈)转 PC 属性
/// </summary>
/// <param name="plc_value">bool or UInt16[]</param>
/// <returns></returns>
public object ToPropertyObj(object plc_value)
{
if (type == REG_TYPE.BOOL)
{
return plc_value;
}
else
{
return RegTypeConverter.ToObject((UInt16[])plc_value, type, scale);
}
}
/// <summary>
/// PLC 对象(寄存器、线圈)转 PC 属性, 本地
/// </summary>
public void ToPropertyObj()
{
value = ToPropertyObj(plcValue);
}
/// <summary>
/// PC 属性 转 PLC 对象(寄存器、线圈)
/// </summary>
/// <param name="value"></param>
/// <returns>bool or UInt16[]</returns>
public object ToPLCObj(object value)
{
if (type == REG_TYPE.BOOL)
return value;
else
return RegTypeConverter.ToRegs(value, type, scale);
}
/// <summary>
/// PC 属性 转 PLC 对象(寄存器、线圈), 本地
/// </summary>
public void ToPLCObj()
{
object plcobj = ToPLCObj(value);
//更新 本地的 PLC 数据
switch (dataArea)
{
case PLCAddressArea.Coil:
plcValue = plcobj;
break;
case PLCAddressArea.Register:
{
UInt16[] src = plcobj as UInt16[];
UInt16[] dest = plcValue as UInt16[];
Array.Copy(src, dest, dest.Length);
}
break;
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return propertyName + " [" + ((int)dataArea).ToString() + "](" + addr + ")";
}
}
}
......@@ -46,14 +46,23 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AreaManager.cs" />
<Compile Include="CoilData.cs" />
<Compile Include="RegisterData.cs" />
<Compile Include="RegTypeConverter.cs" />
<Compile Include="ClientTCP.cs" />
<Compile Include="DataToRegs.cs" />
<Compile Include="ModbusMapper_Client.cs" />
<Compile Include="IModbusClient.cs" />
<Compile Include="IModbusRegister.cs" />
<Compile Include="ModbusMapper.cs" />
<Compile Include="Pack_Proto.cs" />
<Compile Include="PLCGroup.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WithThread\ClientTCP.cs" />
<Compile Include="WithThread\IModbusClient.cs" />
<Compile Include="WithThread\IModbusServer.cs" />
<Compile Include="WithThread\ModbusMapper_Client.cs" />
<Compile Include="WithThread\ServerTCP.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Project.FLY.FObjSys\FObjSys\FObjBase.csproj">
......@@ -70,14 +79,11 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Resource Include="FodyWeavers.xml" />
<Resource Include="FodyWeavers.xml">
<SubType>Designer</SubType>
</Resource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fody">
<Version>4.2.1</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="PropertyChanged.Fody">
<Version>2.6.1</Version>
</PackageReference>
......
......@@ -40,32 +40,21 @@ namespace FLY.Modbus
/// </summary>
public int UpdateInterval = 200;
TimeSpan actUpdateInterval;
/// <summary>
/// 实际更新间隔
/// </summary>
public TimeSpan ActUpdateInterval { get; protected set; }
//{
// get { return actUpdateInterval; }
// protected set {
// if (actUpdateInterval != value)
// {
// actUpdateInterval = value;
// NotifyPropertyChanged("ActUpdateInterval");
// }
// }
//}
private Stopwatch mStopwatch = new Stopwatch();
class RegWrite
{
public PLCAddressArea dataArea;
public int addr;
public DataToRegs dr;
public object val;
public RegWrite(PLCAddressArea dataArea, int addr, object val)
public RegWrite(DataToRegs dr, object val)
{
this.dataArea = dataArea;
this.addr = addr;
this.dr = dr;
this.val = val;
}
}
......@@ -101,23 +90,28 @@ namespace FLY.Modbus
{
if (!mclient.IsConnected)
{
coils.ClearPlanState();
registers.ClearPlanState();
foreach (var areaManager in mAreaManager)
areaManager.ClearPlanState();
//复位全部状态
isInCommunication = false;
p_doing = null;
isUpdating = false;
curr_area_idx = 0;
// DRMap 的数据 与 下端 属性的数据一致
foreach (RegWrite rw in rws)
{
//更新 本地的 PLC 数据
SetModbusData(rw.dataArea, rw.addr, rw.val);
}
rws.Clear();
//foreach (var areaManager in mAreaManager)
// areaManager.UpdatePLCObjFromProperty();
//rws.Clear();
mStopwatch.Stop();
}
else
{
foreach (var areaManager in mAreaManager)
areaManager.SetAllIsPlcValueChanged();
rws.Clear();
}
}
}
......@@ -137,6 +131,11 @@ namespace FLY.Modbus
isUpdating = true;
ActUpdateInterval = mStopwatch.Elapsed;
mStopwatch.Restart();
foreach (var areaManager in mAreaManager)
{
areaManager.MakePlanReal();
}
}
}
NextPlan();
......@@ -185,7 +184,7 @@ namespace FLY.Modbus
/// <param name="obj"></param>
void Do_01_callback(bool[] val, object obj)
{
ModbusMapper.Plan p = obj as ModbusMapper.Plan;
Plan p = obj as Plan;
p.isDoing = false;
SetModbusData(PLCAddressArea.Coil, p.addr, val);
......@@ -200,7 +199,7 @@ namespace FLY.Modbus
void Do_03_callback(UInt16[] val, object obj)
{
ModbusMapper.Plan p = obj as ModbusMapper.Plan;
Plan p = obj as Plan;
p.isDoing = false;
SetModbusData(PLCAddressArea.Register, p.addr, val);
isInCommunication = false;
......@@ -216,7 +215,6 @@ namespace FLY.Modbus
{
isInCommunication = false;
NextPlan();
}
/// <summary>
/// 正在通讯中
......@@ -229,7 +227,7 @@ namespace FLY.Modbus
/// <summary>
/// 读更新 的计划
/// </summary>
ModbusMapper.Plan p_doing = null;
Plan p_doing = null;
///// <summary>
/////
///// </summary>
......@@ -251,23 +249,15 @@ namespace FLY.Modbus
/// <param name="dr">数据映射</param>
/// <param name="val">数据值,不能为空</param>
/// <returns></returns>
public override bool SetNameData(DataToRegs dr, object val)
public override void SetNameData(DataToRegs dr, object val)
{
RegWrite rw;
switch (dr.dataArea)
{
case PLCAddressArea.Coil:
rw = rws.Find((c) => { return (c.addr == dr.addr) && (c.dataArea == PLCAddressArea.Coil); });
if (rw != null) rws.Remove(rw);
rws.Add(new RegWrite(dr.dataArea, dr.addr, dr.ToBools(val)));
return true;
case PLCAddressArea.Register:
rw = rws.Find((c) => { return (c.addr == dr.addr) && (c.dataArea == PLCAddressArea.Register); });
if (rw != null) rws.Remove(rw);
rws.Add(new RegWrite(dr.dataArea, dr.addr, dr.ToRegs(val)));
return true;
}
return false;
//rw = rws.Find((c) => { return (c.dr == dr); });
//if (rw != null)
// rws.Remove(rw);
rw = new RegWrite(dr, val);
rws.Add(rw);
}
/// <summary>
/// 更新写命令缓冲区
......@@ -275,23 +265,23 @@ namespace FLY.Modbus
void UpdateWriteData()
{
RegWrite rw = rws.First();
switch (rw.dataArea)
rws.Remove(rw);
//更新 本地的 PLC 数据
rw.dr.isPlcValueChanged = true;
switch (rw.dr.dataArea)
{
case PLCAddressArea.Coil:
if (mclient.Do_0F((UInt16)rw.addr, (bool[])(rw.val), Do_0F_callback, null) != ModbusClient_Errno.OK)
if (mclient.Do_0F((UInt16)rw.dr.addr, new bool[] { (bool)rw.dr.plcValue }, Do_0F_callback, null) != ModbusClient_Errno.OK)
return;
SetModbusData(rw.dataArea, rw.addr, rw.val);
break;
case PLCAddressArea.Register:
if (mclient.Do_10((UInt16)rw.addr, (UInt16[])rw.val, Do_10_callback, null) != ModbusClient_Errno.OK)
if (mclient.Do_10((UInt16)rw.dr.addr, (UInt16[])rw.dr.plcValue, Do_10_callback, null) != ModbusClient_Errno.OK)
return;
SetModbusData(rw.dataArea, rw.addr, rw.val);
break;
}
//更新 本地的 PLC 数据
SetModbusData(rw.dataArea, rw.addr , rw.val);
rws.Remove(rw);
isInCommunication = true;
}
void UpdateReadData()
......@@ -308,6 +298,8 @@ namespace FLY.Modbus
p_doing.isDoing = true;
isInCommunication = true;
}
}
}
......@@ -65,7 +65,7 @@ namespace FLY.Modbus
return buf.ToArray();
}
public bool TryParse(byte[] value, int startIndex, out int rlen)
public bool TryParse(byte[] value, int startIndex, int value_len, out int rlen)
{
//格式——————————————
//MBAP
......@@ -82,7 +82,7 @@ namespace FLY.Modbus
rlen = 0;
int cnt = 7 + 1;
if (value.Count() < cnt)
if (value_len < cnt)
return false;
int index = startIndex;
tranid = COMMON.ToUInt16_Big_endian(value, index);
......@@ -94,7 +94,7 @@ namespace FLY.Modbus
index += 2;
cnt += len - 2;
if (value.Count() < cnt)
if (value_len < cnt)
return false;
unitid = value[index];
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLY.Modbus
{
/// <summary>
/// PLC寄存器 与 C# 类型数据 转换, 不含bool
/// </summary>
public class RegTypeConverter
{
/// <summary>
/// PLC寄存器 转 C# 类型数据
/// </summary>
/// <param name="data">PLC 寄存器数据</param>
/// <param name="type">PLC 数据类型</param>
/// <param name="scale">PLC数据放大倍数</param>
/// <returns></returns>
public static object ToObject(ushort[] data, REG_TYPE type, double scale)
{
switch (type)
{
//case REG_TYPE.BOOL:
// {
// return data[0] != 0 ? true : false;
// }
case REG_TYPE.INT16:
{
float v = (Int16)data[0];
v = (float)(v * scale);
return v;
}
case REG_TYPE.UINT16:
{
float v = (UInt16)data[0];
v = (float)(v * scale);
return v;
}
case REG_TYPE.UINT32:
{
float v = (UInt32)(data[0] | (data[1] << 16));
v = (float)(v * scale);
return v;
}
case REG_TYPE.INT32:
{
float v = (Int32)(data[0] | (data[1] << 16));
v = (float)(v * scale);
return v;
}
case REG_TYPE.FLOAT:
{
byte[] dat = new byte[] { (byte)data[0], (byte)(data[0] >> 8), (byte)data[1], (byte)(data[1] >> 8) };
float v = BitConverter.ToSingle(dat, 0);
if ((v > 1000000) || (v < -1000000))
v = 0;
v = (float)(v * scale);
return v;
}
default:
{
throw new Exception("ToObject type=" + type + "不支持的类型");
}
}
}
/// <summary>
/// C# 类型数据 转 PLC寄存器
/// </summary>
/// <param name="value">C#中数值 只能是 float or bool</param>
/// <param name="type">PLC 数据类型</param>
/// <param name="scale">PLC数据放大倍数</param>
/// <returns></returns>
public static ushort[] ToRegs(object value, REG_TYPE type, double scale)
{
byte[] bs;
//if (type == REG_TYPE.BOOL)
//{
// bool v = (bool)value;
// if (v)
// {
// return new ushort[1] { 1 };
// }
// else
// {
// return new ushort[1] { 0 };
// }
//}
float f = (float)value;
f = (float)(f / scale);
switch (type)
{
case REG_TYPE.INT16:
{
short v = (short)f;
return new ushort[] { (ushort)v };
}
case REG_TYPE.UINT16:
{
ushort v = (ushort)f;
return new ushort[] { v };
}
case REG_TYPE.FLOAT:
{
float v = (float)f;
bs = BitConverter.GetBytes(v);
return new ushort[] {
(ushort)((bs[1] << 8) | bs[0]),
(ushort)((bs[3] << 8) | bs[2]) };
}
break;
case REG_TYPE.INT32:
{
Int32 v = (Int32)f;
bs = BitConverter.GetBytes(v);
return new ushort[] {
(ushort)((bs[1] << 8) | bs[0]),
(ushort)((bs[3] << 8) | bs[2]) };
}
case REG_TYPE.UINT32:
{
UInt32 v = (UInt32)f;
bs = BitConverter.GetBytes(v);
return new ushort[] {
(ushort)((bs[1] << 8) | bs[0]),
(ushort)((bs[3] << 8) | bs[2]) };
}
}
return null;
}
}
/// <summary>
/// C# 类型枚举
/// </summary>
public enum REG_TYPE
{
/// <summary>
/// 出错
/// </summary>
Unknown = 0,
/// <summary>
/// float
/// </summary>
FLOAT = 1,
/// <summary>
/// uint16
/// </summary>
UINT16 = 2,
/// <summary>
/// int16
/// </summary>
INT16 = 3,
/// <summary>
/// uint32
/// </summary>
UINT32 = 4,
/// <summary>
/// int32
/// </summary>
INT32 = 5,
/// <summary>
/// bool
/// </summary>
BOOL = 6,
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FLY.Modbus
{
/// <summary>
/// property 与 PLC寄存器 更新操作
/// </summary>
public class RegisterData : AreaManager
{
/// <summary>
///
/// </summary>
public RegisterData(PLCAddressArea area, int maxOfOneRead) : base(area, maxOfOneRead)
{
}
/// <summary>
/// 根据当前regs的配置指定读寄存器的计划
/// </summary>
public override void MakePlan()
{
if (regs == null)
{
throw new Exception("还没有执行 BuildArray()");
}
lock (plan_buffer)
{
plan_buffer.Clear();
int addr = -1;
int num = 0;
for (int i = 0; i < regs.Count(); i++)
{
if (!regs[i].isNeedUpdate)
continue;
if (addr == -1)
{
addr = regs[i].addr;
UInt16[] plc_value = (UInt16[])regs[i].plcValue;
num = plc_value.Count();
if (num > maxOfOneRead)
{
//一片区域已经大于 最大读取寄存器数,分拆
while (num > maxOfOneRead)
{
plan.Add(new Plan(area, addr, maxOfOneRead));
addr += maxOfOneRead;
num -= maxOfOneRead;
}
if (num > 0)
plan.Add(new Plan(area, addr, num));
addr = -1;
}
}
else
{
UInt16[] plc_value = (UInt16[])regs[i].plcValue;
int n = regs[i].addr + plc_value.Count() - 1 - addr + 1;
if (n <= maxOfOneRead)
{
num = n;
}
else
{
plan_buffer.Add(new Plan(area, addr, num));
addr = -1;
i--;
}
}
}
if (addr != -1)
{
plan_buffer.Add(new Plan(area, addr, num));
addr = -1;
}
isPlanChanged = true;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace FLY.Modbus.WithThread
{
/// <summary>
/// 同一时刻只能有一个通信,当同时执行 Do_xx, 这些指令会被hold住
/// 如果连接断开,或者异常,这些 Do_xx,全部返回 false
/// </summary>
public interface IModbusClient
{
/// <summary>
/// 通信超时
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// socket 错误信息
/// </summary>
string ErrMsg { get; set; }
/// <summary>
/// 连接成功
/// </summary>
bool IsConnected { get; set; }
/// <summary>
/// 远端地址
/// </summary>
IPEndPoint RemoteEP { get; set; }
/// <summary>
/// 建立连接
/// </summary>
bool Connect();
/// <summary>
/// 停止
/// </summary>
void Close();
/// <summary>
/// 读多个 COIL
/// </summary>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="values"></param>
/// <returns></returns>
bool Do_01(int addr, int cnt, out IEnumerable<bool> values);
/// <summary>
/// 读多个Holding REGs
/// </summary>
/// <param name="addr"></param>
/// <param name="cnt"></param>
/// <param name="values"></param>
/// <returns></returns>
bool Do_03(int addr, int cnt, out IEnumerable<UInt16> values);
/// <summary>
/// Write Single Coil
/// </summary>
/// <param name="addr"></param>
/// <param name="data"></param>
/// <returns></returns>
bool Do_05(int addr, bool data);
/// <summary>
/// 写多个coil
/// </summary>
/// <param name="addr"></param>
/// <param name="datas"></param>
/// <returns></returns>
bool Do_0F(int addr, IEnumerable<bool> datas);
/// <summary>
/// 写多个REG
/// </summary>
/// <param name="addr"></param>
/// <param name="datas"></param>
/// <returns></returns>
bool Do_10(int addr, IEnumerable<UInt16> datas);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace FLY.Modbus.WithThread
{
public interface IModbusServer
{
/// <summary>
/// 通信超时, client 长时间连接上,但不通讯,会被断开
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// 本地地址
/// </summary>
IPEndPoint LocalEP { get; set; }
/// <summary>
/// 当前连接数
/// </summary>
int ConnectedCnt { get; }
/// <summary>
/// 工作中
/// </summary>
bool IsRunning { get; set; }
/// <summary>
/// socket 错误信息
/// </summary>
string ErrMsg { get; set; }
GetValueHander GetValue { get; }
SetValueHander SetValue { get; }
void Start();
void Stop();
}
public delegate void GetValueHander(int addr, object values);
public delegate void SetValueHander(int addr, object values);
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FLY.Modbus.WithThread
{
/// <summary>
/// 使用线程版本
/// 支持自定义逻辑
/// </summary>
public class ModbusMapper_Client : ModbusMapper
{
/// <summary>
/// 更新周期,单位ms,
/// </summary>
public int UpdateInterval = 100;
/// <summary>
/// 实际更新间隔
/// </summary>
public TimeSpan ActUpdateInterval { get; private set; }
/// <summary>
/// 工作中
/// </summary>
public bool IsRunning { get; private set; }
/// <summary>
/// 异常次数
/// </summary>
public int ErrorCnt { get; private set; }
class RegWrite
{
public DataToRegs dr;
public object val;
public RegWrite(DataToRegs dr, object val)
{
this.dr = dr;
this.val = val;
}
}
List<RegWrite> rws = new List<RegWrite>();
/// <summary>
/// tcp client
/// </summary>
public ClientTCP Client { get; set; }
CancellationTokenSource cts;
/// <summary>
///
/// </summary>
/// <param name="ep">服务器ep</param>
public ModbusMapper_Client(IPEndPoint ep)
{
Client = new ClientTCP(ep);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Client.ToString();
}
/// <summary>
/// 启动
/// </summary>
public void Start()
{
lock (this)
{
if (IsRunning)
return;
IsRunning = true;
cts = new CancellationTokenSource();
Task.Factory.StartNew(OnPoll_update, cts.Token);
}
}
/// <summary>
/// 停止
/// </summary>
public void Stop()
{
lock (this)
{
if (!IsRunning)
return;
cts.Cancel();
Client.Close();
}
}
void OnPoll_update()
{
Stopwatch stopwatch = new Stopwatch();
bool isTimeForRead = true;
_connect:
while (true)
{
if (cts.IsCancellationRequested)
goto _end;
bool isconnected = Client.Connect();
if (cts.IsCancellationRequested)
goto _end;
if (isconnected)
break;
else
Thread.Sleep(1000);
}
//刚连接成功。
//从本地PC 属性获取value 更新到 本地PLC 寄存器,
//目的: 当从PLC读取寄存器值时,能比较不同,产生变化事件
foreach (var areaManager in mAreaManager)
areaManager.SetAllIsPlcValueChanged();
//清空 写缓存
lock (rws)
{
rws.Clear();
}
stopwatch.Restart();
isTimeForRead = true;
while (true)
{
if (cts.IsCancellationRequested)
goto _end;
if (stopwatch.ElapsedMilliseconds >= UpdateInterval)
{
isTimeForRead = true;
}
if (isTimeForRead)
{
isTimeForRead = false;
TimeSpan elapsed = stopwatch.Elapsed;
ActUpdateInterval = elapsed;
stopwatch.Restart();
if (!UpdateReadData())
{
if (cts.IsCancellationRequested)//可能是外部强制关闭
goto _end;
goto _error;//连接断开,终止更新线程
}
}
//输出写入数据
if (!UpdateWriteData())
{
if (cts.IsCancellationRequested)//可能是外部强制关闭
goto _end;
goto _error;//连接断开,终止更新线程
}
Thread.Sleep(30);
}
_error:
ErrorCnt++;
goto _connect;
_end:
Client.Close();
IsRunning = false;
}
/// <summary>
/// 更新一次读全部数据
/// </summary>
/// <returns></returns>
bool UpdateReadData()
{
foreach (var areaManager in mAreaManager)
{
areaManager.MakePlanReal();
}
//int curr_area_idx = 0;
foreach (var areaManager in mAreaManager)
{
foreach(Plan p_doing in areaManager.plan)
{
switch (p_doing.area)
{
case PLCAddressArea.Coil:
{
if (!Client.Do_01(p_doing.addr, p_doing.num, out IEnumerable<bool> values))
return false;
SetModbusData(PLCAddressArea.Coil, p_doing.addr, values);
}
break;
case PLCAddressArea.Register:
{
if (!Client.Do_03(p_doing.addr, p_doing.num, out IEnumerable<UInt16> values))
return false;
SetModbusData(PLCAddressArea.Register, p_doing.addr, values);
}
break;
}
}
}
return true;
}
/// <summary>
/// PC -> PLC
/// 为命名数据赋值,会改变Modbus的寄存器值,并产生通知
/// </summary>
/// <param name="dr">数据映射</param>
/// <param name="val">数据值,不能为空</param>
/// <returns></returns>
public override void SetNameData(DataToRegs dr, object val)
{
RegWrite rw = new RegWrite(dr, val);
lock (rws)
{
rws.Add(rw);
}
}
/// <summary>
/// 提取全部写命令缓冲区 输出
/// </summary>
bool UpdateWriteData()
{
while (true)
{
RegWrite rw;
lock (rws)
{
if (rws.Count() == 0)
break;
rw = rws.First();
rws.RemoveAt(0);
}
//强制令读时,推送数据
rw.dr.isPlcValueChanged = true;
object plc_value = rw.dr.ToPLCObj(rw.val);
switch (rw.dr.dataArea)
{
case PLCAddressArea.Coil:
if (!Client.Do_0F((UInt16)rw.dr.addr, new bool[] {(bool)plc_value }))
return false;
break;
default:
//case PLCAddressArea.Register:
if (!Client.Do_10((UInt16)rw.dr.addr, (UInt16[])plc_value))
return false;
break;
}
}
return true;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment