From 8df1a9c8cb71e6667cd63a4100f2f6caf96cde4c Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Mon, 4 Oct 2021 18:53:13 +0200 Subject: [PATCH 1/4] Add unit test for ReadClass uint32 bug. https://github.com/S7NetPlus/s7netplus/issues/414 --- S7.Net.UnitTest/TypeTests/ClassTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/S7.Net.UnitTest/TypeTests/ClassTests.cs b/S7.Net.UnitTest/TypeTests/ClassTests.cs index ba99c2cc..03cbe449 100644 --- a/S7.Net.UnitTest/TypeTests/ClassTests.cs +++ b/S7.Net.UnitTest/TypeTests/ClassTests.cs @@ -17,6 +17,19 @@ public void GetClassSizeTest() Assert.AreEqual(Class.GetClassSize(new TestClassUnevenSize(3, 17)), 10); } + /// + /// Ensure Uint32 is correctly parsed through ReadClass functions. Adresses issue https://github.com/S7NetPlus/s7netplus/issues/414 + /// + [TestMethod] + public void TestUint32Read() + { + var result = new TestUint32(); + var data = new byte[4] { 0, 0, 0, 5 }; + var bytesRead = Class.FromBytes(result, data); + Assert.AreEqual(bytesRead, data.Length); + Assert.AreEqual(5u, result.Value1); + } + private class TestClassUnevenSize { public bool Bool { get; set; } @@ -29,5 +42,10 @@ public TestClassUnevenSize(int byteCount, int bitCount) Bools = new bool[bitCount]; } } + + private class TestUint32 + { + public uint Value1 { get; set; } + } } } From 12281ec8020dfad274a84ece094eee60ef5b4940 Mon Sep 17 00:00:00 2001 From: Serge Camille Date: Mon, 4 Oct 2021 18:59:05 +0200 Subject: [PATCH 2/4] Fix ReadClass for Uint32 Use consistent DWord conversion for both Int32 and UInt32. Unfortunately there is no Span or even a FromByteArray function accepting a offset, so just use the same Array.Copy falls used for double. --- S7.Net/Types/Class.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 0dd78fa3..225f3f8c 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -153,11 +153,9 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; - // hier auswerten - uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], - bytes[(int)numBytes + 2], - bytes[(int)numBytes + 1], - bytes[(int)numBytes + 0]); + var wordBuffer = new byte[4]; + Array.Copy(bytes, (int)numBytes, wordBuffer, 0, wordBuffer.Length); + uint sourceUInt = DWord.FromByteArray(wordBuffer); value = sourceUInt.ConvertToInt(); numBytes += 4; break; @@ -165,12 +163,9 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) numBytes++; - // hier auswerten - value = DWord.FromBytes( - bytes[(int)numBytes], - bytes[(int)numBytes + 1], - bytes[(int)numBytes + 2], - bytes[(int)numBytes + 3]); + var wordBuffer2 = new byte[4]; + Array.Copy(bytes, (int)numBytes, wordBuffer2, 0, wordBuffer2.Length); + value = DWord.FromByteArray(wordBuffer2); numBytes += 4; break; case "Single": From 142f1ba90eed639e7b3a7063f6522b5a444de86d Mon Sep 17 00:00:00 2001 From: Mike Cremer <8211507+MCPC10@users.noreply.github.com> Date: Sat, 10 Dec 2022 18:59:25 +0100 Subject: [PATCH 3/4] Added support string/wstring for class type --- S7.Net.UnitTest/Helpers/TestClass.cs | 13 ++++ S7.Net.UnitTest/S7NetTestsAsync.cs | 29 +++++++-- S7.Net.UnitTest/S7NetTestsSync.cs | 19 ++++++ S7.Net/Types/Class.cs | 97 +++++++++++++++++----------- S7.Net/Types/S7StringAttribute.cs | 2 +- 5 files changed, 115 insertions(+), 45 deletions(-) diff --git a/S7.Net.UnitTest/Helpers/TestClass.cs b/S7.Net.UnitTest/Helpers/TestClass.cs index c810adce..2f2c7750 100644 --- a/S7.Net.UnitTest/Helpers/TestClass.cs +++ b/S7.Net.UnitTest/Helpers/TestClass.cs @@ -1,4 +1,6 @@  +using S7.Net.Types; + namespace S7.Net.UnitTest.Helpers { class TestClass @@ -51,5 +53,16 @@ class TestClass /// DB1.DBD16 /// public ushort DWordVariable { get; set; } + + /// + /// DB1.DBX20.0 + /// + [S7String(S7StringType.S7WString, 10)] + public string WStringVariable { get; set; } + /// + /// DB1.DBX44.0 + /// + [S7String(S7StringType.S7String, 10)] + public string StringVariable { get; set; } } } diff --git a/S7.Net.UnitTest/S7NetTestsAsync.cs b/S7.Net.UnitTest/S7NetTestsAsync.cs index f94ddb81..731e802f 100644 --- a/S7.Net.UnitTest/S7NetTestsAsync.cs +++ b/S7.Net.UnitTest/S7NetTestsAsync.cs @@ -7,6 +7,7 @@ using S7.UnitTest.Helpers; using System.Threading.Tasks; using System.Threading; +using System.Security.Cryptography; #endregion @@ -154,7 +155,9 @@ public async Task Test_Async_ReadAndWriteClass() IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; await plc.WriteClassAsync(tc, DB2); @@ -168,6 +171,8 @@ public async Task Test_Async_ReadAndWriteClass() Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable); Assert.AreEqual(tc.RealVariable, tc2.RealVariable); Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable); + Assert.AreEqual(tc.WStringVariable, tc2.WStringVariable); + Assert.AreEqual(tc.StringVariable, tc2.StringVariable); } [TestMethod] @@ -580,7 +585,9 @@ public async Task Test_Async_ReadClassIgnoresNonPublicSetters() IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; await plc.WriteClassAsync(tc, DB2); @@ -628,7 +635,10 @@ public async Task Test_Async_ReadClassWithGenericReturnsSameResultAsReadClassWit IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" + }; await plc.WriteClassAsync(tc, DB2); @@ -646,6 +656,9 @@ public async Task Test_Async_ReadClassWithGenericReturnsSameResultAsReadClassWit Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3)); Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable); Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable); + Assert.AreEqual(tc2.WStringVariable, tc2Generic.WStringVariable); + Assert.AreEqual(tc2.StringVariable, tc2Generic.StringVariable); + } [TestMethod] @@ -671,7 +684,9 @@ public async Task Test_Async_ReadClassWithGenericAndClassFactoryReturnsSameResul IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; await plc.WriteClassAsync(tc, DB2); @@ -686,6 +701,8 @@ public async Task Test_Async_ReadClassWithGenericAndClassFactoryReturnsSameResul Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3)); Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable); Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable); + Assert.AreEqual(tc2Generic.WStringVariable, tc2GenericWithClassFactory.WStringVariable); + Assert.AreEqual(tc2Generic.StringVariable, tc2GenericWithClassFactory.StringVariable); } [TestMethod] @@ -792,7 +809,9 @@ public async Task Test_Async_ReadClassReturnsNumberOfReadBytesFromThePlc() IntVariable = -15000, LRealVariable = -154.789, RealVariable = -154.789f, - DWordVariable = 850 + DWordVariable = 850, + WStringVariable = "ÄÜÉÊéà", + StringVariable = "Hallo" }; plc.WriteClass(tc, DB2); diff --git a/S7.Net.UnitTest/S7NetTestsSync.cs b/S7.Net.UnitTest/S7NetTestsSync.cs index c84c1a70..76317c0f 100644 --- a/S7.Net.UnitTest/S7NetTestsSync.cs +++ b/S7.Net.UnitTest/S7NetTestsSync.cs @@ -5,6 +5,7 @@ using S7.Net.UnitTest.Helpers; using S7.Net.Types; using S7.UnitTest.Helpers; +using System.Security.Cryptography; #endregion @@ -183,6 +184,9 @@ public void T04_ReadAndWriteClass() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; + plc.WriteClass(tc, DB2); TestClass tc2 = new TestClass(); // Values that are read from a class are stored inside the class itself, that is passed by reference @@ -194,6 +198,8 @@ public void T04_ReadAndWriteClass() Assert.AreEqual(tc.LRealVariable, tc2.LRealVariable); Assert.AreEqual(tc.RealVariable, tc2.RealVariable); Assert.AreEqual(tc.DWordVariable, tc2.DWordVariable); + Assert.AreEqual(tc.WStringVariable, tc2.WStringVariable); + Assert.AreEqual(tc.StringVariable, tc2.StringVariable); } /// @@ -577,6 +583,8 @@ public void T12_ReadClassIgnoresNonPublicSetters() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; plc.WriteClass(tc, DB2); @@ -622,6 +630,8 @@ public void T14_ReadClassWithGenericReturnsSameResultAsReadClassWithoutGeneric() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; plc.WriteClass(tc, DB2); @@ -637,6 +647,8 @@ public void T14_ReadClassWithGenericReturnsSameResultAsReadClassWithoutGeneric() Assert.AreEqual(Math.Round(tc2.LRealVariable, 3), Math.Round(tc2Generic.LRealVariable, 3)); Assert.AreEqual(tc2.RealVariable, tc2Generic.RealVariable); Assert.AreEqual(tc2.DWordVariable, tc2Generic.DWordVariable); + Assert.AreEqual(tc2.WStringVariable, tc2Generic.WStringVariable); + Assert.AreEqual(tc2.StringVariable, tc2Generic.StringVariable); } [TestMethod, ExpectedException(typeof(PlcException))] @@ -665,6 +677,8 @@ public void T16_ReadClassWithGenericAndClassFactoryReturnsSameResultAsReadClassW tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; plc.WriteClass(tc, DB2); @@ -679,6 +693,8 @@ public void T16_ReadClassWithGenericAndClassFactoryReturnsSameResultAsReadClassW Assert.AreEqual(Math.Round(tc2Generic.LRealVariable, 3), Math.Round(tc2GenericWithClassFactory.LRealVariable, 3)); Assert.AreEqual(tc2Generic.RealVariable, tc2GenericWithClassFactory.RealVariable); Assert.AreEqual(tc2Generic.DWordVariable, tc2GenericWithClassFactory.DWordVariable); + Assert.AreEqual(tc2Generic.WStringVariable, tc2GenericWithClassFactory.WStringVariable); + Assert.AreEqual(tc2Generic.StringVariable, tc2GenericWithClassFactory.StringVariable); } [TestMethod, ExpectedException(typeof(PlcException))] @@ -837,6 +853,9 @@ public void T21_ReadClassReturnsNumberOfReadBytesFromThePlc() tc.LRealVariable = -154.789; tc.RealVariable = -154.789f; tc.DWordVariable = 850; + tc.WStringVariable = "ÄÜÉÊéà"; + tc.StringVariable = "Hallo"; + plc.WriteClass(tc, DB2); int expectedReadBytes = (int)Types.Class.GetClassSize(tc); diff --git a/S7.Net/Types/Class.cs b/S7.Net/Types/Class.cs index 0dd78fa3..1aefc002 100644 --- a/S7.Net/Types/Class.cs +++ b/S7.Net/Types/Class.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; namespace S7.Net.Types { @@ -25,7 +26,7 @@ private static IEnumerable GetAccessableProperties(Type classType) } - private static double GetIncreasedNumberOfBytes(double numBytes, Type type) + private static double GetIncreasedNumberOfBytes(double numBytes, Type type, PropertyInfo? propertyInfo) { switch (type.Name) { @@ -38,30 +39,30 @@ private static double GetIncreasedNumberOfBytes(double numBytes, Type type) break; case "Int16": case "UInt16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 2; break; case "Int32": case "UInt32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 4; break; case "Single": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 4; break; case "Double": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); numBytes += 8; break; + case "String": + S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + IncrementToEven(ref numBytes); + numBytes += attribute.ReservedLengthInBytes; + break; default: var propertyClass = Activator.CreateInstance(type); numBytes = GetClassSize(propertyClass, numBytes, true); @@ -93,12 +94,12 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i IncrementToEven(ref numBytes); for (int i = 0; i < array.Length; i++) { - numBytes = GetIncreasedNumberOfBytes(numBytes, elementType); + numBytes = GetIncreasedNumberOfBytes(numBytes, elementType, property); } } else { - numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType); + numBytes = GetIncreasedNumberOfBytes(numBytes, property.PropertyType, property); } } if (false == isInnerProperty) @@ -111,7 +112,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i return numBytes; } - private static object? GetPropertyValue(Type propertyType, byte[] bytes, ref double numBytes) + private static object? GetPropertyValue(Type propertyType, PropertyInfo? propertyInfo, byte[] bytes, ref double numBytes) { object? value = null; @@ -133,26 +134,20 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes++; break; case "Int16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten ushort source = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); value = source.ConvertToShort(); numBytes += 2; break; case "UInt16": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten value = Word.FromBytes(bytes[(int)numBytes + 1], bytes[(int)numBytes]); numBytes += 2; break; case "Int32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten uint sourceUInt = DWord.FromBytes(bytes[(int)numBytes + 3], bytes[(int)numBytes + 2], @@ -162,9 +157,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += 4; break; case "UInt32": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten value = DWord.FromBytes( bytes[(int)numBytes], @@ -174,9 +167,7 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += 4; break; case "Single": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); // hier auswerten value = Real.FromByteArray( new byte[] { @@ -187,15 +178,31 @@ public static double GetClassSize(object instance, double numBytes = 0.0, bool i numBytes += 4; break; case "Double": - numBytes = Math.Ceiling(numBytes); - if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0) - numBytes++; + IncrementToEven(ref numBytes); var buffer = new byte[8]; Array.Copy(bytes, (int)numBytes, buffer, 0, 8); // hier auswerten value = LReal.FromByteArray(buffer); numBytes += 8; break; + case "String": + S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + IncrementToEven(ref numBytes); + + // get the value + var sData = new byte[attribute.ReservedLengthInBytes]; + Array.Copy(bytes, (int)numBytes, sData, 0, sData.Length); + value = attribute.Type switch + { + S7StringType.S7String => S7String.FromByteArray(sData), + S7StringType.S7WString => S7WString.FromByteArray(sData), + _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute") + }; + numBytes += sData.Length; + break; default: var propClass = Activator.CreateInstance(propertyType); numBytes = FromBytes(propClass, bytes, numBytes); @@ -227,7 +234,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { array.SetValue( - GetPropertyValue(elementType, bytes, ref numBytes), + GetPropertyValue(elementType, property, bytes, ref numBytes), i); } } @@ -235,7 +242,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes { property.SetValue( sourceClass, - GetPropertyValue(property.PropertyType, bytes, ref numBytes), + GetPropertyValue(property.PropertyType, property, bytes, ref numBytes), null); } } @@ -243,7 +250,7 @@ public static double FromBytes(object sourceClass, byte[] bytes, double numBytes return numBytes; } - private static double SetBytesFromProperty(object propertyValue, byte[] bytes, double numBytes) + private static double SetBytesFromProperty(object propertyValue, PropertyInfo? propertyInfo, byte[] bytes, double numBytes) { int bytePos = 0; int bitPos = 0; @@ -285,6 +292,18 @@ private static double SetBytesFromProperty(object propertyValue, byte[] bytes, d case "Double": bytes2 = LReal.ToByteArray((double)propertyValue); break; + case "String": + S7StringAttribute? attribute = propertyInfo?.GetCustomAttributes().SingleOrDefault(); + if (attribute == default(S7StringAttribute)) + throw new ArgumentException("Please add S7StringAttribute to the string field"); + + bytes2 = attribute.Type switch + { + S7StringType.S7String => S7String.ToByteArray((string)propertyValue, attribute.ReservedLength), + S7StringType.S7WString => S7WString.ToByteArray((string)propertyValue, attribute.ReservedLength), + _ => throw new ArgumentException("Please use a valid string type for the S7StringAttribute") + }; + break; default: numBytes = ToBytes(propertyValue, bytes, numBytes); break; @@ -320,12 +339,12 @@ public static double ToBytes(object sourceClass, byte[] bytes, double numBytes = Type elementType = property.PropertyType.GetElementType(); for (int i = 0; i < array.Length && numBytes < bytes.Length; i++) { - numBytes = SetBytesFromProperty(array.GetValue(i), bytes, numBytes); + numBytes = SetBytesFromProperty(array.GetValue(i), property, bytes, numBytes); } } else { - numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), bytes, numBytes); + numBytes = SetBytesFromProperty(property.GetValue(sourceClass, null), property, bytes, numBytes); } } return numBytes; diff --git a/S7.Net/Types/S7StringAttribute.cs b/S7.Net/Types/S7StringAttribute.cs index 4d6e1075..768667d0 100644 --- a/S7.Net/Types/S7StringAttribute.cs +++ b/S7.Net/Types/S7StringAttribute.cs @@ -2,7 +2,7 @@ namespace S7.Net.Types { - [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] public sealed class S7StringAttribute : Attribute { private readonly S7StringType type; From f47918946daff3bf633cdb0e35509a204c8c7fdc Mon Sep 17 00:00:00 2001 From: Michael Croes Date: Sat, 10 Dec 2022 20:51:44 +0100 Subject: [PATCH 4/4] ci: Run test on ubuntu-20.04 due to lack of snap7 on newer ubuntu --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0d138107..ad5d9a90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,14 +14,14 @@ jobs: DOTNET_NOLOGO : 1 strategy: matrix: - os: [windows-latest, ubuntu-latest, macos-latest] + os: [windows-latest, ubuntu-20.04, macos-latest] test-framework: [netcoreapp3.1, net5.0] include: - - os: ubuntu-latest + - os: ubuntu-20.04 test-framework: netcoreapp3.1 installSnap7: true dotnet-sdk: '3.1.x' - - os: ubuntu-latest + - os: ubuntu-20.04 test-framework: net5.0 installSnap7: true dotnet-sdk: '5.0.x' @@ -48,7 +48,7 @@ jobs: - uses: actions/checkout@v2 - name: Install Snap7 Linux - if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-latest' }} + if: ${{ matrix.installSnap7 && matrix.os == 'ubuntu-20.04' }} run: | sudo add-apt-repository ppa:gijzelaar/snap7 sudo apt-get update