diff --git a/src/HassModel/NetDaemon.HassModel.CodeGenerator/MetaData/ServicesMetaData/ServiceMetaDataParser.cs b/src/HassModel/NetDaemon.HassModel.CodeGenerator/MetaData/ServicesMetaData/ServiceMetaDataParser.cs index c6259215..c36db738 100644 --- a/src/HassModel/NetDaemon.HassModel.CodeGenerator/MetaData/ServicesMetaData/ServiceMetaDataParser.cs +++ b/src/HassModel/NetDaemon.HassModel.CodeGenerator/MetaData/ServicesMetaData/ServiceMetaDataParser.cs @@ -59,16 +59,9 @@ private static IReadOnlyCollection GetServices(JsonElement domainEl var result = serviceElement.Deserialize(SerializerOptions)! with { Service = serviceName, + Fields = GetFields(serviceElement) }; - if (serviceElement.TryGetProperty("fields", out var fieldProperty)) - { - result = result with - { - Fields = fieldProperty.EnumerateObject().Select(p => GetField(p.Name, p.Value)).ToList() - }; - } - return result; } catch (Exception ex) @@ -78,6 +71,14 @@ private static IReadOnlyCollection GetServices(JsonElement domainEl } } + private static List GetFields(JsonElement element) + { + if (!element.TryGetProperty("fields", out var fieldProperty)) return []; + + // advanced_fields can have nested fields inside, we flatten them to a single list of fields + return fieldProperty.EnumerateObject().SelectMany(p => p.Name == "advanced_fields" ? GetFields(p.Value) : [GetField(p.Name, p.Value)]).ToList(); + } + private static HassServiceField GetField(string fieldName, JsonElement element) { return element.Deserialize(SerializerOptions)! with diff --git a/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataParserTest.cs b/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataParserTest.cs index 2e164563..a586fac9 100644 --- a/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataParserTest.cs +++ b/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataParserTest.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Runtime.InteropServices.ComTypes; +using System.Text.Json; using NetDaemon.HassModel.CodeGenerator; using NetDaemon.HassModel.CodeGenerator.Model; @@ -42,6 +43,38 @@ public void TestSomeBasicServicesCanBeParsed() res.First().Services.ElementAt(1).Target!.Entity.SelectMany(e=>e.Domain).Should().BeEmpty(); } + [Fact] + public void TestServicesWithAdvancedFieldsCanBeParsed() + { + var sample = File.ReadAllText(@"CodeGenerator/ServiceMetaDataSamples/Lights.json"); + + var res = Parse(sample); + res.Should().HaveCount(1); + var lightsDomain = res.First(); + + lightsDomain.Domain.Should().Be("light"); + lightsDomain.Services.Select(s => s.Service).Should().BeEquivalentTo("turn_on", "turn_off", "toggle"); + var turnOnService = lightsDomain.Services.First(); + turnOnService.Fields!.Select(f => f.Field).Should().BeEquivalentTo( + "transition", + "rgb_color", + "kelvin", + "brightness_pct", + "brightness_step_pct", + "effect", + "rgbw_color", + "rgbww_color", + "color_name", + "hs_color", + "xy_color", + "color_temp", + "brightness", + "brightness_step", + "white", + "profile", + "flash"); + } + [Fact] public void TestServicesWithReturnValueCanBeParsed() { diff --git a/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataSamples/Lights.json b/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataSamples/Lights.json new file mode 100644 index 00000000..a88dea1e --- /dev/null +++ b/src/HassModel/NetDaemon.HassModel.Tests/CodeGenerator/ServiceMetaDataSamples/Lights.json @@ -0,0 +1,974 @@ +{ + "light": { + "turn_on": { + "name": "Turn on", + "description": "Turn on one or more lights and adjust properties of the light, even when they are turned on already.", + "fields": { + "transition": { + "filter": { + "supported_features": [ + 32 + ] + }, + "selector": { + "number": { + "min": 0, + "max": 300, + "unit_of_measurement": "seconds" + } + }, + "name": "Transition", + "description": "Duration it takes to get to next state." + }, + "rgb_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[255, 100, 100]", + "selector": { + "color_rgb": null + }, + "name": "Color", + "description": "The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue." + }, + "kelvin": { + "filter": { + "attribute": { + "supported_color_modes": [ + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "color_temp": { + "unit": "kelvin", + "min": 2000, + "max": 6500 + } + }, + "name": "Color temperature", + "description": "Color temperature in Kelvin." + }, + "brightness_pct": { + "filter": { + "attribute": { + "supported_color_modes": [ + "brightness", + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "number": { + "min": 0, + "max": 100, + "unit_of_measurement": "%" + } + }, + "name": "Brightness", + "description": "Number indicating the percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness, and 100 is the maximum brightness." + }, + "brightness_step_pct": { + "filter": { + "attribute": { + "supported_color_modes": [ + "brightness", + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "number": { + "min": -100, + "max": 100, + "unit_of_measurement": "%" + } + }, + "name": "Brightness step", + "description": "Change brightness by a percentage." + }, + "effect": { + "filter": { + "supported_features": [ + 4 + ] + }, + "selector": { + "text": null + }, + "name": "Effect", + "description": "Light effect." + }, + "advanced_fields": { + "collapsed": true, + "fields": { + "rgbw_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[255, 100, 100, 50]", + "selector": { + "object": null + } + }, + "rgbww_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[255, 100, 100, 50, 70]", + "selector": { + "object": null + } + }, + "color_name": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "select": { + "translation_key": "color_name", + "options": [ + "homeassistant", + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkgrey", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkslategrey", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dimgrey", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "grey", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightgrey", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightslategrey", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "navyblue", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "slategrey", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen" + ] + } + } + }, + "hs_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[300, 70]", + "selector": { + "object": null + } + }, + "xy_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[0.52, 0.43]", + "selector": { + "object": null + } + }, + "color_temp": { + "filter": { + "attribute": { + "supported_color_modes": [ + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "color_temp": { + "unit": "mired", + "min": 153, + "max": 500 + } + } + }, + "brightness": { + "filter": { + "attribute": { + "supported_color_modes": [ + "brightness", + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "number": { + "min": 0, + "max": 255 + } + } + }, + "brightness_step": { + "filter": { + "attribute": { + "supported_color_modes": [ + "brightness", + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "number": { + "min": -225, + "max": 255 + } + } + }, + "white": { + "filter": { + "attribute": { + "supported_color_modes": [ + "white" + ] + } + }, + "selector": { + "constant": { + "value": true, + "label": "Enabled" + } + } + }, + "profile": { + "example": "relax", + "selector": { + "text": null + } + }, + "flash": { + "filter": { + "supported_features": [ + 8 + ] + }, + "selector": { + "select": { + "options": [ + { + "label": "Long", + "value": "long" + }, + { + "label": "Short", + "value": "short" + } + ] + } + } + } + } + } + }, + "target": { + "entity": [ + { + "domain": [ + "light" + ] + } + ] + } + }, + "turn_off": { + "name": "Turn off", + "description": "Turn off one or more lights.", + "fields": { + "transition": { + "filter": { + "supported_features": [ + 32 + ] + }, + "selector": { + "number": { + "min": 0, + "max": 300, + "unit_of_measurement": "seconds" + } + }, + "name": "Transition", + "description": "Duration it takes to get to next state." + }, + "advanced_fields": { + "collapsed": true, + "fields": { + "flash": { + "filter": { + "supported_features": [ + 8 + ] + }, + "selector": { + "select": { + "options": [ + { + "label": "Long", + "value": "long" + }, + { + "label": "Short", + "value": "short" + } + ] + } + } + } + } + } + }, + "target": { + "entity": [ + { + "domain": [ + "light" + ] + } + ] + } + }, + "toggle": { + "name": "Toggle", + "description": "Toggles one or more lights, from on to off, or, off to on, based on their current state.", + "fields": { + "transition": { + "filter": { + "supported_features": [ + 32 + ] + }, + "selector": { + "number": { + "min": 0, + "max": 300, + "unit_of_measurement": "seconds" + } + }, + "name": "Transition", + "description": "Duration it takes to get to next state." + }, + "rgb_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[255, 100, 100]", + "selector": { + "color_rgb": null + }, + "name": "Color", + "description": "The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue." + }, + "kelvin": { + "filter": { + "attribute": { + "supported_color_modes": [ + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "color_temp": { + "unit": "kelvin", + "min": 2000, + "max": 6500 + } + }, + "name": "Color temperature", + "description": "Color temperature in Kelvin." + }, + "brightness_pct": { + "filter": { + "attribute": { + "supported_color_modes": [ + "brightness", + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "number": { + "min": 0, + "max": 100, + "unit_of_measurement": "%" + } + }, + "name": "Brightness", + "description": "Number indicating the percentage of full brightness, where 0 turns the light off, 1 is the minimum brightness, and 100 is the maximum brightness." + }, + "effect": { + "filter": { + "supported_features": [ + 4 + ] + }, + "selector": { + "text": null + }, + "name": "Effect", + "description": "Light effect." + }, + "advanced_fields": { + "collapsed": true, + "fields": { + "rgbw_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[255, 100, 100, 50]", + "selector": { + "object": null + } + }, + "rgbww_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[255, 100, 100, 50, 70]", + "selector": { + "object": null + } + }, + "color_name": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "select": { + "translation_key": "color_name", + "options": [ + "homeassistant", + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkgrey", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkslategrey", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dimgrey", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "grey", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightgrey", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightslategrey", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "navyblue", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "slategrey", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen" + ] + } + } + }, + "hs_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[300, 70]", + "selector": { + "object": null + } + }, + "xy_color": { + "filter": { + "attribute": { + "supported_color_modes": [ + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "example": "[0.52, 0.43]", + "selector": { + "object": null + } + }, + "color_temp": { + "filter": { + "attribute": { + "supported_color_modes": [ + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "color_temp": { + "unit": "mired", + "min": 153, + "max": 500 + } + } + }, + "brightness": { + "filter": { + "attribute": { + "supported_color_modes": [ + "brightness", + "color_temp", + "hs", + "xy", + "rgb", + "rgbw", + "rgbww" + ] + } + }, + "selector": { + "number": { + "min": 0, + "max": 255 + } + } + }, + "white": { + "filter": { + "attribute": { + "supported_color_modes": [ + "white" + ] + } + }, + "selector": { + "constant": { + "value": true, + "label": "Enabled" + } + } + }, + "profile": { + "example": "relax", + "selector": { + "text": null + } + }, + "flash": { + "filter": { + "supported_features": [ + 8 + ] + }, + "selector": { + "select": { + "options": [ + { + "label": "Long", + "value": "long" + }, + { + "label": "Short", + "value": "short" + } + ] + } + } + } + } + } + }, + "target": { + "entity": [ + { + "domain": [ + "light" + ] + } + ] + } + } + } +} diff --git a/src/HassModel/NetDaemon.HassModel.Tests/NetDaemon.HassModel.Tests.csproj b/src/HassModel/NetDaemon.HassModel.Tests/NetDaemon.HassModel.Tests.csproj index 02f3b240..e77cb9af 100644 --- a/src/HassModel/NetDaemon.HassModel.Tests/NetDaemon.HassModel.Tests.csproj +++ b/src/HassModel/NetDaemon.HassModel.Tests/NetDaemon.HassModel.Tests.csproj @@ -32,4 +32,10 @@ + + + PreserveNewest + + +