Skip to content

Setup: Data Model Configuration

Trais McAllister edited this page Nov 20, 2023 · 3 revisions

Adapter Setup: Data Model Configuration

Setting up an adapter using a data model may be more comfortable for programmers familiar with Object-Oriented programming or those who use frameworks such as Entity Framework. A data model configuration is best utilized when paired with an implementation of IAdapterSource when you are consuming data from another system.

Create an IAdapterSource

In this example, we'll create an IAdapterSource that randomly updates values on a timer and call it TimerSource. Implementing IAdapterSource requires the following:

  • OnDataReceived event
  • Start() method
  • Stop() method
class TimerSource : IAdapterSource {
  public event DataReceivedHandler? OnDataReceived;
  
  public void Start() { }

  public void Stop() { }
}

Let's add the timer now:

class TimerSource : IAdapterSource {
  // Implemented from IAdapterSource
  public event DataReceivedHandler? OnDataReceived;

  private System.Timers.Timer _timer = new System.Timers.Timer();  

  public TimerSource() {
    _timer.Interval = 50;// Update every 50ms
    _timer.Elapsed += _tick;
  }

  private void _tick(object? sender, System.Timers.ElapsedEventArgs e) {
    // ... update DataItems
    
    if (OnDataReceived != null) OnDataReceived(null, new DataReceivedEventArgs());
  }

  // Implemented from IAdapterSource
  public void Start(CancellationToken token = default) {
    _timer.Start();
  }

  // Implemented from IAdapterSource
  public void Stop() {
    _timer.Stop();
  }
}

Create IAdapterDataModel

Now that we have a mechanism for "getting data", we need a data model to store it within. When designing a data model, you must use the DataItemAttribute implementations on your properties.

class DataModel : IAdapterDataModel {
  [Event("avail")]
  public string? Availability { get; set; }

  [Sample("xPos")]
  public int? XPosition { get; set; }

  [Sample("yPos")]
  public int? YPosition { get; set; }

  [Sample("zPos")]
  public int? ZPosition { get; set; }

  [Event("exec")]
  public string? Execution { get; set; }
}

Update and Publish the Data Model

Going back into your IAdapterSource implementation, let's now implement the use of your new DataModel class:

class TimerSource : IAdapterSource {
  // ... IAdapterSource implementation

  public DataModel Model { get; private set; } = new DataModel();

  private Random _rand = new Random();

  private void _tick(object? sender, System.Timers.ElapsedEventArgs e) {
    // Simply update the Model with the latest values
    Model.Availability = "AVAILABLE";

    Model.Execution = Guid.NewGuid().ToString();

    Model.XPosition = _rand.NextDouble() * 1000;
    Model.YPosition = _rand.NextDouble() * 1000;
    Model.ZPosition = _rand.NextDouble() * 1000;

    // Then, send the Model (updated or not) to the Adapter via the OnDataReceived event
    if (OnDataReceived != null) OnDataReceived(Model, new DataReceivedEventArgs());
  }
}

Initialize the Adapter

With both the IAdapterSource and IAdapterDataModel implemented, we can properly initialize our Adapter:

var source = new TimerSource();

var adapter = new TcpAdapter();
adapter.Start(source); // Provide an IAdapterSource to automatically map the DataItems

Console.WriteLine("Press any key to exit");
while(!Console.KeyAvailable) {
  // Wait for input to exit
}

adapter.Stop();

Full example

See the PCAdapter project for a full implementation of IAdapterSource and IAdapterDataModel. The PCAdapter project tracks your mouse movement as the data source.

Benefits of IAdapterDataModel

With the release of v2.0.0, the Adapter SDK provides a comprehensive object model that mirrors the MTConnect standard. When configuring an IAdapterDataModel with the use of implemented MTConnect Component classes, the Adapter SDK is capable of constructing the device model configuration for the reference C++ Agent and sending it to the Agent.

If you utilize the comprehensive object model for Observational Types/Sub-Types, then the references for those types are automatically generated in the device model configuration as well.

A simple example is when defining the axes for your machine:

public class MyMachineAxis : Axis
{
	[Sample("pos")]
	public Position.ACTUAL ActualPosition { get; set; }
}

public class MyMachineAxes : Axes
{
	[DataItemPartial("x_")]
	public MyMachineAxis X => GetOrAddAxis<MyMachineAxis>(nameof(X));

	[DataItemPartial("y_")]
	public MyMachineAxis Y => GetOrAddAxis<MyMachineAxis>(nameof(Y));
	
	[DataItemPartial("z_")]
	public MyMachineAxis Z => GetOrAddAxis<MyMachineAxis>(nameof(Z));
}

Resulting device model configuration fragment:

<Axes id="{name}" name="{name}">
  <Components>
	<Axis id="x" name="X">
	  <DataItems>
		<DataItem category="SAMPLE" id="x_pos" name="x_pos" subType="ACTUAL" type="POSITION" units="MILLIMETER" />
	  </DataItems>
	</Axis>
	<Axis id="y" name="Y">
	  <DataItems>
		<DataItem category="SAMPLE" id="y_pos" name="y_pos" subType="ACTUAL" type="POSITION" units="MILLIMETER" />
	  </DataItems>
	</Axis>
	<Axis id="z" name="Z">
	  <DataItems>
		<DataItem category="SAMPLE" id="z_pos" name="z_pos" subType="ACTUAL" type="POSITION" units="MILLIMETER" />
	  </DataItems>
	</Axis>
  </Components>
</Axes>