Search code examples
c#raspberry-pi4.net-7.0debian-based

Issues with .NET's configuration json serialization on a Raspberry Pi 4B with Raspbian (Debian 11)


I am writing a custom C# console program targeting .net 7.0 and running on a Raspberry Pi 4B with Rasbian after being published self-contained targeting ARM64. There is 1 property of my configuration that seemingly refuses to deserialize automatically.

The LedDefinitions property deserializes perfectly when I debug it on my desktop (windows or wsl), but when I run in on the Pi, it won't deserialize and I get an empty array. I have hacked it with a PostConfigure setup like below, but I don't understand why I have to do this.

I have projects at my job that are setup similarly and they seem to work without any hackery, although they run inside of containers based on the mcr.microsoft.com/dotnet/aspnet:6.0 docker image.

Any insight as to what I need to look at or tweak is much appreciated.

Program.cs snippet

    var builder = Host.CreateDefaultBuilder(args)
        .ConfigureServices((bc, s) =>
        {
            s.Configure<LightingConfiguration>(bc.Configuration.GetSection("LightingConfiguration"));
            s.AddTransient<LightingExecutor>();
            s.PostConfigure<LightingConfiguration>(x =>
            {
                var b = bc.Configuration
                          .GetSection("LightingConfiguration")
                          .GetChildren()
                          .Where(y => y.Path.EndsWith("LedDefinitions"))
                          .SelectMany(y => y.GetChildren())
                          .ToArray();
    
                var propIndex  = typeof(LedRange).GetProperty("Index");
                var propLedStart  = typeof(LedRange).GetProperty("LedStart");
                var propLedEnd  = typeof(LedRange).GetProperty("LedEnd");
                var propPatternStart = typeof(LedRange).GetProperty("PatternStart");
    
                var newLedRanges = new List<LedRange>();
    
                foreach (var q in b)
                {
                    var obj = new LedRange();
    
                    foreach(var p in q.GetChildren())
                    {
                        var prop = p.Key switch
                        {
                            "Index" => propIndex,
                            "LedStart" => propLedStart,
                            "LedEnd" => propLedEnd,
                            "PatternStart" => propPatternStart,
                            _ => null,
                        };
    
                        prop?.SetValue(obj, int.Parse(p.Value));
                    }
                    newLedRanges.Add(obj);
                }
    
                x.LedDefinitions = newLedRanges.ToArray();
            });
        });
    
    var host = builder.Build();
    
    var config = host.Services.GetRequiredService<IOptionsMonitor<LightingConfiguration>>();

Configuration class

    class LightingConfiguration
    {
        public int NumberOfLEDs { get; init; }
        public bool DoFire { get; init; }
        public bool HorizontalFire { get; init; }

        public int PatternOffsetPerFrame { get; init; }
        public int TargetFps { get; init; }
        public string[] PatternToRepeat { get; init; }
        public LedRange[] LedDefinitions { get; set; }

        public int Cooling { get; init; }       // Rate at which the pixels cool off
        public int Sparks { get; init; }        // How many sparks will be attempted each frame
        public int SparkHeight { get; init; }   // If created, max height for a spark
        public int Sparking { get; init; }      // Probability of a spark each attempt
        public bool Reversed { get; init; }     // if reversed, we draw from 0 outwards
        public bool Mirrored { get; init; }     // if mirrored, we split and duplicate the drawing
    }

    class LedRange
    {
        public int Index { get; init; }
        public int LedStart { get; init; }
        public int LedEnd { get; init; }
        public int PatternStart { get; init; }
    }

Example appsettings.json file.

    {
      "LightingConfiguration": {
        "NumberOfLEDs": 60, //300
        "DoFire": false, //true
        "HorizontalFire": true, //true
    
        "PatternOffsetPerFrame": 1,
        "TargetFps": 5,
        "PatternToRepeat": [
          "00800000",
          "00008000",
          "00000080"
        ],
        "LedDefinitions": [
          {
            "Index": 0,
            "LedStart": 1,
            "LedEnd": 29,
            "PatternStart": 0
          },
          {
            "Index": 1,
            "LedStart": 31,
            "LedEnd": 58,
            "PatternStart": 0
          }
        ],
    
        "Cooling": 30,
        "Sparking": 100,
        "Sparks": 5,
        "SparkHeight": 300,
        "Reversed": false,
        "Mirrored": false
      }
    }
$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>disable</Nullable>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ProduceReferenceAssembly>False</ProduceReferenceAssembly>
    <Platforms>AnyCPU;ARM64</Platforms>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="appsettings.json" />
  </ItemGroup>

  <ItemGroup>
    <Content Include="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Iot.Device.Bindings" Version="3.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
  </ItemGroup>

</Project>

Publishing properties

Configuration: Debug | ARM64
Target Framework: net7.0
Deployment mode: Self-contained
Target runtime: linux-arm64
Options: Produce single file
         Trim unused code

Solution

  • I found a solution, but I am not sure why it is required. I changed the host builder to the below code.

                var builder = Host.CreateDefaultBuilder(args)
                    .ConfigureServices((bc, s) =>
                    {
                        s.Configure<LightingConfiguration>(bc.Configuration.GetSection("LightingConfiguration"));
                        s.Configure<LedRange>(bc.Configuration.GetSection("LightingConfiguration").GetSection("LedDefinitions"));
                        s.AddTransient<LightingExecutor>();
                    });
    

    I'm posting this as an answer in case someone else runs into this.