From 9c66c1d76df7a284b6b47ed48162c9d95e84e366 Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Thu, 5 Aug 2021 11:47:12 +0200 Subject: [PATCH 1/7] add sample for automaper --- NET/AdsMapperCli/AdsMapperCli.csproj | 17 ++++ NET/AdsMapperCli/DestinationDataObject.cs | 89 +++++++++++++++++ NET/AdsMapperCli/Program.cs | 99 +++++++++++++++++++ NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs | 2 +- NET/Mbc.Pcs.Net.sln | 7 ++ .../State/PlcAdsStateReaderConfig.cs | 6 ++ 6 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 NET/AdsMapperCli/AdsMapperCli.csproj create mode 100644 NET/AdsMapperCli/DestinationDataObject.cs create mode 100644 NET/AdsMapperCli/Program.cs 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..9a80b29 --- /dev/null +++ b/NET/AdsMapperCli/DestinationDataObject.cs @@ -0,0 +1,89 @@ +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; + 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 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..ce54a08 --- /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"))), + 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 }; + 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..587f024 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")) .ForMember(dest => dest.DoubleValue4MappedName, opt => opt.MapFrom("fdoublevalue4")) .ForMember(dest => dest.DoubleValue4MappedName, opt => opt.ConvertFromSourceUsing(value => ((double)value) / 2))); 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/State/PlcAdsStateReaderConfig.cs b/NET/Mbc.Pcs.Net/State/PlcAdsStateReaderConfig.cs index 33e1ec9..00935cb 100644 --- a/NET/Mbc.Pcs.Net/State/PlcAdsStateReaderConfig.cs +++ b/NET/Mbc.Pcs.Net/State/PlcAdsStateReaderConfig.cs @@ -20,8 +20,14 @@ public struct PlcAdsStateReaderConfig public AdsMapperConfiguration AdsMapperConfiguration { get; set; } + /// + /// Cycle time the ADS Server notifies for new plc data + /// public TimeSpan CycleTime { get; set; } + /// + /// Repporting Intervall of the . A Reportings contains a list of data in the time of + /// public TimeSpan MaxDelay { get; set; } } } From d2bf849f0cb5532de3f3f89631aaa701eb277bfd Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Thu, 5 Aug 2021 13:18:14 +0200 Subject: [PATCH 2/7] Support string and wstring in ADS Mapper --- NET/AdsMapperCli/DestinationDataObject.cs | 8 ++++ NET/AdsMapperCli/Program.cs | 4 +- NET/Mbc.Ads.Mapper/AdsMapperConfiguration.cs | 35 ++++++++++++---- NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs | 42 +++++++++++++++++++- NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj | 2 +- 5 files changed, 79 insertions(+), 12 deletions(-) diff --git a/NET/AdsMapperCli/DestinationDataObject.cs b/NET/AdsMapperCli/DestinationDataObject.cs index 9a80b29..a67e8b7 100644 --- a/NET/AdsMapperCli/DestinationDataObject.cs +++ b/NET/AdsMapperCli/DestinationDataObject.cs @@ -23,6 +23,10 @@ namespace AdsMapperCli 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'; + sPlcVersion : STRING(10) := '21.08.30.0'; + sUtf7String : STRING(6) := 'ÄÖö@Ü7'; + wsUnicodeString : WSTRING(6) := "ÄÖö@Ü8"; END_STRUCT END_TYPE @@ -71,6 +75,10 @@ public class DestinationDataObject : IPlcState 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/AdsMapperCli/Program.cs b/NET/AdsMapperCli/Program.cs index ce54a08..91f1704 100644 --- a/NET/AdsMapperCli/Program.cs +++ b/NET/AdsMapperCli/Program.cs @@ -26,7 +26,7 @@ public static void Main(string[] args) { VariablePath = $"PCS_Status.stTest", AdsMapperConfiguration = new AdsMapperConfiguration( - cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt", "s"))), + cfg => cfg.ForAllSourceMember(opt => opt.RemovePrefix("f", "n", "b", "a", "e", "t", "d", "dt", "s", "ws"))), CycleTime = TimeSpan.FromMilliseconds(2), MaxDelay = TimeSpan.FromMilliseconds(500), }; @@ -89,7 +89,7 @@ private static void OnConnectionStateChanged(object sender, PlcConnectionChangeA private static void OnPlcStatesChange(object source, PlcMultiStateChangedEventArgs testPlaceStatusEvent) { - var config = new System.Text.Json.JsonSerializerOptions() { WriteIndented = true }; + 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"); 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..621df8e 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,29 @@ 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).WritePlcString((string)value, sourceDatatype.ByteSize - 1, System.Text.Encoding.UTF7); + } + + 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 without the termination of /0 + int size = (sourceDatatype.ByteSize / 2) - 1; + return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcString((string)value, size, System.Text.Encoding.Unicode); + } + 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 b213410..25cb050 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.0 + 0.4.1 Mbc.Ads.Mapper From 9f62a3a457c832d1dc12965e0e9414054f24ec82 Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Thu, 5 Aug 2021 14:10:43 +0200 Subject: [PATCH 3/7] Expand unit test for string and wsstring --- NET/AdsMapperCli/DestinationDataObject.cs | 1 - NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs | 18 +++-- .../AdsMapperTestFakePlcData.cs | 67 ++++++++++++++++++- NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs | 7 +- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/NET/AdsMapperCli/DestinationDataObject.cs b/NET/AdsMapperCli/DestinationDataObject.cs index a67e8b7..be78687 100644 --- a/NET/AdsMapperCli/DestinationDataObject.cs +++ b/NET/AdsMapperCli/DestinationDataObject.cs @@ -23,7 +23,6 @@ namespace AdsMapperCli 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'; sPlcVersion : STRING(10) := '21.08.30.0'; sUtf7String : STRING(6) := 'ÄÖö@Ü7'; wsUnicodeString : WSTRING(6) := "ÄÖö@Ü8"; diff --git a/NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs b/NET/Mbc.Ads.Mapper.Test/AdsMapperTest.cs index 587f024..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", "t", "d", "dt", "s")) + 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/AdsStreamAccessor.cs b/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs index 621df8e..a01d5e5 100644 --- a/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs +++ b/NET/Mbc.Ads.Mapper/AdsStreamAccessor.cs @@ -205,7 +205,7 @@ public static Action CreatePrimitiveTypeWriteFunction(T } // Writing the string without the termination of /0 - return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcString((string)value, sourceDatatype.ByteSize - 1, System.Text.Encoding.UTF7); + return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcAnsiStringFixedLength((string)value, sourceDatatype.ByteSize); } if (managedType == typeof(string) && sourceDatatype.DataTypeId == AdsDatatypeId.ADST_WSTRING) @@ -215,9 +215,8 @@ public static Action CreatePrimitiveTypeWriteFunction(T 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 - int size = (sourceDatatype.ByteSize / 2) - 1; - return (adsWriter, value) => Position(adsWriter, streamByteOffset).WritePlcString((string)value, size, System.Text.Encoding.Unicode); + // 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()}'."); From 36c105ff9384ae873b44661fa9c827e7b0c0fc77 Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Fri, 6 Aug 2021 09:32:38 +0200 Subject: [PATCH 4/7] Support strings in BinaryObjectPersister --- .../PlcCommandAsyncTests.cs | 2 +- .../DataRecorder/BinaryObjectPersisterTest.cs | 8 ++++- .../DataRecorder/BinaryObjectPersister.cs | 32 +++++++++++++++++++ NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj | 2 +- 4 files changed, 41 insertions(+), 3 deletions(-) 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.Test/DataRecorder/BinaryObjectPersisterTest.cs b/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs index 744a602..03d4a3d 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,7 @@ 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"); } internal class Values @@ -104,6 +108,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/DataRecorder/BinaryObjectPersister.cs b/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs index d28f1b7..5ed7512 100644 --- a/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs +++ b/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs @@ -80,6 +80,11 @@ private static Action GetWriteDispatcher(Type type) return WriteDispatchUInt; } + if (type == typeof(string)) + { + return WriteDispatchString; + } + if (type.IsEnum) { return GetWriteDispatcher(type.GetEnumUnderlyingType()); @@ -123,6 +128,18 @@ private static void WriteDispatchUInt(object value, BinaryWriter writer) writer.Write((uint)value); } + /// + /// Prefix the binary stream with the size 4-byte long. + /// + private static void WriteDispatchString(object value, BinaryWriter writer) + { + string text = (string)value; + byte[] bytes = System.Text.Encoding.ASCII.GetBytes(text); + Int32 size = text.Length; + writer.Write(size); + writer.Write(bytes); + } + private static void WriteDispatchArray(Action elementDispatcher, object value, BinaryWriter writer) { Array array = (Array)value; @@ -188,6 +205,11 @@ private static Func GetReadDispatcher(Type type) return (BinaryReader r) => ReadDispatchEnum(primitivReadDispatch, type, r); } + if (type == typeof(string)) + { + return ReadDispatchString; + } + throw new InvalidOperationException($"Unsupported type {type}."); } @@ -226,6 +248,16 @@ private static object ReadDispatchUInt(BinaryReader reader) return reader.ReadUInt32(); } + /// + /// the binary stream must be prefixed with the size 4-byte long. + /// + private static object ReadDispatchString(BinaryReader reader) + { + var size = reader.ReadInt32(); + var bytes = reader.ReadBytes(size); + return System.Text.Encoding.ASCII.GetString(bytes); + } + private static object ReadDispatchEnum(Func primitivReadDispatch, Type type, BinaryReader reader) { var primitiveValue = primitivReadDispatch(reader); diff --git a/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj b/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj index 653423e..c001ccc 100644 --- a/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj +++ b/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj @@ -2,7 +2,7 @@ net471 - 3.1.0 + 3.1.1 Mbc.Pcs.Net From 048b2767ececf4cbb12abad31f80213e2fe56562 Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Fri, 6 Aug 2021 16:55:16 +0200 Subject: [PATCH 5/7] BinaryObjectPersister handles string when it is null --- .../DataRecorder/BinaryObjectPersisterTest.cs | 79 +++++++++++++++++++ .../DataRecorder/BinaryObjectPersister.cs | 2 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs b/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs index 03d4a3d..f3ddd5f 100644 --- a/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs +++ b/NET/Mbc.Pcs.Net.Test/DataRecorder/BinaryObjectPersisterTest.cs @@ -87,6 +87,85 @@ public void DeserializeTypes() 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 { public DateTime DateTime { get; set; } diff --git a/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs b/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs index 5ed7512..c378b83 100644 --- a/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs +++ b/NET/Mbc.Pcs.Net/DataRecorder/BinaryObjectPersister.cs @@ -133,7 +133,7 @@ private static void WriteDispatchUInt(object value, BinaryWriter writer) /// private static void WriteDispatchString(object value, BinaryWriter writer) { - string text = (string)value; + string text = (string)value ?? string.Empty; byte[] bytes = System.Text.Encoding.ASCII.GetBytes(text); Int32 size = text.Length; writer.Write(size); From 3c81fd1cead397547baa68e8f88cf34e08de1912 Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Fri, 6 Aug 2021 17:28:49 +0200 Subject: [PATCH 6/7] Add Github Action CI Script --- .github/workflows | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows 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 From a2e898bbef1cc44da10dfaacc769a13c08f285f4 Mon Sep 17 00:00:00 2001 From: Michael Helfenstein Date: Tue, 17 Aug 2021 18:53:12 +0200 Subject: [PATCH 7/7] update ensure.that --- NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj | 6 +++--- NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj | 4 ++-- NET/Mbc.Pcs.Net.Command/Mbc.Pcs.Net.Command.csproj | 2 +- NET/Mbc.Pcs.Net.Test.Util/Mbc.Pcs.Net.Test.Util.csproj | 2 +- NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj b/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj index 25cb050..13a08a6 100644 --- a/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj +++ b/NET/Mbc.Ads.Mapper/Mbc.Ads.Mapper.csproj @@ -3,15 +3,15 @@ net471 - 0.4.1 + 0.4.2 Mbc.Ads.Mapper - - + + diff --git a/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj b/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj index d78d4c1..efe9d60 100644 --- a/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj +++ b/NET/Mbc.Ads.Utils/Mbc.Ads.Utils.csproj @@ -3,7 +3,7 @@ net471 - 0.5.0 + 0.5.1 Mbc.Ads.Utils @@ -11,6 +11,6 @@ - + \ No newline at end of file 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/Mbc.Pcs.Net.csproj b/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj index c001ccc..b034107 100644 --- a/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj +++ b/NET/Mbc.Pcs.Net/Mbc.Pcs.Net.csproj @@ -2,14 +2,14 @@ net471 - 3.1.1 + 3.1.2 Mbc.Pcs.Net - +