Skip to content

Commit

Permalink
New .NET sample that uses latest project templates.
Browse files Browse the repository at this point in the history
  • Loading branch information
markwaterman committed Oct 30, 2023
1 parent 1377298 commit eb7937f
Show file tree
Hide file tree
Showing 61 changed files with 410 additions and 2,082 deletions.
22 changes: 22 additions & 0 deletions DotNetCore/Messages/DigitalTwinMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using JsonSubTypes;
using Newtonsoft.Json;

namespace Messages
{
/// <summary>
/// Base class for messages sent to a real-time digital twin.
/// </summary>
/// <remarks>
/// Messages sent to real-time digitial twins should derive from this type.
/// </remarks>
[JsonConverter(typeof(JsonSubtypes), "MessageType")]
public abstract class DigitalTwinMessage
{
/// <summary>
/// Used during deserialization to determine derived message type.
/// </summary>
/// <see href="https://static.scaleoutsoftware.com/docs/digital_twin_user_guide/software_toolkit/dt_builder/dotnetcore_api/dotnetcore_multiple_msg_types.html">Using Multiple Message Types</see>
public abstract string MessageType { get; }
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>1.4.0</Version>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
Expand Down
23 changes: 23 additions & 0 deletions DotNetCore/Messages/WindTurbineMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Messages
{
public class WindTurbineMessage : DigitalTwinMessage
{
/// <summary>Device temperature.</summary>
public double Temperature { get; set; }

/// <summary>Device RPMs.</summary>
public double RPM { get; set; }

/// <summary>Timestamp of when the message was originated by device.</summary>
public DateTimeOffset Timestamp { get; set; }

/// <summary>
/// Overridden property used by JsonSubtypes during deserialization.
/// </summary>
public override string MessageType => nameof(WindTurbineMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

namespace RealTimeWindTurbine
{
class Program
internal class Program
{
public static void Main(string[] args)
{
if (args.Length > 0 && args[0].ToLower() == "--debug")
{
DebugHelper.StartIG(igName: "__RealTimeWindTurbine_IG",
DebugHelper.StartIG(igName: "__RealTimeRealTimeWindTurbine_IG",
startupParam: null,
startupSignalPort: 45845,
devConnectionString: "bootstrapGateways=localhost:721;ignoreKeyAppId=true");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"profiles": {
"RealTimeWindTurbine": {
"RealTimeRealTimeWindTurbine": {
"commandName": "Project",
"commandLineArgs": "--debug",
"environmentVariables": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
<Version>1.0.0</Version>
<OutputType>Exe</OutputType>
<StartupObject />
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Scaleout.DigitalTwin.Hosting" Version="3.5.0" />
<PackageReference Include="Scaleout.DigitalTwin.Hosting" Version="3.10.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\WindTurbineMessages\WindTurbineMessages.csproj" />
<ProjectReference Include="..\Messages\Messages.csproj" />
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Messages;
using Newtonsoft.Json;
using Scaleout.Streaming.DigitalTwin.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RealTimeWindTurbine
{
public class RealTimeWindTurbineMessageProcessor :
MessageProcessor<RealTimeWindTurbineModel, DigitalTwinMessage>
{
public override ProcessingResult ProcessMessages(ProcessingContext context,
RealTimeWindTurbineModel digitalTwin,
IEnumerable<DigitalTwinMessage> newMessages)
{
try
{
// Process incoming messages
foreach (var message in newMessages)
{
switch (message)
{
case WindTurbineMessage windTurbineMsg:
// Store the incoming message for later historical analysis
// in the model instance.
digitalTwin.GearboxTemps.Add(windTurbineMsg.Temperature);
digitalTwin.RPMs.Add(windTurbineMsg.RPM);
break;
default:
throw new NotImplementedException($"Message processor does not support message type {message.GetType()}");
}

// We can optionally send a message or alert back to a data source (e.g. IoT device):
if (digitalTwin.GearboxTemps.Last() > 120.0)
{
var msg = new { Command = "Shutdown" };
context.SendToDataSource(msg);
}
}
}
catch (Exception ex)
{
context.LogMessage(LogSeverity.Error, string.Format(
"Exception occurred while processing new messages for object '{0}'. Details: {1}",
digitalTwin.Id, ex.Message));
}

// Persist changes to the digitalTwin.
return ProcessingResult.DoUpdate;
}
}
}
14 changes: 14 additions & 0 deletions DotNetCore/RealTimeWindTurbine/RealTimeWindTurbineModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Messages;
using System;
using Scaleout.Streaming.DigitalTwin.Core;
using System.Collections.Generic;

namespace RealTimeWindTurbine
{
public class RealTimeWindTurbineModel : DigitalTwinBase
{
public List<double> GearboxTemps { get; } = new List<double>();

public List<double> RPMs { get; } = new List<double>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"SupportedPersistenceProviders": "AzureDigitalTwinsService,SQLServer,SQLite",
"EnableSimulationSupport": false,

"DTCacheConfig": {
"ClientCacheEviction": "Random-MaxMemory",
"ClientCacheCapacity": 20000,
"KeystringCacheSize": 20000
},

"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"MinimumLevel": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@

namespace SimulatedWindTurbine
{
class Program
internal class Program
{
public static void Main(string[] args)
{
if (args.Length > 0 && args[0].ToLower() == "--debug")
{
DebugHelper.StartIG(igName: "__SimulatedWindTurbine_IG",
DebugHelper.StartIG(igName: "__SimulatedSimulatedWindTurbine_IG",
startupParam: null,
startupSignalPort: 45845,
devConnectionString: "bootstrapGateways=localhost:721;ignoreKeyAppId=true");
}

Startup.RunWithSimulationSupport(new WindTurbineSimulationMessageProcessor(), new WindTurbineSimulationProcessor(), args);
Startup.RunWithSimulationSupport(new SimulatedWindTurbineMessageProcessor(), new SimulatedWindTurbineProcessor(), args);

}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"profiles": {
"RealTimeWindTurbine": {
"SimulatedSimulatedWindTurbine": {
"commandName": "Project",
"commandLineArgs": "--debug",
"environmentVariables": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
<Version>1.0.0</Version>
<OutputType>Exe</OutputType>
<StartupObject />
<Nullable>disable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Scaleout.DigitalTwin.Hosting" Version="3.5.0" />
<PackageReference Include="Scaleout.DigitalTwin.Hosting" Version="3.10.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\WindTurbineMessages\WindTurbineMessages.csproj" />
<ProjectReference Include="..\Messages\Messages.csproj" />
</ItemGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Messages;
using Scaleout.Streaming.DigitalTwin.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimulatedWindTurbine
{
/// <summary>
/// Handles messages sent from a real-time digital twin.
/// </summary>
/// <remarks>
/// Messages sent here typically originate from ProcessingContext.SendToDataSource() calls made
/// in a real-time digital twin's MessageProcessor implementation. The model
/// in this simulation project is simulating a data source (a real-world device),
/// so when a real-time model sends a message back to its data source during a simulation run,
/// the message arrives here.
/// </remarks>
public class SimulatedWindTurbineMessageProcessor : MessageProcessor<SimulatedWindTurbineModel, DigitalTwinMessage>
{
public override ProcessingResult ProcessMessages(ProcessingContext context, SimulatedWindTurbineModel digitalTwin, IEnumerable<DigitalTwinMessage> newMessages)
{
// If your simulation model receives messages, process them for the supplied digital twin.
/*
foreach (DigitalTwinMessage message in newMessages)
{
switch (message)
{
case ExampleMessage exampleMessage:
digitalTwin.SomeState = exampleMessage.StringPayload;
break;
default:
throw new NotImplementedException($"Message processor does not support message type {message.GetType()}");
}
}
*/

// Return ProcessingResult.DoUpdate if this method modified the state of this digital twin instance
// to persist the changes back to ScaleOut StreamServer;
// otherwise, if no changes occurred or the changes are to be discarded,
// return ProcessingResult.NoUpdate as an optimization to reduce update overhead.
return ProcessingResult.DoUpdate;
}
}
}
11 changes: 11 additions & 0 deletions DotNetCore/SimulatedWindTurbine/SimulatedWindTurbineModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Scaleout.Streaming.DigitalTwin.Core;

namespace SimulatedWindTurbine
{
public class SimulatedWindTurbineModel : DigitalTwinBase
{
public double CurrentTemp { get; set; }

public double CurrentRPMs { get; set; }
}
}
27 changes: 27 additions & 0 deletions DotNetCore/SimulatedWindTurbine/SimulatedWindTurbineProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using Messages;
using Scaleout.Streaming.DigitalTwin.Core;

namespace SimulatedWindTurbine
{
public class SimulatedWindTurbineProcessor : SimulationProcessor<SimulatedWindTurbineModel>
{
public override ProcessingResult ProcessModel(ProcessingContext context,
SimulatedWindTurbineModel simTurbine,
DateTimeOffset currentTime)
{
simTurbine.CurrentRPMs++;
simTurbine.CurrentTemp++;

var msg = new WindTurbineMessage
{
RPM = simTurbine.CurrentRPMs,
Temperature = simTurbine.CurrentTemp,
Timestamp = context.GetCurrentTime()
};
context.SimulationController.EmitTelemetry("RealTimeWindTurbine", msg);

return ProcessingResult.DoUpdate;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"SupportedPersistenceProviders": "AzureDigitalTwinsService,SQLServer,SQLite",
"EnableSimulationSupport": true,

"DTCacheConfig": {
"ClientCacheEviction": "Random-MaxMemory",
"ClientCacheCapacity": 20000,
"KeystringCacheSize": 20000
},

"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"MinimumLevel": {
Expand Down
29 changes: 29 additions & 0 deletions DotNetCore/UnitTests/RealTimeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Scaleout.DigitalTwin.Workbench;
using SimulatedWindTurbine;
using RealTimeWindTurbine;
using Messages;

namespace UnitTests
{
public class RealTimeTests
{
[Fact]
public void SendMessage()
{
RealTimeWorkbench wb = new RealTimeWorkbench();
var endpoint = wb.AddRealTimeModel("RealTimeWindTurbine", new RealTimeWindTurbineMessageProcessor());

var msg = new WindTurbineMessage
{
RPM = 20,
Temperature = 75,
Timestamp = DateTimeOffset.Now
};
endpoint.Send("Turbine1", msg);

var rtInstances = wb.GetInstances<RealTimeWindTurbineModel>("RealTimeWindTurbine");
var rtTurbine1 = rtInstances["Turbine1"];
Assert.Equal(20, rtTurbine1.RPMs.Last());
}
}
}
Loading

0 comments on commit eb7937f

Please sign in to comment.