diff --git a/.github/workflows b/.github/workflows
new file mode 100644
index 0000000..4998598
--- /dev/null
+++ b/.github/workflows
@@ -0,0 +1,32 @@
+# This is a basic workflow to help you get started with Actions
+name: CI
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the master branch
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ # This workflow contains a single job called "build"
+ build:
+ # The type of runner that the job will run on
+ runs-on: windows-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - name: check out repository code
+ uses: actions/checkout@v2
+
+ # Build .Net Source
+ - name: Execut cake to build .NET
+ working-directory: NET
+ run: .\build.ps1 -t Test
+ shell: powershell
\ No newline at end of file
diff --git a/NET/AdsMapperCli/AdsMapperCli.csproj b/NET/AdsMapperCli/AdsMapperCli.csproj
new file mode 100644
index 0000000..ac539e2
--- /dev/null
+++ b/NET/AdsMapperCli/AdsMapperCli.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net48
+ AdsMapperCli.Program
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NET/AdsMapperCli/DestinationDataObject.cs b/NET/AdsMapperCli/DestinationDataObject.cs
new file mode 100644
index 0000000..be78687
--- /dev/null
+++ b/NET/AdsMapperCli/DestinationDataObject.cs
@@ -0,0 +1,96 @@
+using Mbc.Pcs.Net.State;
+using System;
+
+namespace AdsMapperCli
+{
+ /* PLC Struct:
+ TYPE ST_Test :
+ STRUCT
+ bBoolValue1 : BOOL := TRUE;
+ nByteValue1 : BYTE := 255;
+ nSbyteValue1 : BYTE := 127;
+ nUshortValue1 : UINT := 65535;
+ nShortValue1 : INT := 32767;
+ nUintValue1 : UDINT := 4294967295;
+ nIntValue1 : DINT := 2147483647;
+ fFloatValue1 : REAL := -1.11;
+ fDoubleValue1 : LREAL := 1.11;
+ fDoubleValue2 : LREAL := 2.22;
+ fDoubleValue3 : LREAL := 3.33;
+ fDoubleValue4MappedName : LREAL := 4.44;
+ tPlcTimeValue1 : TIME := T#1H33M44S555MS;
+ dPlcDateValue1 : DATE := D#2021-08-30;
+ dtPlcDateTimeValue1 : DATE_AND_TIME := DT#2021-08-30-11:12:13;
+ aIntArrayValue : ARRAY[0..2] OF DINT := [1, 2, 3];
+ eEnumStateValue : E_State := E_State.eRunning;
+ sPlcVersion : STRING(10) := '21.08.30.0';
+ sUtf7String : STRING(6) := 'ÄÖö@Ü7';
+ wsUnicodeString : WSTRING(6) := "ÄÖö@Ü8";
+ END_STRUCT
+ END_TYPE
+
+ {attribute 'qualified_only'}
+ {attribute 'strict'}
+ TYPE E_State :
+ (
+ eNone := 0,
+ eStartup := 1,
+ eRunning := 2,
+ eStop := 3
+ );
+ END_TYPE
+ */
+
+ public class DestinationDataObject : IPlcState
+ {
+ #region " PlcAdsStateReader requires IPlcState interface "
+
+ ///
+ /// SPS Zeitstempel der Status Daten
+ ///
+ public DateTime PlcTimeStamp { get; set; }
+
+ ///
+ /// Güte der Status Daten
+ ///
+ public PlcDataQuality PlcDataQuality { get; set; }
+
+ #endregion
+
+ public bool BoolValue1 { get; set; }
+ public byte ByteValue1 { get; set; }
+ public sbyte SbyteValue1 { get; set; }
+ public ushort UshortValue1 { get; set; }
+ public short ShortValue1 { get; set; }
+ public uint UintValue1 { get; set; }
+ public int IntValue1 { get; set; }
+ public float FloatValue1 { get; set; }
+ public double DoubleValue1 { get; set; }
+ public double DoubleValue2 { get; set; }
+ public double DoubleValue3 { get; set; }
+ public double DoubleValue4MappedName { get; set; }
+ public TimeSpan PlcTimeValue1 { get; set; }
+ public DateTime PlcDateValue1 { get; set; }
+ public DateTime PlcDateTimeValue1 { get; set; }
+ public int[] IntArrayValue { get; set; } = new int[3];
+ public State EnumStateValue { get; set; }
+ public string PlcVersion { get; set; }
+ public string Utf7String { get; set; }
+ public string UnicodeString { get; set; }
+
+ //public Motor MotorObject { get; set; }
+ }
+
+ public class Motor
+ {
+ public double ActualSpeed { get; set; }
+ }
+
+ public enum State
+ {
+ None = 0,
+ Startup = 1,
+ Running = 2,
+ Stop = 3,
+ }
+}
diff --git a/NET/AdsMapperCli/Program.cs b/NET/AdsMapperCli/Program.cs
new file mode 100644
index 0000000..91f1704
--- /dev/null
+++ b/NET/AdsMapperCli/Program.cs
@@ -0,0 +1,99 @@
+using Mbc.Ads.Mapper;
+using Mbc.Pcs.Net.Connection;
+using Mbc.Pcs.Net.State;
+using NLog;
+using System;
+
+namespace AdsMapperCli
+{
+ public static class Program
+ {
+ private static readonly ILogger _logger = LogManager.GetCurrentClassLogger();
+ private static PlcAdsConnectionService _adsConnectionService;
+ private static PlcAdsStateReader _plcAdsTestPlaceStatus;
+
+ public static void Main(string[] args)
+ {
+ SetupNLog();
+ try
+ {
+ string amsnetid = "172.28.85.92.1.1";
+ _logger.Info("Setup & Connect to TwinCat on {0}", amsnetid);
+ _adsConnectionService = new PlcAdsConnectionService(amsnetid, 851);
+ _adsConnectionService.ConnectionStateChanged += OnConnectionStateChanged;
+
+ var testPlaceStatusConfig = new PlcAdsStateReaderConfig
+ {
+ VariablePath = $"PCS_Status.stTest",
+ AdsMapperConfiguration = new AdsMapperConfiguration(
+ cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt", "s", "ws"))),
+ CycleTime = TimeSpan.FromMilliseconds(2),
+ MaxDelay = TimeSpan.FromMilliseconds(500),
+ };
+
+ // Setup state Reader
+ _plcAdsTestPlaceStatus = new PlcAdsStateReader(_adsConnectionService, testPlaceStatusConfig);
+ _plcAdsTestPlaceStatus.StatesChanged += OnPlcStatesChange;
+
+ // RocknRoll
+ _adsConnectionService.Start();
+
+ // Wait for termination
+ var keepRunning = true;
+ Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
+ {
+ e.Cancel = true;
+ keepRunning = false;
+ };
+
+ while (keepRunning) { }
+
+ _logger.Info("stopping output");
+ _plcAdsTestPlaceStatus.StopSampling();
+ }
+ catch (Exception ex)
+ {
+ _logger.Info(ex, "houston we have a problem: {0}", ex.Message);
+ }
+ finally
+ {
+ _plcAdsTestPlaceStatus?.Dispose();
+ _adsConnectionService?.Dispose();
+ }
+ }
+
+ private static void SetupNLog()
+ {
+ var config = new NLog.Config.LoggingConfiguration();
+ // Targets where to log to: Console
+ var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
+ // Rules for mapping loggers to targets
+ config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
+ // Apply config
+ NLog.LogManager.Configuration = config;
+ }
+
+ private static void OnConnectionStateChanged(object sender, PlcConnectionChangeArgs e)
+ {
+ if (e.Connected)
+ {
+ _logger.Info("Connected to TwinCat");
+ _plcAdsTestPlaceStatus.StartSampling();
+ }
+ else
+ {
+ Console.WriteLine("Disconnected to TwinCat");
+ _plcAdsTestPlaceStatus.StopSampling();
+ }
+ }
+
+ private static void OnPlcStatesChange(object source, PlcMultiStateChangedEventArgs testPlaceStatusEvent)
+ {
+ var config = new System.Text.Json.JsonSerializerOptions() { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+ config.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
+
+ _logger.Info("New State");
+ _logger.Info(System.Text.Json.JsonSerializer.Serialize(testPlaceStatusEvent.State, config));
+ }
+ }
+}
diff --git a/NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs b/NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs
index 0f41a79..ee73719 100644
--- a/NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs
+++ b/NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs
@@ -28,7 +28,7 @@ public void AdsMappingConfigurationShouldMapAdsStreamToDataObject()
{
// Arrange
AdsMapperConfiguration config = new AdsMapperConfiguration(
- cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix('f', 'n', 'b', 'a', 'e'))
+ cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt", "s", "ws"))
.ForMember(dest => dest.DoubleValue4MappedName, opt => opt.MapFrom("fdoublevalue4"))
.ForMember(dest => dest.DoubleValue4MappedName, opt => opt.ConvertFromSourceUsing(value => ((double)value) / 2)));
@@ -58,6 +58,10 @@ public void AdsMappingConfigurationShouldMapAdsStreamToDataObject()
mappedResult.IntArrayValue[2].Should().Be(102);
mappedResult.EnumStateValue.Should().Be(State.Running);
+
+ mappedResult.PlcVersion.Should().Be("21.08.30.0");
+ mappedResult.Utf7String.Should().Be("ÄÖö@Ü7");
+ mappedResult.UnicodeString.Should().Be("ÄÖö@Ü8");
}
[Fact]
@@ -65,7 +69,7 @@ public void AdsMappingConfigurationPerformance()
{
// Arrange
AdsMapperConfiguration config = new AdsMapperConfiguration(
- cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix('f', 'n', 'b', 'a', 'e'))
+ cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt", "s", "ws"))
.ForMember(dest => dest.DoubleValue4MappedName, opt => opt.MapFrom("fdoublevalue4"))
.ForMember(dest => dest.DoubleValue4MappedName, opt => opt.ConvertFromSourceUsing(value => ((double)value) / 2)));
AdsMapper mapper = config.CreateAdsMapper(_fakePlcData.AdsSymbolInfo);
@@ -161,7 +165,7 @@ public void AdsMappingConfigurationShouldMapDataObjectToAdsStream()
{
// Arrange
AdsMapperConfiguration config = new AdsMapperConfiguration(
- cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt"))
+ cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt", "s", "ws"))
.ForMember(dest => dest.DoubleValue4MappedName, opt => opt.MapFrom("fdoublevalue4"))
.ForMember(dest => dest.DoubleValue4MappedName, opt => opt.ConvertToSourceUsing((value, type) => value * 2)));
var dataObject = new DestinationDataObject
@@ -183,9 +187,12 @@ public void AdsMappingConfigurationShouldMapDataObjectToAdsStream()
PlcDateTimeValue1 = new DateTime(2018, 08, 30, 19, 33, 44),
IntArrayValue = new int[] { 100, 101, 102 },
EnumStateValue = State.Running,
+ PlcVersion = "21.08.30.0",
+ Utf7String = "ÄÖö@Ü7",
+ UnicodeString = "ÄÖö@Ü8",
};
byte[] expectedData = _fakePlcData.AdsStream.ToArray();
- Array.Clear(expectedData, 82, 8); // nested MotorObject
+ Array.Clear(expectedData, 114, 8); // nested MotorObject
// Act
AdsMapper mapper = config.CreateAdsMapper(_fakePlcData.AdsSymbolInfo);
@@ -217,6 +224,9 @@ private class DestinationDataObject
public DateTime PlcDateTimeValue1 { get; set; }
public int[] IntArrayValue { get; set; } = new int[3];
public State EnumStateValue { get; set; }
+ public string PlcVersion { get; set; }
+ public string Utf7String { get; set; }
+ public string UnicodeString { get; set; }
public Motor MotorObject { get; set; }
}
diff --git a/NET/Mbc.Ads.Mapper.Test/AdsMapperTestFakePlcData.cs b/NET/Mbc.Ads.Mapper.Test/AdsMapperTestFakePlcData.cs
index 9cce993..dc8f932 100644
--- a/NET/Mbc.Ads.Mapper.Test/AdsMapperTestFakePlcData.cs
+++ b/NET/Mbc.Ads.Mapper.Test/AdsMapperTestFakePlcData.cs
@@ -40,6 +40,9 @@ private IAdsSymbolInfo CreateFakeIAdsSymbolInfo()
dtPlcDateTimeValue1 : DATE_AND_TIME;
aIntArrayValue : ARRAY[0..2] OF DINT;
eEnumStateValue : E_State;
+ sPlcVersion : STRING(10) := '21.08.30.0';
+ sUtf7String : STRING(6) := 'ÄÖö@Ü7';
+ wsUnicodeString : WSTRING(6) := "ÄÖö@Ü8";
END_STRUCT
END_TYPE
@@ -55,15 +58,17 @@ private IAdsSymbolInfo CreateFakeIAdsSymbolInfo()
var tcAdsSymbol5Fake = A.Fake();
var subItemListFake_ST_Test = new List();
+ int streamSize = 122;
+
A.CallTo(() => tcAdsSymbol5Fake.Category).Returns(DataTypeCategory.Struct);
A.CallTo(() => tcAdsSymbol5Fake.Name).Returns("PCS_Status.aPcsTestPlace[1].stTest");
- A.CallTo(() => tcAdsSymbol5Fake.Size).Returns(90);
+ A.CallTo(() => tcAdsSymbol5Fake.Size).Returns(streamSize);
A.CallTo(() => tcAdsSymbol5Fake.DataType.Category).Returns(DataTypeCategory.Struct);
A.CallTo(() => tcAdsSymbol5Fake.DataType.HasSubItemInfo).Returns(true);
A.CallTo(() => tcAdsSymbol5Fake.DataType.SubItems).Returns(new ReadOnlySubItemCollection(subItemListFake_ST_Test));
IAdsSymbolInfo fakeSymbolInfo = A.Fake();
- A.CallTo(() => fakeSymbolInfo.SymbolsSize).Returns(90);
+ A.CallTo(() => fakeSymbolInfo.SymbolsSize).Returns(streamSize);
A.CallTo(() => fakeSymbolInfo.SymbolPath).Returns("PCS_Status.aPcsTestPlace[1].stTest");
A.CallTo(() => fakeSymbolInfo.Symbol).Returns(tcAdsSymbol5Fake);
@@ -344,6 +349,57 @@ private IAdsSymbolInfo CreateFakeIAdsSymbolInfo()
A.CallTo(() => subItemFake_eEnumStateValue.BaseType.BaseType.Size).Returns(2);
subItemListFake_ST_Test.Add(subItemFake_eEnumStateValue);
+ var subItemFake_sPlcVersion = A.Fake();
+ A.CallTo(() => subItemFake_sPlcVersion.SubItemName).Returns("sPlcVersion");
+ A.CallTo(() => subItemFake_sPlcVersion.Name).Returns("STRING(6)");
+ A.CallTo(() => subItemFake_sPlcVersion.Offset).Returns(82);
+ A.CallTo(() => subItemFake_sPlcVersion.Size).Returns(11);
+ A.CallTo(() => subItemFake_sPlcVersion.Category).Returns(DataTypeCategory.Alias);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.DataTypeId).Returns(AdsDatatypeId.ADST_STRING);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.ByteSize).Returns(11);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.Category).Returns(DataTypeCategory.String);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.IsPrimitive).Returns(true);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.IsString).Returns(true);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.ManagedType).Returns(typeof(System.String));
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.HasSubItemInfo).Returns(false);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.HasArrayInfo).Returns(false);
+ A.CallTo(() => subItemFake_sPlcVersion.BaseType.HasEnumInfo).Returns(false);
+ subItemListFake_ST_Test.Add(subItemFake_sPlcVersion);
+
+ var subItemFake_sUtf7String = A.Fake();
+ A.CallTo(() => subItemFake_sUtf7String.SubItemName).Returns("sUtf7String");
+ A.CallTo(() => subItemFake_sUtf7String.Name).Returns("STRING(6)");
+ A.CallTo(() => subItemFake_sUtf7String.Offset).Returns(93);
+ A.CallTo(() => subItemFake_sUtf7String.Size).Returns(7);
+ A.CallTo(() => subItemFake_sUtf7String.Category).Returns(DataTypeCategory.Alias);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.DataTypeId).Returns(AdsDatatypeId.ADST_STRING);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.ByteSize).Returns(7);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.Category).Returns(DataTypeCategory.String);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.IsPrimitive).Returns(true);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.IsString).Returns(true);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.ManagedType).Returns(typeof(System.String));
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.HasSubItemInfo).Returns(false);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.HasArrayInfo).Returns(false);
+ A.CallTo(() => subItemFake_sUtf7String.BaseType.HasEnumInfo).Returns(false);
+ subItemListFake_ST_Test.Add(subItemFake_sUtf7String);
+
+ var subItemFake_wsUnicodeString = A.Fake();
+ A.CallTo(() => subItemFake_wsUnicodeString.SubItemName).Returns("wsUnicodeString");
+ A.CallTo(() => subItemFake_wsUnicodeString.Name).Returns("WSTRING(6)");
+ A.CallTo(() => subItemFake_wsUnicodeString.Offset).Returns(100);
+ A.CallTo(() => subItemFake_wsUnicodeString.Size).Returns(14);
+ A.CallTo(() => subItemFake_wsUnicodeString.Category).Returns(DataTypeCategory.Alias);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.DataTypeId).Returns(AdsDatatypeId.ADST_WSTRING);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.ByteSize).Returns(14);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.Category).Returns(DataTypeCategory.String);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.IsPrimitive).Returns(false);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.IsString).Returns(false);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.ManagedType).Returns(typeof(System.String));
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.HasSubItemInfo).Returns(false);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.HasArrayInfo).Returns(false);
+ A.CallTo(() => subItemFake_wsUnicodeString.BaseType.HasEnumInfo).Returns(false);
+ subItemListFake_ST_Test.Add(subItemFake_wsUnicodeString);
+
return fakeSymbolInfo;
}
@@ -378,8 +434,13 @@ private AdsStream CreateAdsStream()
// Write enum
adsWriter.Write((ushort)2); // offset 80 (Enum of type INT16)
+ // Write string
+ adsWriter.WritePlcAnsiStringFixedLength("21.08.30.0", 11); // offset 82
+ adsWriter.WritePlcAnsiStringFixedLength("ÄÖö@Ü7", 7); // offset 93
+ adsWriter.WritePlcUnicodeStringFixedLength("ÄÖö@Ü8", 14); // offset 100
+
// Write Motor Object
- adsWriter.Write(double.MaxValue); // offset 82
+ adsWriter.Write(double.MaxValue); // offset 114
adsStream.Position = 0;
return adsStream;
diff --git a/NET/Mbc.Ads.Mapper/AdsMapperConfiguration.cs b/NET/Mbc.Ads.Mapper/AdsMapperConfiguration.cs
index 282bd0f..d39da87 100644
--- a/NET/Mbc.Ads.Mapper/AdsMapperConfiguration.cs
+++ b/NET/Mbc.Ads.Mapper/AdsMapperConfiguration.cs
@@ -93,8 +93,10 @@ private void AddSymbolsMappingRecursive(ITcAdsDataType item, int offset, string
}
break;
- case DataTypeCategory.Struct:
case DataTypeCategory.String:
+ AddStringSymbolsMapping(item, offset, name, mapper);
+ break;
+ case DataTypeCategory.Struct:
throw new NotImplementedException($"This Category type '{item.BaseType.Category}' used for PLC Varialbe {name} is yet not implemented.");
case DataTypeCategory.Alias:
// If alias call it recursive to find underlying primitive
@@ -124,10 +126,10 @@ private void AddPrimitiveSymbolsMapping(ITcAdsDataType adsDataType, int offset,
var definition = new AdsMappingDefinition(adsDataType);
definition.DestinationMemberConfiguration = dest;
- definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(primitiveManagedType, offset);
+ definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(primitiveManagedType, offset, adsDataType.BaseType);
definition.DataObjectValueSetter = DataObjectAccessor.CreateValueSetter(dest.Member);
- definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(primitiveManagedType, offset);
+ definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(primitiveManagedType, offset, adsDataType.BaseType);
definition.DataObjectValueGetter = DataObjectAccessor.CreateValueGetter(dest.Member);
mapper.AddStreamMapping(definition);
@@ -150,10 +152,10 @@ private void AddEnumSymbolsMapping(ITcAdsDataType adsDataType, int offset, strin
var definition = new AdsMappingDefinition(adsDataType);
definition.DestinationMemberConfiguration = dest;
- definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(adsDataType.BaseType.BaseType.ManagedType, offset);
+ definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(adsDataType.BaseType.BaseType.ManagedType, offset, adsDataType.BaseType.BaseType);
definition.DataObjectValueSetter = DataObjectAccessor.CreateValueSetter(dest.Member);
- definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(adsDataType.BaseType.BaseType.ManagedType, offset);
+ definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(adsDataType.BaseType.BaseType.ManagedType, offset, adsDataType.BaseType.BaseType);
definition.DataObjectValueGetter = DataObjectAccessor.CreateValueGetter(dest.Member);
mapper.AddStreamMapping(definition);
@@ -176,10 +178,10 @@ private void AddArraySymbolsMapping(ITcAdsDataType adsDataType, int offset, stri
int actStreamOffset = offset + (idx * arrayValueType.Size);
var capturedIdx = idx;
- definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(arrayValueType.ManagedType, actStreamOffset);
+ definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(arrayValueType.ManagedType, actStreamOffset, arrayValueType);
definition.DataObjectValueSetter = DataObjectAccessor.CreateValueSetter(dest.Member, arrayIndex: capturedIdx);
- definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(arrayValueType.ManagedType, actStreamOffset);
+ definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(arrayValueType.ManagedType, actStreamOffset, arrayValueType);
definition.DataObjectValueGetter = DataObjectAccessor.CreateValueGetter(dest.Member, arrayIndex: capturedIdx);
mapper.AddStreamMapping(definition);
@@ -187,6 +189,25 @@ private void AddArraySymbolsMapping(ITcAdsDataType adsDataType, int offset, stri
});
}
+ private void AddStringSymbolsMapping(ITcAdsDataType adsDataType, int offset, string name, AdsMapper mapper)
+ {
+ var memberMappingConfiguration = FindAdsMappingDefinition(name);
+
+ memberMappingConfiguration.Destination.MatchSome(dest =>
+ {
+ var definition = new AdsMappingDefinition(adsDataType);
+ definition.DestinationMemberConfiguration = dest;
+
+ definition.StreamReadFunction = AdsStreamAccessor.CreatePrimitiveTypeReadFunction(adsDataType.BaseType.ManagedType, offset, adsDataType.BaseType);
+ definition.DataObjectValueSetter = DataObjectAccessor.CreateValueSetter(dest.Member);
+
+ definition.StreamWriterFunction = AdsStreamAccessor.CreatePrimitiveTypeWriteFunction(adsDataType.BaseType.ManagedType, offset, adsDataType.BaseType);
+ definition.DataObjectValueGetter = DataObjectAccessor.CreateValueGetter(dest.Member);
+
+ mapper.AddStreamMapping(definition);
+ });
+ }
+
private MemberMappingConfiguration FindAdsMappingDefinition(string sourceSymbolName)
{
// Get Mapping configuration from source member name
diff --git a/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs b/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs
index 1e78dd7..a01d5e5 100644
--- a/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs
+++ b/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs
@@ -7,6 +7,7 @@
using System;
using TwinCAT.Ads;
using TwinCAT.PlcOpen;
+using TwinCAT.TypeSystem;
namespace Mbc.Ads.Mapper
{
@@ -20,8 +21,9 @@ internal static class AdsStreamAccessor
///
/// The .NET type to read
/// The offset of the subitem in bytes
+ /// Source Datatype information of the source ITcAdsDataType
/// A function to read a primitive value from the given ADS reader (not null).
- public static Func CreatePrimitiveTypeReadFunction(Type managedType, int streamByteOffset)
+ public static Func CreatePrimitiveTypeReadFunction(Type managedType, int streamByteOffset, ITcAdsDataType sourceDatatype)
{
// Guards
Ensure.Any.IsNotNull(managedType, optsFn: opts => opts.WithMessage("Could not create AdsStreamMappingDelegate for a PrimitiveType because the managedType is null."));
@@ -94,6 +96,18 @@ public static Func CreatePrimitiveTypeReadFunction(Type
return (adsReader) => Position(adsReader, streamByteOffset).ReadPlcTIME();
}
+ if (managedType == typeof(string) && sourceDatatype.DataTypeId == AdsDatatypeId.ADST_STRING)
+ {
+ // Example: byteSize = 81; // Size of 80 ANSI chars + /0 (STRING[80])
+ return (adsReader) => Position(adsReader, streamByteOffset).ReadPlcString(sourceDatatype.ByteSize, System.Text.Encoding.UTF7);
+ }
+
+ if (managedType == typeof(string) && sourceDatatype.DataTypeId == AdsDatatypeId.ADST_WSTRING)
+ {
+ // Example: byteSize = 2 * 81; // Size of 80 UNICODE chars + /0 (WSTRING[80])
+ return (adsReader) => Position(adsReader, streamByteOffset).ReadPlcString(sourceDatatype.ByteSize, System.Text.Encoding.Unicode);
+ }
+
throw new NotSupportedException($"AdsStreamMappingDelegate execution not supported for the ManagedType '{managedType?.ToString()}'.");
}
@@ -108,8 +122,9 @@ private static AdsBinaryReader Position(AdsBinaryReader adsReader, int offset)
///
/// The ADS managed type to write.
/// The offset of the subtime in bytes.
+ /// Size information of the source ITcAdsDataType
/// A function (=Action) to write a primitive value to a given ADS writer (not null).
- public static Action CreatePrimitiveTypeWriteFunction(Type managedType, int streamByteOffset)
+ public static Action CreatePrimitiveTypeWriteFunction(Type managedType, int streamByteOffset, ITcAdsDataType sourceDatatype)
{
// Guards
Ensure.Any.IsNotNull(managedType, optsFn: opts => opts.WithMessage("Could not create AdsStreamMappingDelegate for a PrimitiveType because the managedType is null."));
@@ -182,6 +197,28 @@ public static Action CreatePrimitiveTypeWriteFunction(T
return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcType(new TOD((TimeSpan)value).Time);
}
+ if (managedType == typeof(string) && sourceDatatype.DataTypeId == AdsDatatypeId.ADST_STRING)
+ {
+ if (sourceDatatype.ByteSize <= 1)
+ {
+ throw new NotSupportedException($"AdsStreamMappingDelegate execution not possible for the ManagedType '{managedType?.ToString()}' with a total ByteSize of '{sourceDatatype.ByteSize}'.");
+ }
+
+ // Writing the string without the termination of /0
+ return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcAnsiStringFixedLength((string)value, sourceDatatype.ByteSize);
+ }
+
+ if (managedType == typeof(string) && sourceDatatype.DataTypeId == AdsDatatypeId.ADST_WSTRING)
+ {
+ if (sourceDatatype.ByteSize <= 2)
+ {
+ throw new NotSupportedException($"AdsStreamMappingDelegate execution not possible for the ManagedType '{managedType?.ToString()}' with a total ByteSize of '{sourceDatatype.ByteSize}'.");
+ }
+
+ // Writing the string with the termination of /0
+ return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcUnicodeStringFixedLength((string)value, sourceDatatype.ByteSize);
+ }
+
throw new NotSupportedException($"AdsStreamMappingDelegate execution not supported for the ManagedType '{managedType?.ToString()}'.");
}
diff --git a/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj b/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj
index c5ed9c0..13a08a6 100644
--- a/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj
+++ b/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj
@@ -3,7 +3,7 @@
net471
- 0.4.1
+ 0.4.2
Mbc.Ads.Mapper
@@ -11,7 +11,7 @@
-
+
diff --git a/NET/Mbc.Pcs.Net.Command.Test/PlcCommandAsyncTests.cs b/NET/Mbc.Pcs.Net.Command.Test/PlcCommandAsyncTests.cs
index abb6cc7..2bf7abf 100644
--- a/NET/Mbc.Pcs.Net.Command.Test/PlcCommandAsyncTests.cs
+++ b/NET/Mbc.Pcs.Net.Command.Test/PlcCommandAsyncTests.cs
@@ -354,7 +354,7 @@ public async Task ExecuteAsyncExecutionBehaviorIsLockShouldLockingExecutionOrder
await Task.WhenAll(tasks);
// Assert
- lastCommand.Should().Be(2);
+ lastCommand.Should().BeCloseTo(2, 1);
}
[Fact]
diff --git a/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj b/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj
index fae95ee..ae6706c 100644
--- a/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj
+++ b/NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj
@@ -2,7 +2,7 @@
net471
- 2.2.0
+ 2.2.1
Mbc.Pcs.Net.Command
diff --git a/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj b/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj
index 4fdb964..8b99fb7 100644
--- a/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj
+++ b/NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj
@@ -2,7 +2,7 @@
net471
- 2.1.0
+ 2.1.1
Mbc.Pcs.Net
diff --git a/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs b/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs
index 744a602..f3ddd5f 100644
--- a/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs
+++ b/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs
@@ -24,6 +24,7 @@ public void SerializeTypes()
Enum = EnumTest.Value2,
FloatArray = new float[] { 10, 11 },
Float2Array = new float[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } },
+ String = "Hallo Welt",
};
var persister = new BinaryObjectPersister();
var stream = new MemoryStream();
@@ -45,6 +46,7 @@ public void SerializeTypes()
0x01, 0x00, 0x00, 0x00, // Enum
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x30, 0x41, // Float-Array
0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xc0, 0x40, // Float2-Array
+ 0x0a, 0x00, 0x00, 0x00, 0x48, 0x61, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x65, 0x6c, 0x74, // string
});
}
@@ -63,7 +65,8 @@ public void DeserializeTypes()
0xff, 0xff, 0xff, 0xfe, // UInt
0x01, 0x00, 0x00, 0x00, // Enum
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x30, 0x41, // Float-Array
- 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xc0, 0x40,// Float2-Array
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xc0, 0x40, // Float2-Array
+ 0x0a, 0x00, 0x00, 0x00, 0x48, 0x61, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x65, 0x6c, 0x74, // string
});
var persister = new BinaryObjectPersister();
@@ -81,6 +84,86 @@ public void DeserializeTypes()
value.Enum.Should().Be(EnumTest.Value2);
value.FloatArray.Should().BeEquivalentTo(10, 11);
value.Float2Array.Should().BeEquivalentTo(new float[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } });
+ value.String.Should().Be("Hallo Welt");
+ }
+
+ [Fact]
+ public void SerializeTypesWithNullStringValue()
+ {
+ // Arrange
+ var value = new Values
+ {
+ DateTime = new DateTime(1234),
+ Bool = true,
+ Float = 42,
+ Byte = 15,
+ UShort = 4,
+ Int = -1,
+ UInt = 0xFEFFFFFF,
+ Enum = EnumTest.Value2,
+ FloatArray = new float[] { 10, 11 },
+ Float2Array = new float[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } },
+ String = null,
+ };
+ var persister = new BinaryObjectPersister();
+ var stream = new MemoryStream();
+
+ // Act
+ persister.Serialize(value, stream);
+ stream.Position = 0;
+
+ // Assert
+ stream.ToArray().Should().BeEquivalentTo(new byte[]
+ {
+ 0xd2, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DateTime
+ 0x01, // Bool,
+ 0x00, 0x00, 0x28, 0x42, // Float
+ 0x0F, // Byte
+ 0x04, 0x00, // UShort
+ 0xff, 0xff, 0xff, 0xff, // Int
+ 0xff, 0xff, 0xff, 0xfe, // UInt
+ 0x01, 0x00, 0x00, 0x00, // Enum
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x30, 0x41, // Float-Array
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xc0, 0x40, // Float2-Array
+ 0x00, 0x00, 0x00, 0x00, // string
+ });
+ }
+
+ [Fact]
+ public void DeserializeTypesWithNullStringValue()
+ {
+ // Arrange
+ var stream = new MemoryStream(new byte[]
+ {
+ 0xd2, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // DateTime
+ 0x01, // Bool,
+ 0x00, 0x00, 0x28, 0x42, // Float
+ 0x0F, // Byte
+ 0x04, 0x00, // UShort
+ 0xff, 0xff, 0xff, 0xff, // Int
+ 0xff, 0xff, 0xff, 0xfe, // UInt
+ 0x01, 0x00, 0x00, 0x00, // Enum
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x41, 0x00, 0x00, 0x30, 0x41, // Float-Array
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xc0, 0x40, // Float2-Array
+ 0x00, 0x00, 0x00, 0x00, // string
+ });
+ var persister = new BinaryObjectPersister();
+
+ // Act
+ var value = (Values)persister.Deserialize(stream);
+
+ // Assert
+ value.DateTime.Should().Be(new DateTime(1234));
+ value.Bool.Should().BeTrue();
+ value.Float.Should().Be(42);
+ value.Byte.Should().Be(15);
+ value.UShort.Should().Be(4);
+ value.Int.Should().Be(-1);
+ value.UInt.Should().Be(0xFEFFFFFF);
+ value.Enum.Should().Be(EnumTest.Value2);
+ value.FloatArray.Should().BeEquivalentTo(10, 11);
+ value.Float2Array.Should().BeEquivalentTo(new float[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } });
+ value.String.Should().Be(string.Empty);
}
internal class Values
@@ -104,6 +187,8 @@ internal class Values
public float[] FloatArray { get; set; }
public float[,] Float2Array { get; set; }
+
+ public string String { get; set; }
}
public enum EnumTest
diff --git a/NET/Mbc.Pcs.Net.sln b/NET/Mbc.Pcs.Net.sln
index feb5574..daee1d4 100644
--- a/NET/Mbc.Pcs.Net.sln
+++ b/NET/Mbc.Pcs.Net.sln
@@ -55,6 +55,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mbc.Pcs.Net.Command.Test",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Profiling", "Profiling\Profiling.csproj", "{6718F565-F3E0-4F0A-8A0B-FE85F9355E10}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdsMapperCli", "AdsMapperCli\AdsMapperCli.csproj", "{6C610A13-BD3D-4B55-A134-E5D5416B8569}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -126,6 +128,10 @@ Global
{6718F565-F3E0-4F0A-8A0B-FE85F9355E10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6718F565-F3E0-4F0A-8A0B-FE85F9355E10}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6718F565-F3E0-4F0A-8A0B-FE85F9355E10}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C610A13-BD3D-4B55-A134-E5D5416B8569}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -134,6 +140,7 @@ Global
{08056A54-605C-4ACC-ADD7-3DB109298C28} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23}
{9E261388-7847-46BD-8D2F-7E928279AB89} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23}
{6718F565-F3E0-4F0A-8A0B-FE85F9355E10} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23}
+ {6C610A13-BD3D-4B55-A134-E5D5416B8569} = {7FC136A2-182C-44F9-AF35-0AA0013BEB23}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {41EBCC31-1BB3-4F43-BB31-8BD9166775A7}
diff --git a/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs b/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs
index d28f1b7..c378b83 100644
--- a/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs
+++ b/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs
@@ -80,6 +80,11 @@ private static Action