From 50df69edf5dbb2b00616b297d24278363edc3e10 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Mon, 16 Feb 2026 11:45:04 +0300 Subject: [PATCH] feat: Camera settings + some refactor --- .../Abstractions/ServiceConfigurationBase.cs | 27 +- .../ConfiurationSectionExtensions.cs | 16 ++ GSS2.Core/Extensions/EnumerableExtensions.cs | 23 ++ GSS2.Core/GSS2.Core.csproj | 2 +- GSS2.Core/Hardware/CameraService.cs | 143 +++++++++- GSS2.Core/Hardware/IlluminatorService.cs | 22 +- GSS2.Core/Hardware/LightsService.cs | 2 +- .../Hardware/TemperatureHumidityService.cs | 4 +- GSS2.Core/Helpers/ControlValueHelper.cs | 250 ++++++++++++++++++ GSS2.Core/Logging/LibCameraLogSink.cs | 4 + GSS2.Test/ViewModels/MainViewModel.cs | 24 +- GSS2.Test/Views/MainView.axaml | 13 +- GSS2.Test/appsettings.json | 46 +++- GSS2.UI.Core/ViewModels/ResourcesViewModel.cs | 19 +- 14 files changed, 542 insertions(+), 53 deletions(-) create mode 100644 GSS2.Core/Extensions/ConfiurationSectionExtensions.cs create mode 100644 GSS2.Core/Helpers/ControlValueHelper.cs diff --git a/GSS2.Core/Abstractions/ServiceConfigurationBase.cs b/GSS2.Core/Abstractions/ServiceConfigurationBase.cs index 4037ba3..21a9fd6 100644 --- a/GSS2.Core/Abstractions/ServiceConfigurationBase.cs +++ b/GSS2.Core/Abstractions/ServiceConfigurationBase.cs @@ -1,12 +1,35 @@ +using System.Collections; +using System.Text.Json; + +using GSS2.Core.Extentions; + +using Microsoft.Extensions.Configuration; + namespace GSS2.Core.Abstractions; public abstract record ServiceConfigurationBase { public virtual string ToString(string separator, int indent) { - List strs = new List(); + var strs = new List(); foreach (var prop in GetType().GetProperties()) - strs.Add($"{prop.Name}: {prop.GetValue(this)}"); + { + if (prop.GetMethod?.IsStatic != false) + continue; + + var value = prop.GetValue(this); + + string? strValue = value switch + { + string str => str, + IConfigurationSection configurationSection => $"\n{(configurationSection.AsDictionary() as IDictionary).ToString(separator, indent * 2)}", + IDictionary dictionary => $"\n{dictionary.ToString(separator, indent * 2)}", + IEnumerable enumerable => $"\n{enumerable.ToString(separator, indent * 2)}", + object obj => obj?.ToString(), + _ => null + }; + strs.Add($"{prop.Name}: {strValue}"); + } return string.Join(separator, strs.Select(s => $"{new string(' ', indent)}{s}")); } } \ No newline at end of file diff --git a/GSS2.Core/Extensions/ConfiurationSectionExtensions.cs b/GSS2.Core/Extensions/ConfiurationSectionExtensions.cs new file mode 100644 index 0000000..b6dee81 --- /dev/null +++ b/GSS2.Core/Extensions/ConfiurationSectionExtensions.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.Configuration; + +namespace GSS2.Core.Extentions; + +public static class ConfiurationSectionExtensions +{ + public static Dictionary AsDictionary(this IConfigurationSection values) + { + var dict = new Dictionary(); + foreach (var (key, value) in values.AsEnumerable()) + if (value is not null) + dict.Add(key.Substring(values.Path.Length + 1), value); + return dict; + } +} + diff --git a/GSS2.Core/Extensions/EnumerableExtensions.cs b/GSS2.Core/Extensions/EnumerableExtensions.cs index 412d17e..ec47de9 100644 --- a/GSS2.Core/Extensions/EnumerableExtensions.cs +++ b/GSS2.Core/Extensions/EnumerableExtensions.cs @@ -1,7 +1,30 @@ +using System.Collections; + namespace GSS2.Core.Extentions; public static class EnumerableExtensions { public static IEnumerable<(int i, T value)> Enumerate(this IEnumerable values) => values.Select((value, i) => (i, value)); + + public static string ToString(this IDictionary values, string separator, int indent) => string.Join(separator, values.Select(v => $"{new string(' ', indent)} {v.Key}: {v.Value}")); + public static string ToString(this IDictionary values, string separator, int indent) + { + var strs = new List(); + var enumerator = values.GetEnumerator(); + while (enumerator.MoveNext()) + strs.Add($"{enumerator.Key}: {enumerator.Value}"); + return string.Join(separator, strs.Select(s => $"{new string(' ', indent)}{s}")); + } + + public static string ToString(this IEnumerable values, string separator, int indent) => (values as IEnumerable).ToString(separator, indent); + public static string ToString(this IEnumerable values, string separator, int indent) + { + var strs = new List(); + var enumerator = values.GetEnumerator(); + while (enumerator.MoveNext()) + strs.Add(enumerator.Current.ToString()); + return string.Join(separator, strs.Select(s => $"{new string(' ', indent)}{s}")); + } + } diff --git a/GSS2.Core/GSS2.Core.csproj b/GSS2.Core/GSS2.Core.csproj index 3298c1f..530327b 100644 --- a/GSS2.Core/GSS2.Core.csproj +++ b/GSS2.Core/GSS2.Core.csproj @@ -21,7 +21,7 @@ - + diff --git a/GSS2.Core/Hardware/CameraService.cs b/GSS2.Core/Hardware/CameraService.cs index 3cc9817..0458912 100644 --- a/GSS2.Core/Hardware/CameraService.cs +++ b/GSS2.Core/Hardware/CameraService.cs @@ -3,11 +3,13 @@ using System.Text; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; using LibCameraSharp; using GSS2.Core.Extentions; using GSS2.Core.Abstractions; +using GSS2.Core.Helpers; using Stream = LibCameraSharp.Stream; @@ -55,17 +57,19 @@ public class CameraService : IDisposable ViewFinderFps = 30, ImageCaptureFrameBufferCount = 2, ImageCaptureWidth = 4056, - ImageCaptureHeight = 3080 + ImageCaptureHeight = 3080, + CameraHardSettings = null }; - public string CameraId { get; set; } = ""; - public int ViewFinderFrameBufferCount { get; set; } - public int ViewFinderWidth { get; set; } - public int ViewFinderHeight { get; set; } - public int ViewFinderFps { get; set; } - public int ImageCaptureFrameBufferCount { get; set; } - public int ImageCaptureWidth { get; set; } - public int ImageCaptureHeight { get; set; } + public string CameraId { get; init; } = ""; + public int ViewFinderFrameBufferCount { get; init; } + public int ViewFinderWidth { get; init; } + public int ViewFinderHeight { get; init; } + public int ViewFinderFps { get; init; } + public int ImageCaptureFrameBufferCount { get; init; } + public int ImageCaptureWidth { get; init; } + public int ImageCaptureHeight { get; init; } + public IConfigurationSection? CameraHardSettings { get; init; } } private bool _disposedValue; @@ -86,6 +90,7 @@ public class CameraService : IDisposable private System.Timers.ElapsedEventHandler? _viewFinderTimerElapsed = null; private EventHandler? _viewFinderRequestComplitedHandler = null; private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource(); + private readonly Dictionary _cameraHardSettings = new Dictionary(); public bool IsImageCapturing { get; private set; } = false; public List ViewFinderBufferQueue { get; private set; } = new List(); @@ -106,6 +111,27 @@ public class CameraService : IDisposable _configuration = configuration; _logger.LogInformation("Using following configuration: \n{}", _configuration.ToString("\n", 4)); + + if (_configuration.CameraHardSettings is not null) + { + foreach (var child in _configuration.CameraHardSettings.GetChildren()) + { + object? value = child.Get(); + if (value is string) + { + _cameraHardSettings.Add(child.Key, value); + continue; + } + value = child.Get(); + if (value is not null) + { + _cameraHardSettings.Add(child.Key, value); + continue; + } + _logger.LogWarning("Cannot serialize camera hard settings value: {}: {}", child.Key, child.Value); + } + } + if (autoInitialize) Initialize(CancellationToken.None); } @@ -279,6 +305,8 @@ public class CameraService : IDisposable throw new Exception($"Error while camera start {result}"); } + _logger.LogInformation("Camera settings: \n{}", GetSettings(4)); + _state |= State.CameraStarted; } private void ViewFinderFrameBuffersAllocate(CancellationToken cancellationToken) @@ -365,7 +393,7 @@ public class CameraService : IDisposable cts.Task.WaitAsync(cancellationToken).GetAwaiter().GetResult(); _camera.RequestCompleted -= RequestCompleted; - _logger.LogDebug("Request complited: \n\tResult: {} \n\tStatus: {}\n\tCookie: {}\n\tSequence: {}\n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers); + _logger.LogDebug("Request complited: \n\tResult: {} \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tHasPendingBuffers: {}", result, request.Status, request.Cookie, request.Sequence, request.HasPendingBuffers); _logger.LogDebug("Request buffer: \n\tStatus: {} \n\tCookie: {} \n\tSequence: {} \n\tTimestamp: {}", buffer.Metadata.Status, buffer.Cookie, buffer.Metadata.Sequence, buffer.Metadata.Timestamp); _logger.LogDebug("Request buffer planes: "); foreach (var (i, plane) in buffer.Metadata.Planes.Enumerate()) @@ -476,6 +504,63 @@ public class CameraService : IDisposable _state |= State.ImageCaptureFrameBuffersMapped; } + private string GetSettings(int indent = 0) + { + if (_camera is null) + throw new InvalidOperationException("Camera not initialized"); + + var controls = _camera.Controls.ToList(); + controls.Sort((c1, c2) => (int)(c1.Key.Id - c2.Key.Id)); + var strs = new List>() + { + {["Id", "Name", "Type", "Direction", "IsArray", "Size", "Range", "Default", "Enumerators"]} + }; + + foreach (var (id, info) in controls) + { + strs.Add([ + id.Id.ToString(), + id.Name.ToString(), + id.Type.ToString(), + id.Direction.ToString(), + id.IsArray.ToString(), + id.Size.ToString(), + $"{info.Min.ToString(true)}...{info.Max.ToString(true)}", + info.Def.ToString(true), + string.Join(", ", id.Enumerators.FirstOrDefault()) + ]); + foreach (var enumerator in id.Enumerators.Skip(1)) + strs.Add([ + "", + "", + "", + "", + "", + "", + "", + "", + string.Join(", ", enumerator) + ]); + } + var maxLengths = new List(); + foreach (var str in strs) + foreach (var (i, s) in str.Enumerate()) + { + if (i >= maxLengths.Count()) + maxLengths.Add(0); + maxLengths[i] = Math.Max(maxLengths[i], s.Length); + } + + var sb = new StringBuilder(); + foreach (var (i, str) in strs.Enumerate()) + { + foreach (var (j, s) in str.Enumerate()) + sb.Append(new string(' ', indent) + string.Format($"{{0,-{maxLengths[j] + 1}}}", s)); + if (i != strs.Count() - 1) + sb.AppendLine(); + } + return sb.ToString(); + } public async Task CaptureImage(CancellationToken cancellationToken, int framesCount = 1) { @@ -510,7 +595,7 @@ public class CameraService : IDisposable while (planesData.Count() < framesCount) { effectiveCancellationToken.ThrowIfCancellationRequested(); - _logger.LogDebug("Requested {} frame(s), now collected {}", framesCount, planesData.Count()); + _logger.LogDebug("Requested {} frame(s), now collected: {}", framesCount, planesData.Count()); var oldPlanesDataCount = planesData.Count(); @@ -524,6 +609,8 @@ public class CameraService : IDisposable foreach (var buffer in buffers) { var request = _camera.CreateRequest(1); + ApplySettings(request, _cameraHardSettings); + var result = request.AddBuffer(_imageCaptureStream, buffer); if (result < 0) throw new Exception("Error while request create"); @@ -669,6 +756,40 @@ public class CameraService : IDisposable IsImageCapturing = false; return image; } + private void ApplySettings(Request request, Dictionary settings) + { + if (_camera is null) + throw new InvalidOperationException("Camera not initialized"); + + foreach (var (id, info) in _camera.Controls) + { + if (!settings.ContainsKey(id.Name)) + continue; + + var value = settings[id.Name]; + ControlValue? controlValue; + try + { + if (!id.IsArray) + controlValue = ControlValueHelper.ConvertToControlValue(value, id, info); + else + controlValue = ControlValueHelper.ConvertToControlArray(value, id, info); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error while converting value (control name:{}, control type:{}, value type:{}, value:{})", id.Name, id.Type, value.GetType(), value); + continue; + } + + if (controlValue is null) + { + _logger.LogWarning("Cannot convert to control value (control name:{}, control type:{}, value type:{}, value:{})", id.Name, id.Type, value.GetType(), value); + continue; + } + + request.Controls.Set(id.Id, controlValue); + } + } public void StartViewFinder() { diff --git a/GSS2.Core/Hardware/IlluminatorService.cs b/GSS2.Core/Hardware/IlluminatorService.cs index c623c83..05b3302 100644 --- a/GSS2.Core/Hardware/IlluminatorService.cs +++ b/GSS2.Core/Hardware/IlluminatorService.cs @@ -28,19 +28,19 @@ public class IlluminatorService : IDisposable MaxUv365PwmDuty = 1.0 }; - public int WhitePwmPin { get; set; } - public int Uv254PwmPin { get; set; } - public int Uv365PwmPin { get; set; } - public int WhiteRelayPin { get; set; } - public int Uv254RelayPin { get; set; } - public int Uv365RelayPin { get; set; } - public int FanPin { get; set; } + public int WhitePwmPin { get; init; } + public int Uv254PwmPin { get; init; } + public int Uv365PwmPin { get; init; } + public int WhiteRelayPin { get; init; } + public int Uv254RelayPin { get; init; } + public int Uv365RelayPin { get; init; } + public int FanPin { get; init; } - public int PwmFrequency { get; set; } + public int PwmFrequency { get; init; } - public double MaxWhitePwmDuty { get; set; } - public double MaxUv254PwmDuty { get; set; } - public double MaxUv365PwmDuty { get; set; } + public double MaxWhitePwmDuty { get; init; } + public double MaxUv254PwmDuty { get; init; } + public double MaxUv365PwmDuty { get; init; } } private static readonly Dictionary PinPwmChips = new() diff --git a/GSS2.Core/Hardware/LightsService.cs b/GSS2.Core/Hardware/LightsService.cs index 6536684..70ff48d 100644 --- a/GSS2.Core/Hardware/LightsService.cs +++ b/GSS2.Core/Hardware/LightsService.cs @@ -20,7 +20,7 @@ public class LightsService ServerAddress = "localhost:50051" }; - public string ServerAddress { get; set; } = string.Empty; + public string ServerAddress { get; init; } = string.Empty; } private readonly ILogger _logger; diff --git a/GSS2.Core/Hardware/TemperatureHumidityService.cs b/GSS2.Core/Hardware/TemperatureHumidityService.cs index e2a92a2..135a016 100644 --- a/GSS2.Core/Hardware/TemperatureHumidityService.cs +++ b/GSS2.Core/Hardware/TemperatureHumidityService.cs @@ -16,8 +16,8 @@ public class TemperatureHumidityService : IDisposable Address = 0x45 }; - public int Bus { get; set; } - public int Address { get; set; } + public int Bus { get; init; } + public int Address { get; init; } } private bool _disposedValue; private readonly ILogger _logger; diff --git a/GSS2.Core/Helpers/ControlValueHelper.cs b/GSS2.Core/Helpers/ControlValueHelper.cs new file mode 100644 index 0000000..6b5b761 --- /dev/null +++ b/GSS2.Core/Helpers/ControlValueHelper.cs @@ -0,0 +1,250 @@ +using System.Collections; + +using LibCameraSharp; + +namespace GSS2.Core.Helpers; + +public static class ControlValueHelper +{ + private static readonly Dictionary> _valueParsers = new() + { + { typeof(bool ), (obj, _) => ParseValue(obj, bool .TryParse) }, + { typeof(byte ), (obj, _) => ParseValue(obj, byte .TryParse) }, + { typeof(float ), (obj, _) => ParseValue(obj, float .TryParse) }, + { typeof(long ), (obj, _) => ParseValue(obj, long .TryParse) }, + { typeof(ushort ), (obj, _) => ParseValue(obj, ushort.TryParse) }, + { typeof(uint ), (obj, _) => ParseValue(obj, uint .TryParse) }, + { typeof(int ), ParseValueInt }, + // int - особый случай потому что он может использовать ControlId.Enumerators для прообразования стокового "enum" в значение + { typeof(Rectangle), ParseValueRectangle }, + { typeof(Size ), ParseValueSize }, + { typeof(Point ), ParseValuePoint } + }; + private delegate bool ParseValueDelegate(string s, out T value); + private static object? ParseValue(object obj, ParseValueDelegate parser) where T : struct + { + if (obj is T value) + return value; + if (obj is string str && parser(str, out var parsed)) + return parsed; + return null; + } + private static object? ParseValueInt(object obj, ControlId id) + { + if (obj is int value) + return value; + if (obj is string str) + { + if (int.TryParse(str, out value)) + return value; + var match = id.Enumerators.FirstOrDefault(e => e.Value == str); + if (!match.Equals(default(KeyValuePair))) + return match.Key; + } + + return null; + } + private static object? ParseValueRectangle(object obj, ControlId id) + { + if (obj is Rectangle value) + return value; + if (obj is IEnumerable enumerable) + { + var list = enumerable.Cast(); + if (list.Count() != 4) + return null; + if (!int.TryParse(list.Skip(0).First(), out var x)) + return null; + if (!int.TryParse(list.Skip(1).First(), out var y)) + return null; + if (!uint.TryParse(list.Skip(2).First(), out var width)) + return null; + if (!uint.TryParse(list.Skip(3).First(), out var height)) + return null; + return new Rectangle(x, y, width, height); + } + return null; + } + private static object? ParseValueSize(object obj, ControlId id) + { + if (obj is Size value) + return value; + if (obj is IEnumerable enumerable) + { + var list = enumerable.Cast(); + if (list.Count() != 2) + return null; + if (!uint.TryParse(list.Skip(0).First(), out var width)) + return null; + if (!uint.TryParse(list.Skip(1).First(), out var height)) + return null; + return new Size(width, height); + } + return null; + } + private static object? ParseValuePoint(object obj, ControlId id) + { + if (obj is Point value) + return value; + if (obj is IEnumerable enumerable) + { + var list = enumerable.Cast(); + if (list.Count() != 4) + return null; + if (!int.TryParse(list.Skip(0).First(), out var x)) + return null; + if (!int.TryParse(list.Skip(1).First(), out var y)) + return null; + return new Point(x, y); + } + return null; + } + private static T? ConvertValueStruct(object obj, ControlId id) where T : struct + { + var type = typeof(T); + + if (!_valueParsers.ContainsKey(type)) + return null; + + var parsed = _valueParsers[type](obj, id); + var result = (T?)parsed; + return result; + } + private static T? ConvertValueClass(object obj, ControlId id) where T : class + { + var type = typeof(T); + + if (!_valueParsers.ContainsKey(type)) + return null; + + var parsed = _valueParsers[type](obj, id); + var result = (T?)parsed; + return result; + } + private static readonly Dictionary> _controlValueConverters = new() + { + { ControlTypeEnum.Bool , (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueBool (v.Value); } }, + { ControlTypeEnum.Byte , (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueByte (v.Value); } }, + { ControlTypeEnum.Float , (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueFloat (v.Value); } }, + { ControlTypeEnum.Integer32 , (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueInteger32 (v.Value); } }, + { ControlTypeEnum.Integer64 , (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueInteger64 (v.Value); } }, + { ControlTypeEnum.Unsigned16, (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueUnsigned16(v.Value); } }, + { ControlTypeEnum.Unsigned32, (obj, id) => { var v = ConvertValueStruct(obj, id); return v is null ? null : new ControlValueUnsigned32(v.Value); } }, + { ControlTypeEnum.Rectangle , (obj, id) => { var v = ConvertValueClass (obj, id); return v is null ? null : new ControlValueRectangle (v); } }, + { ControlTypeEnum.Size , (obj, id) => { var v = ConvertValueClass (obj, id); return v is null ? null : new ControlValueSize (v); } }, + { ControlTypeEnum.Point , (obj, id) => { var v = ConvertValueClass (obj, id); return v is null ? null : new ControlValuePoint (v); } } + }; + private static readonly Dictionary> _controlValueClapers = new() + { + { ControlTypeEnum.Bool , (value, id, info) => { } }, + { ControlTypeEnum.Byte , (value, id, info) => { if (value is ControlValueByte typed && info.Min is ControlValueByte typedMin && info.Max is ControlValueByte typedMax) typed.Set(Math.Clamp(typed.Get(), typedMin.Get(), typedMax.Get())); } }, + { ControlTypeEnum.Float , (value, id, info) => { if (value is ControlValueFloat typed && info.Min is ControlValueFloat typedMin && info.Max is ControlValueFloat typedMax) typed.Set(Math.Clamp(typed.Get(), typedMin.Get(), typedMax.Get())); } }, + { ControlTypeEnum.Integer32 , (value, id, info) => { if (value is ControlValueInteger32 typed && info.Min is ControlValueInteger32 typedMin && info.Max is ControlValueInteger32 typedMax) typed.Set(Math.Clamp(typed.Get(), typedMin.Get(), typedMax.Get())); } }, + { ControlTypeEnum.Integer64 , (value, id, info) => { if (value is ControlValueInteger64 typed && info.Min is ControlValueInteger64 typedMin && info.Max is ControlValueInteger64 typedMax) typed.Set(Math.Clamp(typed.Get(), typedMin.Get(), typedMax.Get())); } }, + { ControlTypeEnum.Unsigned16, (value, id, info) => { if (value is ControlValueUnsigned16 typed && info.Min is ControlValueUnsigned16 typedMin && info.Max is ControlValueUnsigned16 typedMax) typed.Set(Math.Clamp(typed.Get(), typedMin.Get(), typedMax.Get())); } }, + { ControlTypeEnum.Unsigned32, (value, id, info) => { if (value is ControlValueUnsigned32 typed && info.Min is ControlValueUnsigned32 typedMin && info.Max is ControlValueUnsigned32 typedMax) typed.Set(Math.Clamp(typed.Get(), typedMin.Get(), typedMax.Get())); } } + }; + public static ControlValue? ConvertToControlValue(object obj, ControlId id, ControlInfo info) + { + if (!_controlValueConverters.TryGetValue(id.Type, out var converter)) + return null; + var controlValue = converter(obj, id); + if (controlValue is null) + return null; + if (!_controlValueClapers.TryGetValue(id.Type, out var clamper)) + return controlValue; + clamper(controlValue, id, info); + return controlValue; + } + + private static readonly Dictionary> _arrayParsers = new() + { + { typeof(bool ), ParseArray }, + { typeof(byte ), ParseArray }, + { typeof(float ), ParseArray }, + { typeof(long ), ParseArray }, + { typeof(ushort ), ParseArray }, + { typeof(uint ), ParseArray }, + { typeof(int ), ParseArray }, + { typeof(Rectangle), ParseArray}, + { typeof(Size ), ParseArray }, + { typeof(Point ), ParseArray } + }; + private static object? ParseArray(object obj, ControlId id) + { + if (obj is not IEnumerable enumerable) + return null; + + var list = enumerable.Cast().ToList(); + + if ((ulong)list.Count != id.Size) + return null; + + var array = new T[list.Count]; + + for (int i = 0; i < list.Count; i++) + { + var parsed = _valueParsers[typeof(T)](list[i], id); + if (parsed is null) + return null; + + array[i] = (T)parsed; + } + + return array; + } + private static IEnumerable? ConvertArray(object obj, ControlId id) + { + var type = typeof(T); + + if (!_arrayParsers.ContainsKey(type)) + return null; + + var parsed = _arrayParsers[type](obj, id); + var result = (T[]?)parsed; + return result; + } + private static readonly Dictionary> _controlArrayConverters = new() + { + { ControlTypeEnum.Bool , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueBoolSpan (v); } }, + { ControlTypeEnum.Byte , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueByteSpan (v); } }, + { ControlTypeEnum.Float , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueFloatSpan (v); } }, + { ControlTypeEnum.Integer32 , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueInteger32Span (v); } }, + { ControlTypeEnum.Integer64 , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueInteger64Span (v); } }, + { ControlTypeEnum.Unsigned16, (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueUnsigned16Span(v); } }, + { ControlTypeEnum.Unsigned32, (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueUnsigned32Span(v); } }, + { ControlTypeEnum.Rectangle , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueRectangleSpan (v); } }, + { ControlTypeEnum.Size , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValueSizeSpan (v); } }, + { ControlTypeEnum.Point , (obj, id) => { var v = ConvertArray(obj, id); return v is null ? null : new ControlValuePointSpan (v); } } + }; + private static readonly Dictionary> _controlArrayClapers = new() + { + { ControlTypeEnum.Bool , (value, id, info) => { } }, + { ControlTypeEnum.Byte , (value, id, info) => { if (value is ControlValueByteSpan typed && info.Min is ControlValueByte typedMin && info.Max is ControlValueByte typedMax) typed.Set(typed.Get().Select(v => Math.Clamp(v, typedMin.Get(), typedMax.Get()))); } }, + { ControlTypeEnum.Float , (value, id, info) => { if (value is ControlValueFloatSpan typed && info.Min is ControlValueFloat typedMin && info.Max is ControlValueFloat typedMax) typed.Set(typed.Get().Select(v => Math.Clamp(v, typedMin.Get(), typedMax.Get()))); } }, + { ControlTypeEnum.Integer32 , (value, id, info) => { if (value is ControlValueInteger32Span typed && info.Min is ControlValueInteger32 typedMin && info.Max is ControlValueInteger32 typedMax) typed.Set(typed.Get().Select(v => Math.Clamp(v, typedMin.Get(), typedMax.Get()))); } }, + { ControlTypeEnum.Integer64 , (value, id, info) => { if (value is ControlValueInteger64Span typed && info.Min is ControlValueInteger64 typedMin && info.Max is ControlValueInteger64 typedMax) typed.Set(typed.Get().Select(v => Math.Clamp(v, typedMin.Get(), typedMax.Get()))); } }, + { ControlTypeEnum.Unsigned16, (value, id, info) => { if (value is ControlValueUnsigned16Span typed && info.Min is ControlValueUnsigned16 typedMin && info.Max is ControlValueUnsigned16 typedMax) typed.Set(typed.Get().Select(v => Math.Clamp(v, typedMin.Get(), typedMax.Get()))); } }, + { ControlTypeEnum.Unsigned32, (value, id, info) => { if (value is ControlValueUnsigned32Span typed && info.Min is ControlValueUnsigned32 typedMin && info.Max is ControlValueUnsigned32 typedMax) typed.Set(typed.Get().Select(v => Math.Clamp(v, typedMin.Get(), typedMax.Get()))); } }, + { ControlTypeEnum.Rectangle , (value, id, info) => { if (value is ControlValueRectangleSpan typed && info.Min is ControlValueRectangle typedMin && info.Max is ControlValueRectangle typedMax) typed.Set(typed.Get().Select(v => new Rectangle(Math.Clamp(v.X, typedMin.Get().X, typedMax.Get().X ), + Math.Clamp(v.Y, typedMin.Get().Y, typedMax.Get().Y ), + Math.Clamp(v.Width, typedMin.Get().Width, typedMax.Get().Width ), + Math.Clamp(v.Height, typedMin.Get().Height, typedMax.Get().Height)))); } }, + { ControlTypeEnum.Size , (value, id, info) => { if (value is ControlValueSizeSpan typed && info.Min is ControlValueSize typedMin && info.Max is ControlValueSize typedMax) typed.Set(typed.Get().Select(v => new Size( Math.Clamp(v.Width, typedMin.Get().Width, typedMax.Get().Width ), + Math.Clamp(v.Height, typedMin.Get().Height, typedMax.Get().Height)))); } }, + { ControlTypeEnum.Point , (value, id, info) => { if (value is ControlValuePointSpan typed && info.Min is ControlValuePoint typedMin && info.Max is ControlValuePoint typedMax) typed.Set(typed.Get().Select(v => new Point( Math.Clamp(v.X, typedMin.Get().X, typedMax.Get().X ), + Math.Clamp(v.Y, typedMin.Get().Y, typedMax.Get().Y )))); } }, + }; + public static ControlValue? ConvertToControlArray(object obj, ControlId id, ControlInfo info) + { + if (!_controlArrayConverters.ContainsKey(id.Type)) + return null; + var controlValue = _controlArrayConverters[id.Type](obj, id); + if (controlValue is null) + return null; + if (!_controlArrayClapers.TryGetValue(id.Type, out var clamper)) + return controlValue; + clamper(controlValue, id, info); + return controlValue; + } +} \ No newline at end of file diff --git a/GSS2.Core/Logging/LibCameraLogSink.cs b/GSS2.Core/Logging/LibCameraLogSink.cs index 8734ee4..d7be2ba 100644 --- a/GSS2.Core/Logging/LibCameraLogSink.cs +++ b/GSS2.Core/Logging/LibCameraLogSink.cs @@ -13,6 +13,7 @@ public sealed class LibCameraLogSink : IDisposable private readonly ILogger _loggerV4L2; private readonly ILogger _loggerRPISTREAM; private readonly ILogger _loggerWRAPPER; + private readonly ILogger _loggerRPiAgc; private bool _disposedValue; public LibCameraLogSink(ILoggerFactory loggerFactory) @@ -25,6 +26,7 @@ public sealed class LibCameraLogSink : IDisposable _loggerV4L2 = loggerFactory.CreateLogger("LibCamera-V4L2"); _loggerRPISTREAM = loggerFactory.CreateLogger("LibCamera-RPISTREAM"); _loggerWRAPPER = loggerFactory.CreateLogger("LibCamera-WRAPPER"); + _loggerRPiAgc = loggerFactory.CreateLogger("LibCamera-RPiAgc"); Log.Initialize(); Log.SetLevel("Camera", Log.LogLevelEnum.DEBUG); @@ -34,6 +36,7 @@ public sealed class LibCameraLogSink : IDisposable Log.SetLevel("V4L2", Log.LogLevelEnum.DEBUG); Log.SetLevel("RPISTREAM", Log.LogLevelEnum.DEBUG); Log.SetLevel("WRAPPER", Log.LogLevelEnum.DEBUG); + Log.SetLevel("RPiAgc", Log.LogLevelEnum.DEBUG); Log.LogReceived += OnLogReceived; } @@ -55,6 +58,7 @@ public sealed class LibCameraLogSink : IDisposable "V4L2" => _loggerV4L2, "RPISTREAM" => _loggerRPISTREAM, "WRAPPER" => _loggerWRAPPER, + "RPiAgc" => _loggerRPiAgc, _ => null }; diff --git a/GSS2.Test/ViewModels/MainViewModel.cs b/GSS2.Test/ViewModels/MainViewModel.cs index 21cd7d0..0439c12 100644 --- a/GSS2.Test/ViewModels/MainViewModel.cs +++ b/GSS2.Test/ViewModels/MainViewModel.cs @@ -10,9 +10,13 @@ public partial class MainViewModel : ViewModelBase { private readonly ILogger _logger; private readonly CameraService _cameraService; + private readonly IlluminatorService _illuminatorService; [ObservableProperty] private CameraViewModel _cameraViewModel; [ObservableProperty] private ResourcesViewModel _resourcesViewModel; - [ObservableProperty] private OpenCvSharp.Mat? _image; + [ObservableProperty] private OpenCvSharp.Mat? _image1; + [ObservableProperty] private OpenCvSharp.Mat? _image2; + [ObservableProperty] private OpenCvSharp.Mat? _image3; + [ObservableProperty] private OpenCvSharp.Mat? _image4; [ObservableProperty] private IRelayCommand _buttonCaptureClick; [ObservableProperty] private IRelayCommand _buttonUpClick; [ObservableProperty] private IRelayCommand _buttonDownClick; @@ -20,15 +24,29 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _canScrollForward; [ObservableProperty] private bool _canScrollBackward; - public MainViewModel(ILogger logger, CameraViewModel cameraViewModel, ResourcesViewModel resourcesViewModel, CameraService cameraService) + public MainViewModel(ILogger logger, CameraViewModel cameraViewModel, ResourcesViewModel resourcesViewModel, CameraService cameraService, IlluminatorService illuminatorService) { _logger = logger; _cameraService = cameraService; + _illuminatorService = illuminatorService; CameraViewModel = cameraViewModel; ResourcesViewModel = resourcesViewModel; ButtonCaptureClick = new RelayCommand(() => { - Task.Run(() => cameraService.CaptureImage(CancellationToken.None, 10).ContinueWith(image => Image = image.Result)); + Task.Run(async () => + { + _illuminatorService.TurnOn(); + _illuminatorService.SetIntensity(1, 0, 0); + await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image1 = image.Result); + _illuminatorService.SetIntensity(0, 1, 1); + await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image2 = image.Result); + _illuminatorService.SetIntensity(0, 1, 0); + await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image3 = image.Result); + _illuminatorService.SetIntensity(0, 0, 1); + await _cameraService.CaptureImage(CancellationToken.None, 3).ContinueWith(image => Image4 = image.Result); + _illuminatorService.SetIntensity(0, 0, 0); + _illuminatorService.TurnOff(); + }); }); ButtonUpClick = new RelayCommand(() => CurrentIndex--); ButtonDownClick = new RelayCommand(() => CurrentIndex++); diff --git a/GSS2.Test/Views/MainView.axaml b/GSS2.Test/Views/MainView.axaml index 1e3e9be..1e74a7a 100644 --- a/GSS2.Test/Views/MainView.axaml +++ b/GSS2.Test/Views/MainView.axaml @@ -10,12 +10,17 @@