From 0f18fe23f35900d208c7995c8c8b9fa041ddf248 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 13 Mar 2026 09:12:44 +0000 Subject: [PATCH 1/2] Add roundtrip tests for port pin metadata This will help keep track of all serializer export formats for tooling to automatically generate metadata files. --- tests/Metadata/device.ios.yml | 50 +++++++++++++++----------------- tests/MetadataSerializerTests.cs | 9 +++--- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/tests/Metadata/device.ios.yml b/tests/Metadata/device.ios.yml index 01284ab..1cabdf6 100644 --- a/tests/Metadata/device.ios.yml +++ b/tests/Metadata/device.ios.yml @@ -1,5 +1,3 @@ -%YAML 1.1 ---- # yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/ios.json DO3: port: PORTC @@ -56,19 +54,19 @@ POKE0_IR: port: PORTD pinNumber: 4 direction: input - interruptNumber: 0 - interruptPriority: low - triggerMode: toggle pinMode: pullup + triggerMode: toggle + interruptPriority: low + interruptNumber: 0 description: Poke 0 infrared POKE0_IO: port: PORTD pinNumber: 5 direction: input - interruptNumber: 0 - interruptPriority: "off" - triggerMode: toggle pinMode: pullup + triggerMode: toggle + interruptPriority: "off" + interruptNumber: 0 description: Poke 0 DIO POKE0_LED: port: PORTD @@ -88,19 +86,19 @@ POKE1_IR: port: PORTE pinNumber: 4 direction: input - interruptNumber: 0 - interruptPriority: low - triggerMode: toggle pinMode: pullup + triggerMode: toggle + interruptPriority: low + interruptNumber: 0 description: Poke 1 infrared POKE1_IO: port: PORTE pinNumber: 5 direction: input - interruptNumber: 0 - interruptPriority: "off" - triggerMode: toggle pinMode: pullup + triggerMode: toggle + interruptPriority: "off" + interruptNumber: 0 description: Poke 1 DIO POKE1_LED: port: PORTE @@ -120,19 +118,19 @@ POKE2_IR: port: PORTF pinNumber: 4 direction: input - interruptNumber: 0 - interruptPriority: low - triggerMode: toggle pinMode: pullup + triggerMode: toggle + interruptPriority: low + interruptNumber: 0 description: Poke 2 infrared POKE2_IO: port: PORTF pinNumber: 5 direction: input - interruptNumber: 0 - interruptPriority: "off" - triggerMode: toggle pinMode: pullup + triggerMode: toggle + interruptPriority: "off" + interruptNumber: 0 description: Poke 2 DIO POKE2_LED: port: PORTF @@ -152,17 +150,17 @@ ADC1_AVAILABLE: port: PORTJ pinNumber: 0 direction: input - interruptNumber: 0 - interruptPriority: "off" - triggerMode: toggle pinMode: pulldown + triggerMode: toggle + interruptPriority: "off" + interruptNumber: 0 description: ADC1 is available on hardware DI3: port: PORTH pinNumber: 0 direction: input - interruptNumber: 0 - interruptPriority: low - triggerMode: toggle pinMode: tristate + triggerMode: toggle + interruptPriority: low + interruptNumber: 0 description: Input DI3 diff --git a/tests/MetadataSerializerTests.cs b/tests/MetadataSerializerTests.cs index b5916bf..f5bb566 100644 --- a/tests/MetadataSerializerTests.cs +++ b/tests/MetadataSerializerTests.cs @@ -34,15 +34,16 @@ private static string NormalizeYaml(string contents) } [DataTestMethod] - [DataRow("core.yml")] - [DataRow("device.yml")] - public void DeviceMetadata_RoundTripSerializes(string metadataFileName) + [DataRow("core.yml", typeof(DeviceInfo))] + [DataRow("device.yml", typeof(DeviceInfo))] + [DataRow("device.ios.yml", typeof(Dictionary))] + public void Metadata_RoundTripSerializes(string metadataFileName, Type type) { metadataFileName = TestHelper.GetMetadataPath(metadataFileName); var metadataContents = File.ReadAllText(metadataFileName); metadataContents = NormalizeYaml(metadataContents); - var deviceMetadata = MetadataDeserializer.Instance.Deserialize(metadataContents); + var deviceMetadata = MetadataDeserializer.Instance.Deserialize(metadataContents, type); var roundTripContents = MetadataSerializer.Instance.Serialize(deviceMetadata); try { From 2ae851d6ccde458d1e74fe683e590e6e48a7fd6c Mon Sep 17 00:00:00 2001 From: glopesdev Date: Fri, 13 Mar 2026 09:17:25 +0000 Subject: [PATCH 2/2] Annotate port pin metadata members for roundtrip Ensures serializer member order matches convention and casing of all enums matches the schema as serialized. --- src/Firmware.cs | 47 +++++++++++++++++++++++++++++++++++++ src/MetadataDeserializer.cs | 2 ++ src/MetadataSerializer.cs | 2 ++ 3 files changed, 51 insertions(+) diff --git a/src/Firmware.cs b/src/Firmware.cs index fab0e47..235887c 100644 --- a/src/Firmware.cs +++ b/src/Firmware.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using Bonsai.Harp; using YamlDotNet.Core; +using YamlDotNet.Core.Events; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -167,16 +168,20 @@ public class PortPinInfo /// /// Specifies the microcontroller port where the pin is located. /// + [YamlMember(Order = -1)] public string Port; /// /// Specifies the unique pin number in the defined port. /// + [YamlMember(Order = -1, DefaultValuesHandling = DefaultValuesHandling.Preserve)] public int PinNumber; /// /// Specifies whether the pin will be used as input or output. /// + [YamlConverter(typeof(LowerCaseEnumTypeConverter))] + [YamlMember(Order = -1, DefaultValuesHandling = DefaultValuesHandling.Preserve)] public PinDirection Direction; /// @@ -193,21 +198,27 @@ public class InputPinInfo : PortPinInfo /// /// Specifies how the input pin is configured to handle floating inputs. /// + [YamlConverter(typeof(LowerCaseEnumTypeConverter))] + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.Preserve)] public InputPinMode PinMode; /// /// Specifies when the interrupt event for this pin should be triggered. /// + [YamlConverter(typeof(LowerCaseEnumTypeConverter))] public TriggerMode TriggerMode; /// /// Specifies the priority of the interrupt event for this pin. /// + [YamlConverter(typeof(LowerCaseEnumTypeConverter))] + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.Preserve)] public InterruptPriority InterruptPriority; /// /// Specifies the interrupt number associated with this pin. /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.Preserve)] public int InterruptNumber; } @@ -219,16 +230,20 @@ public class OutputPinInfo : PortPinInfo /// /// Specifies whether reading the state of the output pin is allowed. /// + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.Preserve)] public bool AllowRead; /// /// Specifies the output pin wiring configuration. /// + [YamlConverter(typeof(CamelCaseEnumTypeConverter))] public OutputPinMode PinMode; /// /// Specifies the initial state of the output pin at boot time. /// + [YamlConverter(typeof(LowerCaseEnumTypeConverter))] + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.Preserve)] public LogicState InitialState; /// @@ -373,6 +388,38 @@ public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerialize } } +class LowerCaseEnumTypeConverter : IYamlTypeConverter +{ + public static readonly LowerCaseEnumTypeConverter Instance = new(); + + public bool Accepts(Type type) => false; + + public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) => rootDeserializer(type); + + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + var scalarStyle = ScalarStyle.Any; + var scalarValue = LowerCaseNamingConvention.Instance.Apply(value.ToString()); + if (scalarValue == "off") + scalarStyle = ScalarStyle.DoubleQuoted; + emitter.Emit(new Scalar(AnchorName.Empty, TagName.Empty, scalarValue, scalarStyle, true, true)); + } +} + +class CamelCaseEnumTypeConverter : IYamlTypeConverter +{ + public static readonly CamelCaseEnumTypeConverter Instance = new(); + + public bool Accepts(Type type) => false; + + public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) => rootDeserializer(type); + + public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer) + { + emitter.Emit(new Scalar(CamelCaseNamingConvention.Instance.Apply(value.ToString()))); + } +} + /// /// Translates property names into screaming snake case following the current /// ATxmega register naming convention. diff --git a/src/MetadataDeserializer.cs b/src/MetadataDeserializer.cs index 52cdbcc..1fb58e6 100644 --- a/src/MetadataDeserializer.cs +++ b/src/MetadataDeserializer.cs @@ -18,6 +18,8 @@ public static class MetadataDeserializer .WithTypeConverter(MaskValueTypeConverter.Instance) .WithTypeConverter(HarpVersionTypeConverter.Instance) .WithTypeConverter(HexValueTypeConverter.Instance) + .WithTypeConverter(LowerCaseEnumTypeConverter.Instance) + .WithTypeConverter(CamelCaseEnumTypeConverter.Instance) .WithPortPinInfoTypeConverter() .Build(); diff --git a/src/MetadataSerializer.cs b/src/MetadataSerializer.cs index e12df9b..9c7ee9a 100644 --- a/src/MetadataSerializer.cs +++ b/src/MetadataSerializer.cs @@ -18,6 +18,8 @@ public static class MetadataSerializer .WithTypeConverter(MaskValueTypeConverter.Instance) .WithTypeConverter(HarpVersionTypeConverter.Instance) .WithTypeConverter(HexValueTypeConverter.Instance) + .WithTypeConverter(LowerCaseEnumTypeConverter.Instance) + .WithTypeConverter(CamelCaseEnumTypeConverter.Instance) .ConfigureDefaultValuesHandling( DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitDefaults |