1
0
mirror of https://github.com/zhaopeiym/IoTClient synced 2025-10-26 22:15:44 +08:00
IoTClient/IoTClient.Extensions.Adapter/Communication/BACnetCommunication.cs

530 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using IoTClient.Enums;
using IoTClient.Extensions.Adapter.Communication.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO.BACnet;
using System.Linq;
using System.Net;
using System.Threading;
namespace IoTClient.Extensions.Adapter
{
/// <summary>
/// BACnet
/// </summary>
public class BACnetCommunication : IIoTClientCommon
{
private IPEndPoint IpEndPoint;
public bool IsConnected => true;
private string deviceId;
public string ConnectionInfo => $"{IpEndPoint}:{deviceId}";
public string DeviceVersion => "BACnet";
private List<BacNode> devicesList = new List<BacNode>();
private BacnetClient client;
public BACnetCommunication(string ip, string deviceId, int port = 47808)
{
this.deviceId = deviceId;
IpEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
}
public Result Close()
{
try
{
client?.Dispose();
}
catch { }
return new Result();
}
public Result Open()
{
lock (devicesList)
{
//Logger?.Debug($"Open-1 IP:{IpEndPoint.Address.ToString()} {IpEndPoint.Port.ToString()}", tag: "BACnetCommunication_Debug");
if (client == null || devicesList == null || !devicesList.Any())
{
//Logger?.Debug($"Open-2 IP:{IpEndPoint.Address.ToString()} {IpEndPoint.Port.ToString()}", tag: "BACnetCommunication_Debug");
try
{
client?.Dispose();
}
catch { }
//BACnet的默认端口47808 //, localEndpointIp: "192.168.10.4"
client = new BacnetClient(new BacnetIpUdpProtocolTransport(IpEndPoint.Port, false));
client.OnIam -= new BacnetClient.IamHandler(handler_OnIam);
client.OnIam += new BacnetClient.IamHandler(handler_OnIam);
client.Start();
client.WhoIs();
}
}
return new Result();
}
#region private
private void handler_OnIam(BacnetClient sender, BacnetAddress address, uint deviceId, uint maxAPDU, BacnetSegmentations segmentation, ushort vendorId)
{
lock (devicesList)
{
var adr = address.adr;
//Logger?.Debug($"adr:{address.ToString()} IP:{IpEndPoint.Address.ToString()} {IpEndPoint.Port.ToString()}", tag: "BACnetCommunication_Debug");
foreach (BacNode bn in devicesList)
{
if (bn.GetAdd(deviceId) != null)
{
//Logger?.Debug($"adr:{address.ToString()} IP:{IpEndPoint.Address.ToString()} {IpEndPoint.Port.ToString()} deviceId:{devicesList}", tag: "BACnetCommunication_Debug");
return; // Yes
}
}
if (IpEndPoint.Address.ToString() == $"{adr[0]}.{adr[1]}.{adr[2]}.{adr[3]}")
{
devicesList.Add(new BacNode(address, deviceId)); // add it
//Logger?.Debug("开始扫描Scan()", tag: "BACnetCommunication_Debug");
Scan();//开始扫描
}
}
}
/// <summary>
/// 扫描
/// </summary>
private void Scan()
{
foreach (var device in devicesList)
{
//获取子节点个数
var deviceCount = GetDeviceArrayIndexCount(device) + 1;
//扫描子节点(50可设置 配置 - 批量扫描)
ScanPointsBatch(device, 50, deviceCount);
}
}
//获取子节点个数
private uint GetDeviceArrayIndexCount(BacNode device)
{
try
{
if (device.Address == null) return 0;
var objectId = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, device.DeviceId);
var list = ReadScalarValue(device.Address, objectId, BacnetPropertyIds.PROP_OBJECT_LIST, 0);
var rst = Convert.ToUInt32(list.Value);
return rst;
}
catch (Exception ex)
{
//Logger?.Error(ex, ex.Message + "-2", tag: "BACnetCommunication_Debug");
}
return 0;
}
private BacnetValue ReadScalarValue(BacnetAddress address, BacnetObjectId oid, BacnetPropertyIds propertyId, uint arrayIndex)
{
try
{
return client.ReadPropertyRequest(address, oid, propertyId, arrayIndex);
}
catch (Exception ex)
{
//Logger?.Error(ex, ex.Message + "-1", tag: "BACnetCommunication_Debug");
}
return new BacnetValue();
}
/// <summary>
/// 扫描设备
/// </summary>
private void ScanPointsBatch(BacNode device, uint partDeviceCount, uint deviceCount)
{
try
{
if (device == null) return;
var pid = BacnetPropertyIds.PROP_OBJECT_LIST;
var device_id = device.DeviceId;
var bobj = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, device_id);
var adr = device.Address;
if (adr == null) return;
device.Properties.Clear();
List<BacnetPropertyReference> rList = new List<BacnetPropertyReference>();
for (uint i = 0; i < deviceCount; i++)
{
rList.Add(new BacnetPropertyReference((uint)pid, i));
if ((i != 0 && i % partDeviceCount == 0) || i == deviceCount - 1)//不要超了 MaxAPDU
{
IList<BacnetReadAccessResult> lstAccessRst = client.ReadPropertyMultipleRequest(adr, bobj, rList);
if (lstAccessRst?.Any() ?? false)
{
foreach (var aRst in lstAccessRst)
{
if (aRst.values == null) continue;
foreach (var bPValue in aRst.values)
{
if (bPValue.value == null) continue;
foreach (var bValue in bPValue.value)
{
var strBValue = "" + bValue.Value;
var strs = strBValue.Split(':');
if (strs.Length < 2) continue;
var strType = strs[0];
var strObjId = strs[1];
var subNode = new BacProperty();
Enum.TryParse(strType, out BacnetObjectTypes otype);
if (otype == BacnetObjectTypes.OBJECT_NOTIFICATION_CLASS || otype == BacnetObjectTypes.OBJECT_DEVICE) continue;
subNode.ObjectId = new BacnetObjectId(otype, Convert.ToUInt32(strObjId));
device.Properties.Add(subNode); //添加属性
}
}
}
}
rList.Clear();//清空
}
}
}
catch (Exception ex)
{
//Logger?.Error(ex, ex.Message + "-3", tag: "BACnetCommunication_Debug");
}
}
#endregion
public Result<object> Read(string address)
{
var instance = int.Parse(address.Split('_')[0]);
var objectType = int.Parse(address.Split('_')[1]);
var result = new Result<object>();
var rpop = devicesList.SelectMany(t => t.Properties).Where(t => t.ObjectId.Instance == instance && t.ObjectId.Type == (BacnetObjectTypes)objectType).FirstOrDefault();
if (rpop == null)
{
result.Err = $"没有找到对应的点:{address},devicesList:{JsonConvert.SerializeObject(devicesList)},adr:{devicesList.FirstOrDefault()?.Address.ToString()}";
result.IsSucceed = false;
return result;
}
var bacnet = devicesList.Where(t => t.DeviceId.ToString() == deviceId && t.Properties.Any(p => p.ObjectId.Instance == instance && p.ObjectId.Type == (BacnetObjectTypes)objectType)).FirstOrDefault();
int retry = 0;//重试
tag_retry:
try
{
Thread.Sleep(retry * 200);
IList<BacnetValue> NoScalarValue = client.ReadPropertyRequest(bacnet.Address, rpop.ObjectId, BacnetPropertyIds.PROP_PRESENT_VALUE);
if (NoScalarValue?.Any() ?? false)
{
result.Value = NoScalarValue[0].Value;
}
else
{
retry++;
//Logger?.Warning($"读取:{address}重试次数[{retry}]", tag: "BACnetCommunication_Debug");
if (retry < 4) goto tag_retry;
result.Err = $"读取失败:{address}";
result.IsSucceed = false;
}
}
catch (Exception ex)
{
retry++;
//Logger?.Warning($"读取:{address}重试次数[{retry}]", tag: "BACnetCommunication_Debug");
if (retry < 4) goto tag_retry;
result.Err = $"读取失败:{address},{ex.Message}";
result.IsSucceed = false;
}
return result;
}
/// <summary>
/// 批量读取
/// </summary>
/// <param name="addresses"></param>
/// <param name="batchNumber">经过测试9是最大批量数可根据实际情况进行调整</param>
/// <returns></returns>
public Result<Dictionary<string, object>> BatchRead(Dictionary<string, DataTypeEnum> addresses, int batchNumber)
{
var reuslt = new Result<Dictionary<string, object>>();
reuslt.Value = new Dictionary<string, object>();
var batchCount = Math.Ceiling((float)addresses.Count / batchNumber);
//Logger?.Debug($"batchCount:{batchCount}", tag: "temp");
List<BacnetPropertyReference> rList = new List<BacnetPropertyReference>();
rList.Add(new BacnetPropertyReference((uint)BacnetPropertyIds.PROP_PRESENT_VALUE, uint.MaxValue));
BacnetAddress address = devicesList.FirstOrDefault()?.Address;
List<BacnetReadAccessResult> lstAccessRst = new List<BacnetReadAccessResult>();
for (int i = 0; i < batchCount; i++)
{
IList<BacnetReadAccessSpecification> properties = addresses.Skip(i * batchNumber).Take(batchNumber)
.Select(t =>
{
var instance = uint.Parse(t.Key.Split('_')[0]);
var objectType = int.Parse(t.Key.Split('_')[1]);
return new BacnetReadAccessSpecification(new BacnetObjectId((BacnetObjectTypes)objectType, instance), rList);
}).ToList();
//批量读取
lstAccessRst.AddRange(client.ReadPropertyMultipleRequest(address, properties));
}
foreach (var aRst in lstAccessRst)
{
if (aRst.values == null) continue;
object PROP_PRESENT_VALUE = null;
foreach (var bPValue in aRst.values)
{
if (bPValue.value == null || bPValue.value.Count == 0) continue;
var pid = (BacnetPropertyIds)(bPValue.property.propertyIdentifier);
var bValue = bPValue.value.First();
switch (pid)
{
case BacnetPropertyIds.PROP_PRESENT_VALUE://值
{
PROP_PRESENT_VALUE = bValue.Value;
}
break;
}
}
if (!string.IsNullOrWhiteSpace(PROP_PRESENT_VALUE?.ToString()))
reuslt.Value.Add($"{aRst.objectIdentifier.Instance}_{(int)aRst.objectIdentifier.Type}", PROP_PRESENT_VALUE);
}
return reuslt;
}
public Result<bool> ReadBoolean(string address)
{
var result = Read(address);
return new Result<bool>(result, Convert.ToBoolean(result.Value));
}
public Result<byte> ReadByte(string address)
{
var result = Read(address);
return new Result<byte>(result, Convert.ToByte(result.Value));
}
public Result<ushort> ReadUInt16(string address)
{
var result = Read(address);
return new Result<ushort>(result, Convert.ToUInt16(result.Value));
}
public Result<short> ReadInt16(string address)
{
var result = Read(address);
return new Result<short>(result, Convert.ToInt16(result.Value));
}
public Result<uint> ReadUInt32(string address)
{
var result = Read(address);
return new Result<uint>(result, Convert.ToUInt32(result.Value));
}
public Result<int> ReadInt32(string address)
{
var result = Read(address);
return new Result<int>(result, Convert.ToInt32(result.Value));
}
public Result<float> ReadFloat(string address)
{
var result = Read(address);
return new Result<float>(result, Convert.ToSingle(result.Value));
}
public Result<ulong> ReadUInt64(string address)
{
var result = Read(address);
return new Result<ulong>(result, Convert.ToUInt64(result.Value));
}
public Result<long> ReadInt64(string address)
{
var result = Read(address);
return new Result<long>(result, Convert.ToInt64(result.Value));
}
public Result<double> ReadDouble(string address)
{
var result = Read(address);
return new Result<double>(result, Convert.ToDouble(result.Value));
}
public Result<bool> WriteValue(string address, object value)
{
var result = new Result<bool>();
var instance = int.Parse(address.Split('_')[0]?.Trim());
var objectType = int.Parse(address.Split('_')[1]?.Trim());
var rpop = devicesList.SelectMany(t => t.Properties).Where(t => t.ObjectId.Instance == instance && t.ObjectId.Type == (BacnetObjectTypes)objectType).FirstOrDefault();
if (rpop == null)
{
result.Err = $"没有找到对应的点:{address}";
result.IsSucceed = false;
return result;
}
var bacnet = devicesList.Where(t => t.DeviceId.ToString() == deviceId && t.Properties.Any(p => p.ObjectId.Instance == instance && p.ObjectId.Type == (BacnetObjectTypes)objectType)).FirstOrDefault();
BacnetValue[] NoScalarValue = { new BacnetValue(value) };
int retry = 0;//重试
tag_retry:
try
{
Thread.Sleep(retry * 200);
client.WritePropertyRequest(bacnet.Address, rpop.ObjectId, BacnetPropertyIds.PROP_PRESENT_VALUE, NoScalarValue);
}
catch (Exception ex)
{
retry++;
if (retry < 4) goto tag_retry;//强行重试
result.IsSucceed = false;
result.Err = $"写入失败:{address},{ex.Message},Type:{value.GetType()}";
}
return result;
}
public Result Write(string address, int value)
{
return WriteValue(address, value);
}
public Result Write(string address, uint value)
{
return WriteValue(address, value);
}
public Result Write(string address, short value)
{
return WriteValue(address, value);
}
public Result Write(string address, ushort value)
{
return WriteValue(address, value);
}
public Result Write(string address, float value)
{
return WriteValue(address, value);
}
public Result Write(string address, bool value)
{
return WriteValue(address, value);
}
public Result Write(string address, byte value)
{
return WriteValue(address, value);
}
public Result BatchWrite(Dictionary<string, object> addresses, int batchNumber)
{
var result = new Result();
foreach (var address in addresses)
{
DataTypeEnum dataType = DataTypeEnum.None;
switch (address.Value.GetType().Name)
{
case "Boolean":
dataType = DataTypeEnum.Bool; break;
case "UInt16":
dataType = DataTypeEnum.UInt16; break;
case "Int16":
dataType = DataTypeEnum.Int16; break;
case "UInt32":
dataType = DataTypeEnum.UInt32; break;
case "Int32":
dataType = DataTypeEnum.Int32; break;
case "UInt64":
dataType = DataTypeEnum.UInt64; break;
case "Int64":
dataType = DataTypeEnum.Int64; break;
case "Single":
dataType = DataTypeEnum.Float; break;
case "Double":
dataType = DataTypeEnum.Double; break;
default:
throw new Exception($"暂未提供对{address.Value.GetType().Name}类型的写入操作。");
}
var tempResult = Write(address.Key, address.Value, dataType);
if (!tempResult.IsSucceed)
{
result.SetErrInfo(tempResult);
}
result.Requst = tempResult.Requst;
result.Response = tempResult.Response;
}
return result;
}
public Result Write(string address, sbyte value)
{
return WriteValue(address, value);
}
public Result Write(string address, ulong value)
{
return WriteValue(address, value);
}
public Result Write(string address, long value)
{
return WriteValue(address, value);
}
public Result Write(string address, double value)
{
return WriteValue(address, value);
}
public Result Write(string address, object value, DataTypeEnum dataType)
{
var result = new Result() { IsSucceed = false };
switch (dataType)
{
case DataTypeEnum.Bool:
result = Write(address, Convert.ToBoolean(value));
break;
case DataTypeEnum.Byte:
result = Write(address, Convert.ToByte(value));
break;
case DataTypeEnum.Int16:
result = Write(address, Convert.ToInt16(value));
break;
case DataTypeEnum.UInt16:
result = Write(address, Convert.ToUInt16(value));
break;
case DataTypeEnum.Int32:
result = Write(address, Convert.ToInt32(value));
break;
case DataTypeEnum.UInt32:
result = Write(address, Convert.ToUInt32(value));
break;
case DataTypeEnum.Int64:
result = Write(address, Convert.ToInt64(value));
break;
case DataTypeEnum.UInt64:
result = Write(address, Convert.ToUInt64(value));
break;
case DataTypeEnum.Float:
result = Write(address, Convert.ToSingle(value));
break;
case DataTypeEnum.Double:
result = Write(address, Convert.ToDouble(value));
break;
}
return result;
}
}
}