Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md)
[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-☕-FFDD00)](https://buymeacoffee.com/drusteeby)

![DruOPC Browser connected to the simulator, watching live variables](docs/media/druopc-browser.jpg)

**DruOPC** is a two-piece toolkit for learning, testing and demonstrating
OPC UA:

Expand Down Expand Up @@ -33,6 +35,18 @@ hunt, you can [buy me a coffee](https://buymeacoffee.com/drusteeby). ☕

## Quick start

### Docker

```console
git clone https://github.com/drusteeby/DruOPC.git
cd DruOPC
docker compose up
```

Open <http://localhost:5080> and connect to `opc.tcp://simulator:50000`.

### From source

Prerequisite: [.NET SDK](https://dotnet.microsoft.com/download) 10 or later.

```console
Expand Down
7 changes: 6 additions & 1 deletion browser/Services/UaApplicationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ public sealed class UaApplicationProvider
/// Root directory for the client PKI stores.
/// </summary>
public static string PkiRoot { get; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
// SpecialFolderOption.Create: with the default option the folder resolves
// to "" when ~/.local/share does not exist (e.g. in containers), which
// silently turns PkiRoot into a CWD-relative path.
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData,
Environment.SpecialFolderOption.Create),
"DruOPC",
"pki");

Expand Down
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
image: ghcr.io/drusteeby/druopc-simulator:latest
ports:
- "50000:50000" # OPC UA endpoint
- "8080:8080" # pn.json web server
- "5840:8080" # pn.json web server (host port off 8080 to avoid the most common dev-port conflict)
environment:
# The hostname other containers (and certificates) see.
OpcPlc__OpcUa__Hostname: simulator
Expand Down
Binary file added docs/media/druopc-browser.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 2 additions & 9 deletions src/CompanionSpecs/DI/DiNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace OpcPlc.CompanionSpecs.DI;
using Opc.Ua;
using Opc.Ua.Server;
using System;
using System.IO;
using System.Reflection;

/// <summary>
Expand Down Expand Up @@ -31,17 +30,11 @@ public DiNodeManager(IServerInternal server, ApplicationConfiguration _)
/// </summary>
protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
{
var uanodesPath = "CompanionSpecs/DI/Opc.Ua.DI.PredefinedNodes.uanodes";
var snapLocation = Environment.GetEnvironmentVariable("SNAP");
if (!string.IsNullOrWhiteSpace(snapLocation))
{
// Application running as a snap
uanodesPath = Path.Join(snapLocation, uanodesPath);
}
var uanodesPath = "OpcUaDi"; // embedded resource, LogicalName in opc-plc.csproj

var predefinedNodes = new NodeStateCollection();
predefinedNodes.LoadFromBinaryResource(context,
uanodesPath, // CopyToOutputDirectory -> PreserveNewest.
uanodesPath,
typeof(DiNodeManager).GetTypeInfo().Assembly,
updateTables: true);

Expand Down
11 changes: 2 additions & 9 deletions src/PluginNodes/Boiler2PluginNodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ namespace OpcPlc.PluginNodes;
using OpcPlc.PluginNodes.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
Expand Down Expand Up @@ -158,18 +157,12 @@ private void AddNodes()
/// </summary>
private static NodeStateCollection LoadPredefinedNodes(ISystemContext context)
{
var uanodesPath = "Boilers/Boiler2/BoilerModel2.PredefinedNodes.uanodes";
var snapLocation = Environment.GetEnvironmentVariable("SNAP");
if (!string.IsNullOrWhiteSpace(snapLocation))
{
// Application running as a snap
uanodesPath = Path.Join(snapLocation, uanodesPath);
}
var uanodesPath = "BoilerModel2"; // embedded resource, LogicalName in opc-plc.csproj

var predefinedNodes = new NodeStateCollection();

predefinedNodes.LoadFromBinaryResource(context,
uanodesPath, // CopyToOutputDirectory -> PreserveNewest.
uanodesPath,
typeof(PlcNodeManager).GetTypeInfo().Assembly,
updateTables: true);

Expand Down
11 changes: 2 additions & 9 deletions src/PluginNodes/ComplexTypeBoilerPluginNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace OpcPlc.PluginNodes;
using OpcPlc.PluginNodes.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Timers;

Expand Down Expand Up @@ -87,18 +86,12 @@ private void AddNodes(FolderState methodsFolder)
/// </summary>
private static NodeStateCollection LoadPredefinedNodes(ISystemContext context)
{
var uanodesPath = "Boilers/Boiler1/BoilerModel1.PredefinedNodes.uanodes";
var snapLocation = Environment.GetEnvironmentVariable("SNAP");
if (!string.IsNullOrWhiteSpace(snapLocation))
{
// Application running as a snap
uanodesPath = Path.Join(snapLocation, uanodesPath);
}
var uanodesPath = "BoilerModel1"; // embedded resource, LogicalName in opc-plc.csproj

var predefinedNodes = new NodeStateCollection();

predefinedNodes.LoadFromBinaryResource(context,
uanodesPath, // CopyToOutputDirectory -> PreserveNewest.
uanodesPath,
typeof(PlcNodeManager).GetTypeInfo().Assembly,
updateTables: true);

Expand Down
9 changes: 1 addition & 8 deletions src/SimpleEvent/SimpleEventsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ namespace SimpleEvents;
using OpcPlc.SimpleEvent;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;

Expand Down Expand Up @@ -89,13 +88,7 @@ public override NodeId New(ISystemContext context, NodeState node)
/// </summary>
protected override NodeStateCollection LoadPredefinedNodes(ISystemContext context)
{
var uanodesPath = "SimpleEvent/SimpleEvents.PredefinedNodes.uanodes";
var snapLocation = Environment.GetEnvironmentVariable("SNAP");
if (!string.IsNullOrWhiteSpace(snapLocation))
{
// Application running as a snap
uanodesPath = Path.Join(snapLocation, uanodesPath);
}
var uanodesPath = "SimpleEvents"; // embedded resource, LogicalName in opc-plc.csproj

var predefinedNodes = new NodeStateCollection();
predefinedNodes.LoadFromBinaryResource(context,
Expand Down
20 changes: 7 additions & 13 deletions src/opc-plc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.17.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.17.0" />
<PackageReference Include="Scrutor" Version="6.1.0" />
<PackageReference Include="Scrutor" Version="7.0.0" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="3.0.0" />
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="10.0.301" />
<PackageReference Update="Nerdbank.GitVersioning" Version="3.10.91" />
Expand Down Expand Up @@ -83,24 +83,18 @@
</Choose>

<ItemGroup>
<None Update="Boilers\Boiler1\BoilerModel1.PredefinedNodes.uanodes">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Boilers\Boiler2\BoilerModel2.PredefinedNodes.uanodes">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="CompanionSpecs\DI\Opc.Ua.DI.PredefinedNodes.uanodes">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<!-- Compiled information models, embedded so no filesystem layout is assumed at runtime.
LogicalName is the resource name the loaders pass to LoadFromBinaryResource. -->
<EmbeddedResource Include="Boilers\Boiler1\BoilerModel1.PredefinedNodes.uanodes" LogicalName="BoilerModel1" />
<EmbeddedResource Include="Boilers\Boiler2\BoilerModel2.PredefinedNodes.uanodes" LogicalName="BoilerModel2" />
<EmbeddedResource Include="CompanionSpecs\DI\Opc.Ua.DI.PredefinedNodes.uanodes" LogicalName="OpcUaDi" />
<EmbeddedResource Include="SimpleEvent\SimpleEvents.PredefinedNodes.uanodes" LogicalName="SimpleEvents" />
<None Update="nodesfile.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="opcplc.service">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="SimpleEvent\SimpleEvents.PredefinedNodes.uanodes">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
Expand Down
Loading