From c49dab77ba6c44b2e7f89db062a214629df65a80 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 13 Nov 2025 11:18:24 +0000 Subject: [PATCH 1/5] Prototype of mixed payload spec support - Auto-generate converters for payload spec with mixed types - Support for explicit member length - Support for inferring member length based on interface type - AllowUnsafeBlocks is now required for building generated helper code --- interface/Device.tt | 104 +++++++++++++++++++++++++++++--- interface/Interface.tt | 134 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 226 insertions(+), 12 deletions(-) diff --git a/interface/Device.tt b/interface/Device.tt index 4f6b07c..f2ff7fe 100644 --- a/interface/Device.tt +++ b/interface/Device.tt @@ -464,9 +464,10 @@ if (isPrivate) if (member.Value.HasConverter) { var memberType = TemplateHelper.GetInterfaceType(member.Value, register.Type); + var converterInterfaceType = member.Value.GetConverterInterfaceType(register.Type); #> - private static partial <#= memberType #> ParsePayload<#= member.Key #>(<#= register.PayloadInterfaceType #> payload<#= member.Key #>); + private static partial <#= memberType #> ParsePayload<#= member.Key #>(<#= converterInterfaceType #> payload<#= member.Key #>); <# } } @@ -482,7 +483,8 @@ if (isPrivate) member.Key, member.Value, "payload", - register.Type); + register, + deviceMetadata); #> result.<#= member.Key #> = <#= memberConversion #>; <# @@ -532,16 +534,16 @@ if (isPrivate) foreach (var member in register.PayloadSpec) { var payloadIndex = member.Value.Offset.GetValueOrDefault(0); - var memberIndexer = member.Value.Offset.HasValue ? $"[{member.Value.Offset}]" : string.Empty; - var memberConversion = TemplateHelper.GetPayloadMemberFormatter( + var memberAssignment = TemplateHelper.GetPayloadMemberAssignmentFormatter( member.Key, member.Value, - $"value.{member.Key}", - register.Type, + "result", + register, + deviceMetadata, assigned[payloadIndex]); assigned[payloadIndex] = true; #> - result<#= memberIndexer #><#= memberConversion #>; + <#= memberAssignment #>; <# } #> @@ -1002,4 +1004,92 @@ foreach (var groupMask in deviceMetadata.GroupMasks) <# } #> + + internal static class RuntimeArrayHelpers + { + internal static T[] GetSubArray(this T[] array, int offset, int count) + { + var result = new T[count]; + Array.Copy(array, offset, result, 0, count); + return result; + } + + internal static byte ToByte(this ArraySegment segment) => segment.Array[segment.Offset]; + + internal static sbyte ToSByte(this ArraySegment segment) => (sbyte)segment.Array[segment.Offset]; + + internal static ushort ToUInt16(this ArraySegment segment) => BitConverter.ToUInt16(segment.Array, segment.Offset); + + internal static short ToInt16(this ArraySegment segment) => BitConverter.ToInt16(segment.Array, segment.Offset); + + internal static uint ToUInt32(this ArraySegment segment) => BitConverter.ToUInt32(segment.Array, segment.Offset); + + internal static int ToInt32(this ArraySegment segment) => BitConverter.ToInt32(segment.Array, segment.Offset); + + internal static ulong ToUInt64(this ArraySegment segment) => BitConverter.ToUInt64(segment.Array, segment.Offset); + + internal static long ToInt64(this ArraySegment segment) => BitConverter.ToInt64(segment.Array, segment.Offset); + + internal static float ToSingle(this ArraySegment segment) => BitConverter.ToSingle(segment.Array, segment.Offset); + + internal static string ToUTF8String(this ArraySegment segment) => System.Text.Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count); + + internal static void WriteBytes(this ArraySegment segment, byte value) => segment.Array[segment.Offset] = value; + + internal static void WriteBytes(this ArraySegment segment, sbyte value) => segment.Array[segment.Offset] = (byte)value; + + internal static void WriteBytes(this ArraySegment segment, ushort value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + } + + internal static void WriteBytes(this ArraySegment segment, short value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + } + + internal static void WriteBytes(this ArraySegment segment, uint value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + } + + internal static void WriteBytes(this ArraySegment segment, int value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + } + + internal static void WriteBytes(this ArraySegment segment, ulong value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + segment.Array[segment.Offset + 4] = (byte)(value >> 32); + segment.Array[segment.Offset + 5] = (byte)(value >> 40); + segment.Array[segment.Offset + 6] = (byte)(value >> 48); + segment.Array[segment.Offset + 7] = (byte)(value >> 56); + } + + internal static void WriteBytes(this ArraySegment segment, long value) + { + segment.Array[segment.Offset] = (byte)value; + segment.Array[segment.Offset + 1] = (byte)(value >> 8); + segment.Array[segment.Offset + 2] = (byte)(value >> 16); + segment.Array[segment.Offset + 3] = (byte)(value >> 24); + segment.Array[segment.Offset + 4] = (byte)(value >> 32); + segment.Array[segment.Offset + 5] = (byte)(value >> 40); + segment.Array[segment.Offset + 6] = (byte)(value >> 48); + segment.Array[segment.Offset + 7] = (byte)(value >> 56); + } + + internal static unsafe void WriteBytes(this ArraySegment segment, float value) => WriteBytes(segment, *(int*)&value); + } } diff --git a/interface/Interface.tt b/interface/Interface.tt index f7bf2e8..9ab366e 100644 --- a/interface/Interface.tt +++ b/interface/Interface.tt @@ -70,6 +70,7 @@ public class PayloadMemberInfo { public int? Mask; public int? Offset; + public int? Length; public string MaskType; public string InterfaceType; public MemberConverter Converter; @@ -79,6 +80,17 @@ public class PayloadMemberInfo public float? DefaultValue; public bool HasConverter => Converter > MemberConverter.None; + public string GetConverterInterfaceType(PayloadType payloadType) + { + return Converter switch + { + MemberConverter.RawPayload => "ArraySegment", + MemberConverter.Payload => Length.GetValueOrDefault() > 0 + ? $"ArraySegment<{TemplateHelper.GetInterfaceType(payloadType, 0)}>" + : TemplateHelper.GetInterfaceType(payloadType, 0), + MemberConverter.None => TemplateHelper.GetInterfaceType(payloadType, Length.GetValueOrDefault()) + }; + } } public class BitMaskInfo @@ -166,6 +178,26 @@ public static partial class TemplateHelper else return "uint"; } + public static bool GetInterfaceTypeSize(string interfaceType, out PayloadType payloadType, out int size) + { + payloadType = interfaceType switch + { + "byte" => PayloadType.U8, + "sbyte" => PayloadType.S8, + "ushort" => PayloadType.U16, + "short" => PayloadType.S16, + "uint" => PayloadType.U32, + "int" => PayloadType.S32, + "ulong" => PayloadType.U64, + "long" => PayloadType.S64, + "float" => PayloadType.Float, + _ => 0 + }; + + size = GetPayloadTypeSize(payloadType); + return payloadType > 0; + } + public static string GetPayloadTypeSuffix(PayloadType payloadType, int payloadLength = 0) { if (payloadLength > 0) @@ -189,6 +221,11 @@ public static partial class TemplateHelper } } + static int GetPayloadTypeSize(PayloadType payloadType) + { + return (int)payloadType & 0xF; + } + public static string GetRangeAttributeDeclaration(float? minValue, float? maxValue) { var minValueDeclaration = minValue.HasValue ? minValue.Value.ToString() : "long.MinValue"; @@ -244,15 +281,59 @@ public static partial class TemplateHelper return (int)Math.Log(lsb, 2); } + static int GetMemberLength( + PayloadMemberInfo member, + RegisterInfo register, + DeviceInfo deviceMetadata, + out PayloadType payloadType) + { + payloadType = 0; + if (member.Length.HasValue) + return member.Length.GetValueOrDefault(); + + var interfaceType = GetInterfaceType(member, register.Type); + if (deviceMetadata.GroupMasks.TryGetValue(interfaceType, out GroupMaskInfo groupMask)) + interfaceType = groupMask.InterfaceType; + else if (deviceMetadata.BitMasks.TryGetValue(interfaceType, out BitMaskInfo bitMask)) + interfaceType = bitMask.InterfaceType; + + if (GetInterfaceTypeSize(interfaceType, out payloadType, out int size)) + return size; + else + return GetPayloadTypeSize(register.Type); + } + public static string GetPayloadMemberParser( string name, PayloadMemberInfo member, string expression, - PayloadType payloadType) + RegisterInfo register, + DeviceInfo deviceMetadata) { - if (member.Offset.HasValue) + var payloadType = register.Type; + var memberLength = GetMemberLength(member, register, deviceMetadata, out PayloadType memberPayloadType); + var memberOffset = member.Offset.GetValueOrDefault(); + var payloadInterfaceType = GetInterfaceType(payloadType); + if (memberLength > 0 && member.InterfaceType != payloadInterfaceType && member.InterfaceType != "bool") + { + if (member.Converter == MemberConverter.RawPayload) + throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); + + if (member.Converter == MemberConverter.Payload || memberPayloadType > 0) + { + expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; + if (memberPayloadType > 0) + { + expression = $"{expression}.To{GetPayloadTypeSuffix(memberPayloadType)}()"; + return GetConversionToInterfaceType(member.MaskType, expression); + } + } + else + expression = $"{expression}.GetSubArray({memberOffset}, {memberLength})"; + } + else if (member.Offset.HasValue) { - expression = $"{expression}[{member.Offset.Value}]"; + expression = $"{expression}[{member.Offset.GetValueOrDefault()}]"; } if (member.Mask.HasValue) { @@ -266,7 +347,6 @@ public static partial class TemplateHelper expression = $"({expression} >> {shift})"; } - var payloadInterfaceType = GetInterfaceType(payloadType); expression = $"({payloadInterfaceType}){expression}"; } } @@ -278,7 +358,51 @@ public static partial class TemplateHelper return GetConversionToInterfaceType(member.InterfaceType ?? member.MaskType, expression); } - public static string GetPayloadMemberFormatter( + public static string GetPayloadMemberAssignmentFormatter( + string name, + PayloadMemberInfo member, + string expression, + RegisterInfo register, + DeviceInfo deviceMetadata, + bool assigned) + { + var payloadType = register.Type; + var memberLength = GetMemberLength(member, register, deviceMetadata, out PayloadType memberPayloadType); + var memberOffset = member.Offset.GetValueOrDefault(); + var payloadInterfaceType = GetInterfaceType(payloadType); + if (memberLength > 0 && member.InterfaceType != payloadInterfaceType && member.InterfaceType != "bool") + { + if (member.Converter == MemberConverter.RawPayload) + throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); + + if (member.Converter == MemberConverter.Payload || memberPayloadType > 0) + { + expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; + if (memberPayloadType > 0) + { + var valueExpression = $"value.{name}"; + if (!string.IsNullOrEmpty(member.MaskType)) + valueExpression = GetConversionToInterfaceType(GetInterfaceType(memberPayloadType), valueExpression); + expression = $"{expression}.WriteBytes({valueExpression})"; + return expression; + } + } + else + expression = $"{expression}.GetSubArray({memberOffset}, {memberLength})"; + } + + var memberIndexer = member.Offset.HasValue ? $"[{memberOffset}]" : string.Empty; + var memberConversion = TemplateHelper.GetPayloadMemberValueFormatter( + name, + member, + $"value.{name}", + payloadType, + assigned); + return $"{expression}{memberIndexer}{memberConversion}"; + } + + + public static string GetPayloadMemberValueFormatter( string name, PayloadMemberInfo member, string expression, From 6e8259abd3bf835e4054be41e25171998e8cb254 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 19 Dec 2025 10:59:09 +0000 Subject: [PATCH 2/5] Allow converting fixed length string and sub-array Assumes UTF-8 encoding for strings, and sub-arrays of the same primitive payload type. --- interface/Device.tt | 8 ++++++++ interface/Interface.tt | 24 +++++++++--------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/interface/Device.tt b/interface/Device.tt index f2ff7fe..282f374 100644 --- a/interface/Device.tt +++ b/interface/Device.tt @@ -1091,5 +1091,13 @@ foreach (var groupMask in deviceMetadata.GroupMasks) } internal static unsafe void WriteBytes(this ArraySegment segment, float value) => WriteBytes(segment, *(int*)&value); + + internal static unsafe void WriteBytes(this ArraySegment segment, string value) => + System.Text.Encoding.UTF8.GetBytes(value, 0, segment.Count, segment.Array, segment.Offset); + + internal static void WriteBytes(this ArraySegment segment, T[] values) where T : unmanaged + { + Buffer.BlockCopy(values, 0, segment.Array, segment.Offset, segment.Count); + } } } diff --git a/interface/Interface.tt b/interface/Interface.tt index 9ab366e..9c2cbb5 100644 --- a/interface/Interface.tt +++ b/interface/Interface.tt @@ -143,7 +143,7 @@ public static partial class TemplateHelper { if (!string.IsNullOrEmpty(member.InterfaceType)) return member.InterfaceType; else if (!string.IsNullOrEmpty(member.MaskType)) return member.MaskType; - else return GetInterfaceType(payloadType); + else return GetInterfaceType(payloadType, member.Length.GetValueOrDefault()); } public static string GetInterfaceType(PayloadType payloadType) @@ -319,7 +319,9 @@ public static partial class TemplateHelper if (member.Converter == MemberConverter.RawPayload) throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); - if (member.Converter == MemberConverter.Payload || memberPayloadType > 0) + if (member.InterfaceType == "string") + return $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength}).ToUTF8String()"; + else if (member.Converter == MemberConverter.Payload || memberPayloadType > 0) { expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; if (memberPayloadType > 0) @@ -375,20 +377,12 @@ public static partial class TemplateHelper if (member.Converter == MemberConverter.RawPayload) throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); - if (member.Converter == MemberConverter.Payload || memberPayloadType > 0) - { expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; - if (memberPayloadType > 0) - { - var valueExpression = $"value.{name}"; - if (!string.IsNullOrEmpty(member.MaskType)) - valueExpression = GetConversionToInterfaceType(GetInterfaceType(memberPayloadType), valueExpression); - expression = $"{expression}.WriteBytes({valueExpression})"; - return expression; - } - } - else - expression = $"{expression}.GetSubArray({memberOffset}, {memberLength})"; + var valueExpression = $"value.{name}"; + if (!string.IsNullOrEmpty(member.MaskType) && memberPayloadType > 0) + valueExpression = GetConversionToInterfaceType(GetInterfaceType(memberPayloadType), valueExpression); + expression = $"{expression}.WriteBytes({valueExpression})"; + return expression; } var memberIndexer = member.Offset.HasValue ? $"[{memberOffset}]" : string.Empty; From bd308586652a528649027f30e7eb9c6fba45c4ed Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 8 Jan 2026 11:24:32 +0000 Subject: [PATCH 3/5] Allow providing custom payload conversion methods Conversion code for custom interface types can often be shared across registers. Here we make the internal conversion class partial and provide a simple heuristic for generating conversion method calls based on the interface type name. --- interface/Device.tt | 2 +- interface/Interface.tt | 34 ++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/interface/Device.tt b/interface/Device.tt index 282f374..fbea772 100644 --- a/interface/Device.tt +++ b/interface/Device.tt @@ -1005,7 +1005,7 @@ foreach (var groupMask in deviceMetadata.GroupMasks) } #> - internal static class RuntimeArrayHelpers + internal static partial class PayloadExtensions { internal static T[] GetSubArray(this T[] array, int offset, int count) { diff --git a/interface/Interface.tt b/interface/Interface.tt index 9c2cbb5..498a86c 100644 --- a/interface/Interface.tt +++ b/interface/Interface.tt @@ -281,16 +281,12 @@ public static partial class TemplateHelper return (int)Math.Log(lsb, 2); } - static int GetMemberLength( + static int GetMemberSize( PayloadMemberInfo member, RegisterInfo register, DeviceInfo deviceMetadata, out PayloadType payloadType) { - payloadType = 0; - if (member.Length.HasValue) - return member.Length.GetValueOrDefault(); - var interfaceType = GetInterfaceType(member, register.Type); if (deviceMetadata.GroupMasks.TryGetValue(interfaceType, out GroupMaskInfo groupMask)) interfaceType = groupMask.InterfaceType; @@ -311,17 +307,18 @@ public static partial class TemplateHelper DeviceInfo deviceMetadata) { var payloadType = register.Type; - var memberLength = GetMemberLength(member, register, deviceMetadata, out PayloadType memberPayloadType); + var memberLength = member.Length.GetValueOrDefault(); var memberOffset = member.Offset.GetValueOrDefault(); var payloadInterfaceType = GetInterfaceType(payloadType); - if (memberLength > 0 && member.InterfaceType != payloadInterfaceType && member.InterfaceType != "bool") + if (memberLength > 0 && member.InterfaceType != "bool") { if (member.Converter == MemberConverter.RawPayload) throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); + var memberSize = GetMemberSize(member, register, deviceMetadata, out PayloadType memberPayloadType); if (member.InterfaceType == "string") return $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength}).ToUTF8String()"; - else if (member.Converter == MemberConverter.Payload || memberPayloadType > 0) + else if (member.Converter == MemberConverter.Payload || member.InterfaceType is not null && member.InterfaceType != payloadInterfaceType) { expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; if (memberPayloadType > 0) @@ -329,6 +326,10 @@ public static partial class TemplateHelper expression = $"{expression}.To{GetPayloadTypeSuffix(memberPayloadType)}()"; return GetConversionToInterfaceType(member.MaskType, expression); } + else + { + return $"{expression}.To{member.InterfaceType}()"; + } } else expression = $"{expression}.GetSubArray({memberOffset}, {memberLength})"; @@ -369,20 +370,21 @@ public static partial class TemplateHelper bool assigned) { var payloadType = register.Type; - var memberLength = GetMemberLength(member, register, deviceMetadata, out PayloadType memberPayloadType); + var memberLength = member.Length.GetValueOrDefault(); var memberOffset = member.Offset.GetValueOrDefault(); var payloadInterfaceType = GetInterfaceType(payloadType); - if (memberLength > 0 && member.InterfaceType != payloadInterfaceType && member.InterfaceType != "bool") + if (memberLength > 0 && member.InterfaceType != "bool") { if (member.Converter == MemberConverter.RawPayload) throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); - expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; - var valueExpression = $"value.{name}"; - if (!string.IsNullOrEmpty(member.MaskType) && memberPayloadType > 0) - valueExpression = GetConversionToInterfaceType(GetInterfaceType(memberPayloadType), valueExpression); - expression = $"{expression}.WriteBytes({valueExpression})"; - return expression; + var memberSize = GetMemberSize(member, register, deviceMetadata, out PayloadType memberPayloadType); + expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; + var valueExpression = $"value.{name}"; + if (!string.IsNullOrEmpty(member.MaskType) && memberPayloadType > 0) + valueExpression = GetConversionToInterfaceType(GetInterfaceType(memberPayloadType), valueExpression); + expression = $"{expression}.WriteBytes({valueExpression})"; + return expression; } var memberIndexer = member.Offset.HasValue ? $"[{memberOffset}]" : string.Empty; From 79df60d7fde009d46a56c97822a90182a1639ca6 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 8 Jan 2026 11:41:17 +0000 Subject: [PATCH 4/5] Add regression test for heterogeneous register --- tests/CompilerTestHelper.cs | 2 +- tests/GeneratorTests.cs | 4 +++- tests/Interface.Tests.csproj | 1 + tests/Metadata/device.yml | 27 ++++++++++++++++++++++++++- tests/PayloadExtensions.cs | 22 ++++++++++++++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/PayloadExtensions.cs diff --git a/tests/CompilerTestHelper.cs b/tests/CompilerTestHelper.cs index cd1749d..7b16e4d 100644 --- a/tests/CompilerTestHelper.cs +++ b/tests/CompilerTestHelper.cs @@ -26,7 +26,7 @@ public static void CompileFromSource(params string[] code) nameof(CompilerTestHelper), syntaxTrees: syntaxTrees, references: assemblyReferences, - options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true)); using var memoryStream = new MemoryStream(); var result = compilation.Emit(memoryStream); if (!result.Success) diff --git a/tests/GeneratorTests.cs b/tests/GeneratorTests.cs index 547eb51..1f3bb81 100644 --- a/tests/GeneratorTests.cs +++ b/tests/GeneratorTests.cs @@ -8,6 +8,7 @@ public sealed class GeneratorTests TemplateGenerator generator; CompiledTemplate deviceTemplate; CompiledTemplate asyncDeviceTemplate; + string payloadExtensions; [TestInitialize] public async Task Initialize() @@ -15,6 +16,7 @@ public async Task Initialize() generator = new TestTemplateGenerator(); var deviceTemplateContents = TestHelper.GetManifestResourceText("Device.tt"); var asyncDeviceTemplateContents = TestHelper.GetManifestResourceText("AsyncDevice.tt"); + payloadExtensions = TestHelper.GetManifestResourceText("PayloadExtensions.cs"); deviceTemplate = await generator.CompileTemplateAsync(deviceTemplateContents); TestHelper.AssertNoGeneratorErrors(generator); @@ -41,6 +43,6 @@ public void DeviceTemplate_GenerateAndBuildWithoutErrors(string metadataFileName var asyncDeviceCode = ProcessTemplate(asyncDeviceTemplate, metadataFileName); TestHelper.AssertNoGeneratorErrors(generator); - CompilerTestHelper.CompileFromSource(deviceCode, asyncDeviceCode); + CompilerTestHelper.CompileFromSource(deviceCode, asyncDeviceCode, payloadExtensions); } } diff --git a/tests/Interface.Tests.csproj b/tests/Interface.Tests.csproj index 4b4f3a6..b91fbf7 100644 --- a/tests/Interface.Tests.csproj +++ b/tests/Interface.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/tests/Metadata/device.yml b/tests/Metadata/device.yml index 11abfe4..723729b 100644 --- a/tests/Metadata/device.yml +++ b/tests/Metadata/device.yml @@ -21,4 +21,29 @@ registers: Analog1: offset: 1 Analog2: - offset: 2 \ No newline at end of file + offset: 2 + Version: + address: 34 + type: U8 + length: 32 + access: Event + payloadSpec: + ProtocolVersion: + offset: 0 + length: 3 + interfaceType: HarpVersion + FirmwareVersion: + offset: 3 + length: 3 + interfaceType: HarpVersion + HardwareVersion: + offset: 6 + length: 3 + interfaceType: HarpVersion + CoreId: + offset: 9 + length: 3 + interfaceType: string + InterfaceHash: + offset: 12 + length: 20 diff --git a/tests/PayloadExtensions.cs b/tests/PayloadExtensions.cs new file mode 100644 index 0000000..8fefacf --- /dev/null +++ b/tests/PayloadExtensions.cs @@ -0,0 +1,22 @@ +#pragma warning disable IDE0005 +using System; +#pragma warning restore IDE0005 +using Bonsai.Harp; + +namespace Interface.Tests; + +internal static partial class PayloadExtensions +{ + internal static HarpVersion ToHarpVersion(this ArraySegment segment) + { + var major = segment.Array[segment.Offset]; + var minor = segment.Array[segment.Offset + 1]; + return new HarpVersion(major, minor); + } + + internal static void WriteBytes(this ArraySegment segment, HarpVersion value) + { + segment.Array[segment.Offset] = (byte)value.Major.GetValueOrDefault(); + segment.Array[segment.Offset + 1] = (byte)value.Major.GetValueOrDefault(); + } +} \ No newline at end of file From f2c541a23d2de84a6bc410a72d0116ddb0b50c97 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Thu, 8 Jan 2026 12:07:12 +0000 Subject: [PATCH 5/5] Allow payload conversion directly into mask types Also added regression test for a complex register mixing mask types. --- interface/Interface.tt | 3 ++- tests/Metadata/device.yml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/interface/Interface.tt b/interface/Interface.tt index 498a86c..dce1a34 100644 --- a/interface/Interface.tt +++ b/interface/Interface.tt @@ -315,10 +315,11 @@ public static partial class TemplateHelper if (member.Converter == MemberConverter.RawPayload) throw new NotSupportedException("Raw payload converters inside payload spec is not currently supported."); + var memberInterfaceType = GetInterfaceType(member, register.Type); var memberSize = GetMemberSize(member, register, deviceMetadata, out PayloadType memberPayloadType); if (member.InterfaceType == "string") return $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength}).ToUTF8String()"; - else if (member.Converter == MemberConverter.Payload || member.InterfaceType is not null && member.InterfaceType != payloadInterfaceType) + else if (member.Converter == MemberConverter.Payload || memberInterfaceType != GetInterfaceType(payloadType, register.Length)) { expression = $"new ArraySegment<{payloadInterfaceType}>({expression}, {memberOffset}, {memberLength})"; if (memberPayloadType > 0) diff --git a/tests/Metadata/device.yml b/tests/Metadata/device.yml index 723729b..7e91418 100644 --- a/tests/Metadata/device.yml +++ b/tests/Metadata/device.yml @@ -22,6 +22,32 @@ registers: offset: 1 Analog2: offset: 2 + ComplexConfiguration: + address: 33 + type: U8 + access: Write + length: 17 + payloadSpec: + PwmPort: + offset: 0 + length: 1 + maskType: PwmPort + DutyCycle: + offset: 4 + length: 4 + interfaceType: float + Frequency: + offset: 8 + length: 4 + interfaceType: float + EventsEnabled: + offset: 12 + length: 1 + interfaceType: bool + Delta: + offset: 13 + length: 4 + interfaceType: uint Version: address: 34 type: U8 @@ -47,3 +73,9 @@ registers: InterfaceHash: offset: 12 length: 20 +groupMasks: + PwmPort: + values: + Pwm0: 0x1 + Pwm1: 0x2 + Pwm2: 0x4