Initial commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using RobotNet10.Script.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace RobotNet10.ScriptEngine.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of CC-Link IE connection.
|
||||
/// Note: This is a basic implementation. Full CC-Link IE support may require additional libraries.
|
||||
/// </summary>
|
||||
public class CcLinkIeConnection : ICcLinkIeConnection
|
||||
{
|
||||
private TcpClient? _tcpClient;
|
||||
private NetworkStream? _stream;
|
||||
private bool _disposed;
|
||||
|
||||
public string IpAddress { get; }
|
||||
public int StationNumber { get; }
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public CcLinkIeConnection(string ipAddress, int stationNumber = 1)
|
||||
{
|
||||
IpAddress = ipAddress;
|
||||
StationNumber = stationNumber;
|
||||
IsConnected = false;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(IpAddress, 5007); // Standard CC-Link IE port
|
||||
_stream = _tcpClient.GetStream();
|
||||
IsConnected = true;
|
||||
|
||||
// TODO: Implement CC-Link IE handshake protocol
|
||||
// This requires implementing the CC-Link IE protocol stack
|
||||
}
|
||||
catch
|
||||
{
|
||||
Disconnect();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
Disconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
IsConnected = false;
|
||||
_stream?.Close();
|
||||
_stream = null;
|
||||
_tcpClient?.Close();
|
||||
_tcpClient?.Dispose();
|
||||
_tcpClient = null;
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadAsync(int address, int length)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
// TODO: Implement CC-Link IE read operation
|
||||
// This requires implementing the CC-Link IE protocol
|
||||
await Task.CompletedTask;
|
||||
throw new NotImplementedException("CC-Link IE read operation is not yet fully implemented. Full CC-Link IE support requires additional protocol implementation.");
|
||||
}
|
||||
|
||||
public async Task WriteAsync(int address, ushort[] data)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
// TODO: Implement CC-Link IE write operation
|
||||
// This requires implementing the CC-Link IE protocol
|
||||
await Task.CompletedTask;
|
||||
throw new NotImplementedException("CC-Link IE write operation is not yet fully implemented. Full CC-Link IE support requires additional protocol implementation.");
|
||||
}
|
||||
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (!IsConnected || _stream == null)
|
||||
{
|
||||
throw new InvalidOperationException("CC-Link IE connection is not connected. Call ConnectAsync() first.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Disconnect();
|
||||
_disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Net.Http.Headers;
|
||||
using RobotNet10.Script.IO;
|
||||
|
||||
namespace RobotNet10.ScriptEngine.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of HTTP connection using HttpClient.
|
||||
/// </summary>
|
||||
public class HttpConnection : IHttpConnection
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private bool _disposed;
|
||||
|
||||
public string BaseUrl { get; }
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public HttpConnection(string baseUrl, TimeSpan? timeout = null)
|
||||
{
|
||||
BaseUrl = baseUrl.TrimEnd('/');
|
||||
_httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(BaseUrl),
|
||||
Timeout = timeout ?? TimeSpan.FromSeconds(30)
|
||||
};
|
||||
IsConnected = false;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
// HTTP doesn't require explicit connection, but we can verify connectivity
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync("/");
|
||||
IsConnected = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsConnected = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
IsConnected = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<string> GetAsync(string path, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
EnsureConnected();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, path);
|
||||
AddHeaders(request, headers);
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
public async Task<string> PostAsync(string path, string content, string contentType = "application/json", Dictionary<string, string>? headers = null)
|
||||
{
|
||||
EnsureConnected();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, path);
|
||||
request.Content = new StringContent(content);
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
|
||||
AddHeaders(request, headers);
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
public async Task<string> PutAsync(string path, string content, string contentType = "application/json", Dictionary<string, string>? headers = null)
|
||||
{
|
||||
EnsureConnected();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Put, path);
|
||||
request.Content = new StringContent(content);
|
||||
request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
|
||||
AddHeaders(request, headers);
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
public async Task<string> DeleteAsync(string path, Dictionary<string, string>? headers = null)
|
||||
{
|
||||
EnsureConnected();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Delete, path);
|
||||
AddHeaders(request, headers);
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
private static void AddHeaders(HttpRequestMessage request, Dictionary<string, string>? headers)
|
||||
{
|
||||
if (headers != null)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
{
|
||||
request.Headers.Add(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new InvalidOperationException("HTTP connection is not connected. Call ConnectAsync() first.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_httpClient?.Dispose();
|
||||
IsConnected = false;
|
||||
_disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
using Modbus.Device;
|
||||
using RobotNet10.Script.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace RobotNet10.ScriptEngine.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of ModbusTCP connection using NModbus4 library.
|
||||
/// </summary>
|
||||
public class ModbusTcpConnection(string ipAddress, int port = 502, byte slaveId = 1, int connectTimeoutMs = 5000) : IModbusTcpConnection
|
||||
{
|
||||
private TcpClient? _tcpClient;
|
||||
private ModbusMaster? _modbusMaster;
|
||||
private bool _disposed;
|
||||
|
||||
public string IpAddress { get; } = ipAddress;
|
||||
public int Port { get; } = port;
|
||||
public byte SlaveId { get; } = slaveId;
|
||||
public int ConnectTimeoutMs { get; } = connectTimeoutMs;
|
||||
public bool IsConnected { get; private set; } = false;
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
|
||||
// Sử dụng ConnectTimeout
|
||||
using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(ConnectTimeoutMs)))
|
||||
{
|
||||
await _tcpClient.ConnectAsync(IpAddress, Port).WaitAsync(cts.Token);
|
||||
}
|
||||
|
||||
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
|
||||
IsConnected = true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Disconnect();
|
||||
throw new TimeoutException($"Connection to {IpAddress}:{Port} timed out after {ConnectTimeoutMs}ms");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Disconnect();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
Disconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
IsConnected = false;
|
||||
_modbusMaster?.Dispose();
|
||||
_modbusMaster = null;
|
||||
_tcpClient?.Close();
|
||||
_tcpClient?.Dispose();
|
||||
_tcpClient = null;
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort numberOfPoints)
|
||||
{
|
||||
EnsureConnected();
|
||||
return await Task.Run(() => _modbusMaster!.ReadHoldingRegisters(SlaveId, startAddress, numberOfPoints));
|
||||
}
|
||||
|
||||
public async Task<ushort[]> ReadInputRegistersAsync(ushort startAddress, ushort numberOfPoints)
|
||||
{
|
||||
EnsureConnected();
|
||||
return await Task.Run(() => _modbusMaster!.ReadInputRegisters(SlaveId, startAddress, numberOfPoints));
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadCoilsAsync(ushort startAddress, ushort numberOfPoints)
|
||||
{
|
||||
EnsureConnected();
|
||||
return await Task.Run(() => _modbusMaster!.ReadCoils(SlaveId, startAddress, numberOfPoints));
|
||||
}
|
||||
|
||||
public async Task<bool[]> ReadDiscreteInputsAsync(ushort startAddress, ushort numberOfPoints)
|
||||
{
|
||||
EnsureConnected();
|
||||
return await Task.Run(() => _modbusMaster!.ReadInputs(SlaveId, startAddress, numberOfPoints));
|
||||
}
|
||||
|
||||
public async Task WriteSingleCoilAsync(ushort coilAddress, bool value)
|
||||
{
|
||||
EnsureConnected();
|
||||
await Task.Run(() => _modbusMaster!.WriteSingleCoil(SlaveId, coilAddress, value));
|
||||
}
|
||||
|
||||
public async Task WriteMultipleCoilsAsync(ushort startAddress, bool[] values)
|
||||
{
|
||||
EnsureConnected();
|
||||
await Task.Run(() => _modbusMaster!.WriteMultipleCoils(SlaveId, startAddress, values));
|
||||
}
|
||||
|
||||
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
|
||||
{
|
||||
EnsureConnected();
|
||||
await Task.Run(() => _modbusMaster!.WriteSingleRegister(SlaveId, registerAddress, value));
|
||||
}
|
||||
|
||||
public async Task WriteMultipleRegistersAsync(ushort startAddress, ushort[] values)
|
||||
{
|
||||
EnsureConnected();
|
||||
await Task.Run(() => _modbusMaster!.WriteMultipleRegisters(SlaveId, startAddress, values));
|
||||
}
|
||||
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (!IsConnected || _modbusMaster == null)
|
||||
{
|
||||
throw new InvalidOperationException("ModbusTCP connection is not connected. Call ConnectAsync() first.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Disconnect();
|
||||
_disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Opc.Ua;
|
||||
using Opc.Ua.Client;
|
||||
using RobotNet10.Script.IO;
|
||||
|
||||
namespace RobotNet10.ScriptEngine.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of OPC UA connection using OPCFoundation.NetStandard.Opc.Ua library.
|
||||
/// </summary>
|
||||
public class OpcUaConnection : IOpcUaConnection
|
||||
{
|
||||
private Session? _session;
|
||||
private SessionReconnectHandler? _reconnectHandler;
|
||||
private bool _disposed;
|
||||
|
||||
public string EndpointUrl { get; }
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public OpcUaConnection(string endpointUrl)
|
||||
{
|
||||
EndpointUrl = endpointUrl;
|
||||
IsConnected = false;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
await ConnectAsync(string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
public async Task ConnectAsync(string username, string password)
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var applicationConfiguration = new ApplicationConfiguration
|
||||
{
|
||||
ApplicationName = "RobotNet10 ScriptEngine",
|
||||
ApplicationUri = Utils.Format(@"urn:{0}:ScriptEngine", System.Net.Dns.GetHostName()),
|
||||
ApplicationType = ApplicationType.Client,
|
||||
SecurityConfiguration = new SecurityConfiguration
|
||||
{
|
||||
ApplicationCertificate = new CertificateIdentifier { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\MachineDefault" },
|
||||
TrustedIssuerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Certificate Authorities" },
|
||||
TrustedPeerCertificates = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\UA Applications" },
|
||||
RejectedCertificateStore = new CertificateTrustList { StoreType = @"Directory", StorePath = @"%CommonApplicationData%\OPC Foundation\CertificateStores\RejectedCertificates" },
|
||||
AutoAcceptUntrustedCertificates = true,
|
||||
RejectSHA1SignedCertificates = false
|
||||
},
|
||||
TransportConfigurations = new TransportConfigurationCollection(),
|
||||
ClientConfiguration = new ClientConfiguration
|
||||
{
|
||||
DefaultSessionTimeout = 60000
|
||||
}
|
||||
};
|
||||
|
||||
await applicationConfiguration.ValidateAsync(ApplicationType.Client);
|
||||
|
||||
// Discover endpoints first using new API
|
||||
var endpointUrl = new Uri(EndpointUrl);
|
||||
var telemetryContext = new SimpleTelemetryContext();
|
||||
var discoveryClient = await DiscoveryClient.CreateAsync(applicationConfiguration, endpointUrl, DiagnosticsMasks.All, CancellationToken.None);
|
||||
var endpoints = await discoveryClient.GetEndpointsAsync(null, CancellationToken.None);
|
||||
await discoveryClient.CloseAsync(CancellationToken.None);
|
||||
|
||||
// Select endpoint - use the first available endpoint or find best match
|
||||
if (endpoints == null || endpoints.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"No endpoints found for OPC UA server at {EndpointUrl}");
|
||||
}
|
||||
|
||||
// Try to find a secure endpoint first, otherwise use the first one
|
||||
var endpointDescription = endpoints.FirstOrDefault(e => e.SecurityMode != MessageSecurityMode.None) ?? endpoints[0];
|
||||
|
||||
var endpointConfiguration = EndpointConfiguration.Create(applicationConfiguration);
|
||||
var configuredEndpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
|
||||
|
||||
UserIdentity? userIdentity = null;
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
var passwordBytes = string.IsNullOrEmpty(password) ? Array.Empty<byte>() : System.Text.Encoding.UTF8.GetBytes(password);
|
||||
userIdentity = new UserIdentity(username, passwordBytes);
|
||||
}
|
||||
|
||||
// Use ISessionFactory.CreateAsync instead of Session.CreateAsync
|
||||
ISessionFactory sessionFactory = new DefaultSessionFactory(telemetryContext);
|
||||
var session = await sessionFactory.CreateAsync(
|
||||
applicationConfiguration,
|
||||
configuredEndpoint,
|
||||
updateBeforeConnect: false,
|
||||
checkDomain: false,
|
||||
"RobotNet10 ScriptEngine Session",
|
||||
60000,
|
||||
userIdentity,
|
||||
preferredLocales: null,
|
||||
CancellationToken.None);
|
||||
|
||||
// Cast ISession to Session
|
||||
_session = session as Session ?? throw new InvalidOperationException("Failed to create OPC UA session");
|
||||
|
||||
_session.KeepAlive += (ISession session, KeepAliveEventArgs e) =>
|
||||
{
|
||||
if (e.CurrentState != ServerState.Unknown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_reconnectHandler != null && session is Session sessionImpl)
|
||||
{
|
||||
_reconnectHandler.BeginReconnect(sessionImpl, 5000, (sender, args) => { });
|
||||
}
|
||||
};
|
||||
_reconnectHandler = new SessionReconnectHandler(telemetryContext, false, 5000);
|
||||
_reconnectHandler.BeginReconnect(_session, 5000, (sender, e) => { });
|
||||
|
||||
IsConnected = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Disconnect();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task DisconnectAsync()
|
||||
{
|
||||
IsConnected = false;
|
||||
_reconnectHandler?.Dispose();
|
||||
_reconnectHandler = null;
|
||||
if (_session != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _session.CloseAsync(CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors during close
|
||||
}
|
||||
_session.Dispose();
|
||||
}
|
||||
_session = null;
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
// Synchronous wrapper for async disconnect
|
||||
DisconnectAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<object?> ReadNodeAsync(string nodeId)
|
||||
{
|
||||
EnsureConnected();
|
||||
var node = new NodeId(nodeId);
|
||||
var readValueId = new ReadValueId
|
||||
{
|
||||
NodeId = node,
|
||||
AttributeId = Attributes.Value
|
||||
};
|
||||
|
||||
var readValueIdCollection = new ReadValueIdCollection { readValueId };
|
||||
var response = await _session!.ReadAsync(null, 0, TimestampsToReturn.Neither, readValueIdCollection, CancellationToken.None);
|
||||
|
||||
if (StatusCode.IsGood(response.Results[0].StatusCode))
|
||||
{
|
||||
return response.Results[0].Value;
|
||||
}
|
||||
|
||||
throw new Exception($"Failed to read node {nodeId}: {response.Results[0].StatusCode}");
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, object?>> ReadNodesAsync(string[] nodeIds)
|
||||
{
|
||||
EnsureConnected();
|
||||
var readValueIdCollection = new ReadValueIdCollection();
|
||||
|
||||
foreach (var nodeId in nodeIds)
|
||||
{
|
||||
readValueIdCollection.Add(new ReadValueId
|
||||
{
|
||||
NodeId = new NodeId(nodeId),
|
||||
AttributeId = Attributes.Value
|
||||
});
|
||||
}
|
||||
|
||||
var response = await _session!.ReadAsync(null, 0, TimestampsToReturn.Neither, readValueIdCollection, CancellationToken.None);
|
||||
var result = new Dictionary<string, object?>();
|
||||
|
||||
for (int i = 0; i < nodeIds.Length; i++)
|
||||
{
|
||||
if (StatusCode.IsGood(response.Results[i].StatusCode))
|
||||
{
|
||||
result[nodeIds[i]] = response.Results[i].Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[nodeIds[i]] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task WriteNodeAsync(string nodeId, object value)
|
||||
{
|
||||
EnsureConnected();
|
||||
var writeValue = new WriteValue
|
||||
{
|
||||
NodeId = new NodeId(nodeId),
|
||||
AttributeId = Attributes.Value,
|
||||
Value = new DataValue(new Variant(value))
|
||||
};
|
||||
|
||||
var writeValueCollection = new WriteValueCollection { writeValue };
|
||||
var response = await _session!.WriteAsync(null, writeValueCollection, CancellationToken.None);
|
||||
|
||||
if (!StatusCode.IsGood(response.Results[0]))
|
||||
{
|
||||
throw new Exception($"Failed to write node {nodeId}: {response.Results[0]}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task WriteNodesAsync(Dictionary<string, object> values)
|
||||
{
|
||||
EnsureConnected();
|
||||
var writeValueCollection = new WriteValueCollection();
|
||||
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
writeValueCollection.Add(new WriteValue
|
||||
{
|
||||
NodeId = new NodeId(kvp.Key),
|
||||
AttributeId = Attributes.Value,
|
||||
Value = new DataValue(new Variant(kvp.Value))
|
||||
});
|
||||
}
|
||||
|
||||
var response = await _session!.WriteAsync(null, writeValueCollection, CancellationToken.None);
|
||||
|
||||
for (int i = 0; i < response.Results.Count; i++)
|
||||
{
|
||||
if (!StatusCode.IsGood(response.Results[i]))
|
||||
{
|
||||
var nodeId = values.Keys.ElementAt(i);
|
||||
throw new Exception($"Failed to write node {nodeId}: {response.Results[i]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string[]> BrowseNodesAsync(string? nodeId = null)
|
||||
{
|
||||
EnsureConnected();
|
||||
var node = nodeId == null ? ObjectIds.ObjectsFolder : new NodeId(nodeId);
|
||||
var nodesToBrowse = new BrowseDescriptionCollection
|
||||
{
|
||||
new BrowseDescription
|
||||
{
|
||||
NodeId = node,
|
||||
BrowseDirection = BrowseDirection.Forward,
|
||||
ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences,
|
||||
IncludeSubtypes = true,
|
||||
NodeClassMask = 0,
|
||||
ResultMask = (uint)BrowseResultMask.All
|
||||
}
|
||||
};
|
||||
|
||||
var response = await _session!.BrowseAsync(null, null, 0, nodesToBrowse, CancellationToken.None);
|
||||
|
||||
if (response.Results != null && response.Results.Count > 0 && response.Results[0].References != null)
|
||||
{
|
||||
return response.Results[0].References.Select(rd => rd.NodeId.ToString()).ToArray();
|
||||
}
|
||||
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (!IsConnected || _session == null)
|
||||
{
|
||||
throw new InvalidOperationException("OPC UA connection is not connected. Call ConnectAsync() first.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Disconnect();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple telemetry context implementation for OPC UA
|
||||
/// </summary>
|
||||
internal class SimpleTelemetryContext : ITelemetryContext
|
||||
{
|
||||
public ILoggerFactory LoggerFactory => Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance;
|
||||
|
||||
public ActivitySource ActivitySource => new ActivitySource("RobotNet10.ScriptEngine.OpcUa");
|
||||
|
||||
public System.Diagnostics.Metrics.Meter CreateMeter() => new System.Diagnostics.Metrics.Meter("RobotNet10.ScriptEngine.OpcUa");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using RobotNet10.Script.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace RobotNet10.ScriptEngine.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of ProfiNet connection.
|
||||
/// Note: This is a basic implementation. Full ProfiNet support may require additional libraries.
|
||||
/// </summary>
|
||||
public class ProfiNetConnection : IProfiNetConnection
|
||||
{
|
||||
private TcpClient? _tcpClient;
|
||||
private NetworkStream? _stream;
|
||||
private bool _disposed;
|
||||
|
||||
public string IpAddress { get; }
|
||||
public int Slot { get; }
|
||||
public int Subslot { get; }
|
||||
public bool IsConnected { get; private set; }
|
||||
|
||||
public ProfiNetConnection(string ipAddress, int slot = 1, int subslot = 1)
|
||||
{
|
||||
IpAddress = ipAddress;
|
||||
Slot = slot;
|
||||
Subslot = subslot;
|
||||
IsConnected = false;
|
||||
}
|
||||
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
if (IsConnected)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(IpAddress, 34964); // Standard ProfiNet port
|
||||
_stream = _tcpClient.GetStream();
|
||||
IsConnected = true;
|
||||
|
||||
// TODO: Implement ProfiNet DCP (Discovery and Configuration Protocol) handshake
|
||||
// This requires implementing the ProfiNet protocol stack
|
||||
}
|
||||
catch
|
||||
{
|
||||
Disconnect();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
Disconnect();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Disconnect()
|
||||
{
|
||||
IsConnected = false;
|
||||
_stream?.Close();
|
||||
_stream = null;
|
||||
_tcpClient?.Close();
|
||||
_tcpClient?.Dispose();
|
||||
_tcpClient = null;
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadAsync(int index, int length)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
// TODO: Implement ProfiNet read operation
|
||||
// This requires implementing the ProfiNet IO data exchange protocol
|
||||
await Task.CompletedTask;
|
||||
throw new NotImplementedException("ProfiNet read operation is not yet fully implemented. Full ProfiNet support requires additional protocol implementation.");
|
||||
}
|
||||
|
||||
public async Task WriteAsync(int index, byte[] data)
|
||||
{
|
||||
EnsureConnected();
|
||||
|
||||
// TODO: Implement ProfiNet write operation
|
||||
// This requires implementing the ProfiNet IO data exchange protocol
|
||||
await Task.CompletedTask;
|
||||
throw new NotImplementedException("ProfiNet write operation is not yet fully implemented. Full ProfiNet support requires additional protocol implementation.");
|
||||
}
|
||||
|
||||
private void EnsureConnected()
|
||||
{
|
||||
if (!IsConnected || _stream == null)
|
||||
{
|
||||
throw new InvalidOperationException("ProfiNet connection is not connected. Call ConnectAsync() first.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Disconnect();
|
||||
_disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user