Skip to content

Commit

Permalink
Bug fixes for Bacnet.
Browse files Browse the repository at this point in the history
  • Loading branch information
barnstee committed Nov 26, 2024
1 parent 33b6b3d commit f9df73b
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 84 deletions.
62 changes: 33 additions & 29 deletions BACNetClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Opc.Ua.Edge.Translator
using System;
using System.Collections.Generic;
using System.IO.BACnet;
using System.Linq;
using System.Threading.Tasks;

public class BACNetClient : IAsset
Expand All @@ -23,7 +24,7 @@ public void Connect(string ipAddress, int port)
_client = new BacnetClient(transport);
_client.OnIam += OnIAm;
_client.Start();
_client.WhoIs();
_client.WhoIs(-1, -1, new BacnetAddress(BacnetAddressTypes.IP, _endpoint));

Log.Logger.Information("Connected to BACNet device at " + ipAddress);
}
Expand All @@ -37,37 +38,21 @@ private void OnIAm(BacnetClient sender, BacnetAddress adr, uint deviceid, uint m
{
Log.Logger.Information($"Detected device {deviceid} at {adr}");

IList<BacnetValue> value_list;
sender.ReadPropertyRequest(adr, new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, deviceid), BacnetPropertyIds.PROP_OBJECT_LIST, out value_list);
BacnetObjectId deviceObjId = new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, deviceid);
sender.ReadPropertyRequest(adr, deviceObjId, BacnetPropertyIds.PROP_OBJECT_LIST, out IList<BacnetValue> value_list, arrayIndex: 0);

LinkedList<BacnetObjectId> object_list = new LinkedList<BacnetObjectId>();
foreach (BacnetValue value in value_list)
var objectCount = value_list.First().As<uint>();
for (uint i = 1; i <= objectCount; i++)
{
if (Enum.IsDefined(typeof(BacnetObjectTypes), ((BacnetObjectId)value.Value).Type))
{
object_list.AddLast((BacnetObjectId)value.Value);
}
}
sender.ReadPropertyRequest(adr, deviceObjId, BacnetPropertyIds.PROP_OBJECT_LIST, out value_list, arrayIndex: i);
Log.Logger.Information("Object " + value_list[0].Tag + ": " + value_list[0].Value);

foreach (BacnetObjectId object_id in object_list)
{
//read all properties
IList<BacnetValue> values = null;
try
{
if (!sender.ReadPropertyRequest(adr, object_id, BacnetPropertyIds.PROP_PRESENT_VALUE, out values))
{
Log.Logger.Error("Couldn't fetch 'present value' for object: " + object_id.ToString());
continue;
}
}
catch (Exception ex)
{
Log.Logger.Error("Couldn't fetch 'present value' for object: " + object_id.ToString() + ": " + ex.Message);
continue;
}

Log.Logger.Information("Object Name: " + object_id.ToString() + ", Property Id: " + values[0].Tag.ToString() + ", Value: " + values[0].Value.ToString());
BacnetValue Value;
ReadScalarValue(sender, adr, deviceObjId.instance, value_list[0].As<BacnetObjectId>(), BacnetPropertyIds.PROP_OBJECT_NAME, out Value);
Log.Logger.Information("Name: " + Value.Value.ToString());

ReadScalarValue(sender, adr, deviceObjId.instance, value_list[0].As<BacnetObjectId>(), BacnetPropertyIds.PROP_PRESENT_VALUE, out Value);
Log.Logger.Information("Value: " + Value.Value.ToString());
}
}

Expand All @@ -92,5 +77,24 @@ public Task Write(string addressWithinAsset, byte unitID, string function, byte[
// TODO
return Task.CompletedTask;
}

bool ReadScalarValue(BacnetClient sender, BacnetAddress adr, uint device_id, BacnetObjectId BacnetObject, BacnetPropertyIds Proprerty, out BacnetValue Value)
{
Value = new BacnetValue(null);

if (!sender.ReadPropertyRequest(adr, BacnetObject, Proprerty, out IList<BacnetValue> NoScalarValue))
{
return false;
}

Value = NoScalarValue[0];
return true;
}

bool WriteScalarValue(BacnetClient sender, BacnetAddress adr, uint device_id, BacnetObjectId BacnetObject, BacnetPropertyIds Proprerty, BacnetValue Value)
{
BacnetValue[] NoScalarValue = { Value };
return sender.WritePropertyRequest(adr, BacnetObject, Proprerty, NoScalarValue);
}
}
}
14 changes: 11 additions & 3 deletions Samples/BACNet.tm.jsonld → Samples/BACNet.td.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"scheme": "nosec"
}
},
"base": "bacnet://192.168.120.236",
"properties": {
"temperature_setpoint": {
"@type": "brick:Cooling_Zone_Air_Temperature_Setpoint",
Expand All @@ -41,7 +42,9 @@
"@type": "bacv:Real"
},
"bacv:usesService": "ReadProperty",
"op": "readproperty",
"op": [
"readproperty"
],
"href": "bacnet://1005/1,85"
}
]
Expand All @@ -57,14 +60,19 @@
"@type": "bacv:Real"
},
"bacv:usesService": "ReadProperty",
"op": "readproperty",
"op": [
"readproperty"
],
"href": "bacnet://5/0,85"
}
]
}
},
"security": "nosec_sc",
"security": [
"nosec_sc"
],
"title": "Thermostat in plugfest room",
"name": "Thermostat in plugfest room",
"registration": {
"created": "2024-11-25T10:42:34.155016+00:00",
"modified": "2024-11-25T10:42:34.155016+00:00",
Expand Down
4 changes: 0 additions & 4 deletions UAEdgeTranslator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,4 @@
<PackageReference Include="Viscon.Communication.Ads" Version="0.2.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="settings\" />
</ItemGroup>

</Project>
102 changes: 54 additions & 48 deletions UANodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,41 +214,44 @@ private void AddNamespacesFromCompanionSpecs(List<string> namespaceUris, ThingDe
}

OpcUaNamespaces namespaces = JsonConvert.DeserializeObject<OpcUaNamespaces>(ns.ToString());
foreach (Uri opcuaCompanionSpecUrl in namespaces.Namespaces)
if (namespaces.Namespaces != null)
{
// support local Nodesets
if (!opcuaCompanionSpecUrl.IsAbsoluteUri || (!opcuaCompanionSpecUrl.AbsoluteUri.Contains("http://") && !opcuaCompanionSpecUrl.AbsoluteUri.Contains("https://")))
foreach (Uri opcuaCompanionSpecUrl in namespaces.Namespaces)
{
string nodesetFile = string.Empty;
if (Path.IsPathFullyQualified(opcuaCompanionSpecUrl.OriginalString))
// support local Nodesets
if (!opcuaCompanionSpecUrl.IsAbsoluteUri || (!opcuaCompanionSpecUrl.AbsoluteUri.Contains("http://") && !opcuaCompanionSpecUrl.AbsoluteUri.Contains("https://")))
{
// absolute file path
nodesetFile = opcuaCompanionSpecUrl.OriginalString;
string nodesetFile = string.Empty;
if (Path.IsPathFullyQualified(opcuaCompanionSpecUrl.OriginalString))
{
// absolute file path
nodesetFile = opcuaCompanionSpecUrl.OriginalString;
}
else
{
// relative file path
nodesetFile = Path.Combine(Directory.GetCurrentDirectory(), opcuaCompanionSpecUrl.OriginalString);
}

Log.Logger.Information("Loading nodeset from local file: " + nodesetFile);
LoadNamespaceUrisFromNodesetXml(namespaceUris, nodesetFile);
}
else
{
// relative file path
nodesetFile = Path.Combine(Directory.GetCurrentDirectory(), opcuaCompanionSpecUrl.OriginalString);
}

Log.Logger.Information("Loading nodeset from local file: " + nodesetFile);
LoadNamespaceUrisFromNodesetXml(namespaceUris, nodesetFile);
}
else
{
if (_uacloudLibraryClient.DownloadNamespace(Environment.GetEnvironmentVariable("UACLURL"), opcuaCompanionSpecUrl.OriginalString))
{
Log.Logger.Information("Loaded nodeset from Cloud Library URL: " + opcuaCompanionSpecUrl);
if (_uacloudLibraryClient.DownloadNamespace(Environment.GetEnvironmentVariable("UACLURL"), opcuaCompanionSpecUrl.OriginalString))
{
Log.Logger.Information("Loaded nodeset from Cloud Library URL: " + opcuaCompanionSpecUrl);

foreach (string nodesetFile in _uacloudLibraryClient._nodeSetFilenames)
foreach (string nodesetFile in _uacloudLibraryClient._nodeSetFilenames)
{
LoadNamespaceUrisFromNodesetXml(namespaceUris, nodesetFile);
}
}
else
{
LoadNamespaceUrisFromNodesetXml(namespaceUris, nodesetFile);
Log.Logger.Warning($"Could not load nodeset {opcuaCompanionSpecUrl.OriginalString}");
}
}
else
{
Log.Logger.Warning($"Could not load nodeset {opcuaCompanionSpecUrl.OriginalString}");
}
}
}

Expand Down Expand Up @@ -279,25 +282,28 @@ private void AddNodesFromCompanionSpecs(ThingDescription td)
}

OpcUaNamespaces namespaces = JsonConvert.DeserializeObject<OpcUaNamespaces>(ns.ToString());
foreach (Uri opcuaCompanionSpecUrl in namespaces.Namespaces)
if (namespaces.Namespaces != null)
{
// support local Nodesets
if (!opcuaCompanionSpecUrl.IsAbsoluteUri || (!opcuaCompanionSpecUrl.AbsoluteUri.Contains("http://") && !opcuaCompanionSpecUrl.AbsoluteUri.Contains("https://")))
foreach (Uri opcuaCompanionSpecUrl in namespaces.Namespaces)
{
string nodesetFile = string.Empty;
if (Path.IsPathFullyQualified(opcuaCompanionSpecUrl.OriginalString))
{
// absolute file path
nodesetFile = opcuaCompanionSpecUrl.OriginalString;
}
else
// support local Nodesets
if (!opcuaCompanionSpecUrl.IsAbsoluteUri || (!opcuaCompanionSpecUrl.AbsoluteUri.Contains("http://") && !opcuaCompanionSpecUrl.AbsoluteUri.Contains("https://")))
{
// relative file path
nodesetFile = Path.Combine(Directory.GetCurrentDirectory(), opcuaCompanionSpecUrl.OriginalString);
}
string nodesetFile = string.Empty;
if (Path.IsPathFullyQualified(opcuaCompanionSpecUrl.OriginalString))
{
// absolute file path
nodesetFile = opcuaCompanionSpecUrl.OriginalString;
}
else
{
// relative file path
nodesetFile = Path.Combine(Directory.GetCurrentDirectory(), opcuaCompanionSpecUrl.OriginalString);
}

Log.Logger.Information("Adding node set from local nodeset file");
AddNodesFromNodesetXml(nodesetFile);
Log.Logger.Information("Adding node set from local nodeset file");
AddNodesFromNodesetXml(nodesetFile);
}
}
}
}
Expand Down Expand Up @@ -753,14 +759,14 @@ private void AddTag(ThingDescription td, object form, string assetId, byte unitI
if (td.Base.ToLower().StartsWith("bacnet://"))
{
// create an asset tag and add to our list
GenericForm adsForm = JsonConvert.DeserializeObject<GenericForm>(form.ToString());
GenericForm bacnetForm = JsonConvert.DeserializeObject<GenericForm>(form.ToString());
AssetTag tag = new()
{
Name = variableId,
Address = adsForm.Href,
Address = bacnetForm.Href,
UnitID = unitId,
Type = adsForm.Type.ToString(),
PollingInterval = (int)adsForm.PollingTime,
Type = bacnetForm.Type.ToString(),
PollingInterval = 1000,
Entity = null,
MappedUAExpandedNodeID = NodeId.ToExpandedNodeId(_uaVariables[variableId].NodeId, Server.NamespaceUris).ToString(),
MappedUAFieldPath = fieldPath
Expand Down Expand Up @@ -869,14 +875,14 @@ private void AssetConnectionTest(ThingDescription td, out byte unitId)
if (td.Base.ToLower().StartsWith("bacnet://"))
{
string[] address = td.Base.Split(new char[] { ':', '/' });
if ((address.Length != 6) || (address[0] != "bacnet"))
if ((address.Length != 4) || (address[0] != "bacnet"))
{
throw new Exception("Expected BACNet device address in the format bacnet://ipaddress:port!");
throw new Exception("Expected BACNet device address in the format bacnet://ipaddress!");
}

// check if we can reach the BACNet asset
BeckhoffClient client = new();
client.Connect(address[3] + ":" + address[4], int.Parse(address[5]));
BACNetClient client = new();
client.Connect(address[3], 0);

assetInterface = client;
}
Expand Down

0 comments on commit f9df73b

Please sign in to comment.