comprehensive update

1) remove unused components: camera view (due it causes segmentation faults), compute resources and all related, illuminator controller (due in useless without camera view), image storage service, temperature and humidity service
2) database schema - reduce tables references, features storing in records themselves in compressed form, add records creation and editing date and time, add separator comment column
3) analysis - rework of pipeline and ui, now database storing only raw data and all display values calculated from it
4) lights service - add reconnection if disconnected
5) add width and height command line arguments
6) fix some typos and other issues
This commit is contained in:
2026-06-29 15:13:29 +03:00
parent caee9fafd5
commit 72ae26eee7
74 changed files with 5219 additions and 4248 deletions
+3 -243
View File
@@ -41,7 +41,6 @@ public class CameraService : IDisposable
CameraManagerStarted = 1 << 1,
CameraAcquired = 1 << 2,
CameraStarted = 1 << 3,
ViewFinderFrameBufferAllocated = 1 << 4,
ImageCaptureFrameBufferAllocated = 1 << 5,
ImageCaptureFrameBuffersMapped = 1 << 6
}
@@ -51,10 +50,6 @@ public class CameraService : IDisposable
public static CameraServiceConfiguration Default => new CameraServiceConfiguration
{
CameraId = "",
ViewFinderFrameBufferCount = 5,
ViewFinderWidth = 4056,
ViewFinderHeight = 3080,
ViewFinderFps = 30,
ImageCaptureFrameBufferCount = 2,
ImageCaptureWidth = 4056,
ImageCaptureHeight = 3080,
@@ -66,10 +61,6 @@ public class CameraService : IDisposable
};
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; }
@@ -87,16 +78,11 @@ public class CameraService : IDisposable
private CameraManager? _cameraManager;
private Camera? _camera;
private CameraConfiguration? _cameraConfiguration;
private StreamConfiguration? _viewFinderStreamConfiguration;
private StreamConfiguration? _imageCaptureStreamConfiguration;
private FrameBufferAllocator? _viewFinderFrameBufferAllocator;
private FrameBufferAllocator? _imageCaptureFrameBufferAllocator;
private Stream? _viewFinderStream;
private Stream? _imageCaptureStream;
private List<MappedBuffer>? _imageCaptureMappedBuffers;
private System.Timers.Timer? _viewFinderTimer = null;
private System.Timers.ElapsedEventHandler? _viewFinderTimerElapsed = null;
private EventHandler<Camera.RequestCompletedEventArgs>? _viewFinderRequestCompletedHandler = null;
private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
private readonly Dictionary<string, object> _cameraHardSettings = new Dictionary<string, object>();
@@ -176,10 +162,8 @@ public class CameraService : IDisposable
CameraSelect(effectiveCancellationToken);
CameraAcquire(effectiveCancellationToken);
CameraConfigure(effectiveCancellationToken);
ViewFinderFrameBuffersAllocate(effectiveCancellationToken);
ImageCaptureFrameBuffersAllocate(effectiveCancellationToken);
CameraStart(effectiveCancellationToken);
ViewFinderFrameBuffersInitialize(effectiveCancellationToken);
ImageCaptureFrameBuffersInitialize(effectiveCancellationToken);
ImageCaptureFrameBuffersMap(effectiveCancellationToken);
}
@@ -251,16 +235,8 @@ public class CameraService : IDisposable
cancellationToken.ThrowIfCancellationRequested();
_cameraConfiguration = _camera.GenerateConfiguration(
StreamRoleEnum.Viewfinder,
StreamRoleEnum.Raw);
_cameraConfiguration = _camera.GenerateConfiguration(StreamRoleEnum.Raw);
_viewFinderStreamConfiguration = _cameraConfiguration.First();
_viewFinderStreamConfiguration.BufferCount = checked((uint)_configuration.ViewFinderFrameBufferCount);
_viewFinderStreamConfiguration.Size = new Size(
checked((uint)_configuration.ViewFinderWidth),
checked((uint)_configuration.ViewFinderHeight)
);
_imageCaptureStreamConfiguration = _cameraConfiguration.Skip(1).First();
_imageCaptureStreamConfiguration.BufferCount = checked((uint)_configuration.ImageCaptureFrameBufferCount);
@@ -275,13 +251,6 @@ public class CameraService : IDisposable
throw new Exception("Error while camera configuration validation");
}
_logger.LogDebug(@$"ViewFinder stream configuration:
Size: {_viewFinderStreamConfiguration.Size.ToString(true)}
FrameSize: {_viewFinderStreamConfiguration.FrameSize}
Stride: {_viewFinderStreamConfiguration.Stride}
ColorSpace: {_viewFinderStreamConfiguration.ColorSpace?.ToString(true)}
PixelFormat: {_viewFinderStreamConfiguration.PixelFormat?.ToString(true)}");
_logger.LogDebug(@$"ImageCapture stream configuration:
Size: {_imageCaptureStreamConfiguration.Size.ToString(true)}
FrameSize: {_imageCaptureStreamConfiguration.FrameSize}
@@ -298,7 +267,6 @@ public class CameraService : IDisposable
throw new Exception($"Error while camera configuration {result}");
}
_viewFinderStream = _viewFinderStreamConfiguration.Stream();
_imageCaptureStream = _imageCaptureStreamConfiguration.Stream();
}
private void CameraStart(CancellationToken cancellationToken)
@@ -320,28 +288,6 @@ public class CameraService : IDisposable
_state |= State.CameraStarted;
}
private void ViewFinderFrameBuffersAllocate(CancellationToken cancellationToken)
{
if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
if (_viewFinderStream is null)
throw new InvalidOperationException("ViewFinderStream not initialized");
cancellationToken.ThrowIfCancellationRequested();
_viewFinderFrameBufferAllocator = new FrameBufferAllocator(_camera);
cancellationToken.ThrowIfCancellationRequested();
int result = _viewFinderFrameBufferAllocator.Allocate(_viewFinderStream);
if (result < 0)
{
_logger.LogCritical("Error while view finder frame buffers allocation {Result}", result);
throw new Exception($"Error while view finder frame buffers allocation {result}");
}
_state |= State.ViewFinderFrameBufferAllocated;
}
private void ImageCaptureFrameBuffersAllocate(CancellationToken cancellationToken)
{
if (_camera is null)
@@ -364,55 +310,6 @@ public class CameraService : IDisposable
_state |= State.ImageCaptureFrameBufferAllocated;
}
private void ViewFinderFrameBuffersInitialize(CancellationToken cancellationToken)
{
if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
if (_viewFinderStream is null)
throw new InvalidOperationException("ViewFinderStream not initialized");
if (_viewFinderFrameBufferAllocator is null)
throw new InvalidOperationException("ViewFinderFrameBufferAllocator not initialized");
cancellationToken.ThrowIfCancellationRequested();
var buffers = _viewFinderFrameBufferAllocator.Buffers(_viewFinderStream);
_logger.LogDebug("View finder frame buffers initialization");
foreach (var buffer in buffers)
{
cancellationToken.ThrowIfCancellationRequested();
var request = _camera.CreateRequest(0);
var result = request.AddBuffer(_viewFinderStream, buffer);
if (result < 0)
throw new Exception("Error while request create");
var cts = new TaskCompletionSource();
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs e)
{
if (e.Request.Handle == request.Handle)
cts.SetResult();
}
_camera.RequestCompleted += RequestCompleted;
result = _camera.QueueRequest(request);
if (result < 0)
{
_camera.RequestCompleted -= RequestCompleted;
throw new Exception("Error while request queue");
}
cts.Task.WaitAsync(cancellationToken).GetAwaiter().GetResult();
_camera.RequestCompleted -= RequestCompleted;
_logger.LogDebug("Request completed: \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())
_logger.LogDebug("Plane[{}]: bytes used: 0x{:X}", i, plane.BytesUsed);
foreach (var (i, plane) in buffer.Planes.Enumerate())
_logger.LogDebug("Plane[{}]: \n\tFd: 0x{:X} \n\tOffset: 0x{:X} \n\tLength: 0x{:X} \n\tKInvalidOffset: 0x{:X}", i, plane.Fd.Get(), plane.Offset, plane.Length, plane.KInvalidOffset);
}
}
private void ImageCaptureFrameBuffersInitialize(CancellationToken cancellationToken)
{
if (_camera is null)
@@ -826,135 +723,6 @@ public class CameraService : IDisposable
return image;
}
public void StartViewFinder()
{
if (_camera is null)
throw new InvalidOperationException("Camera not initialized");
if (_viewFinderStream is null)
throw new InvalidOperationException("ViewFinderStream not initialized");
if (_viewFinderStreamConfiguration is null)
throw new InvalidOperationException("ViewFinderStreamConfiguration not initialized");
if (_viewFinderFrameBufferAllocator is null)
throw new InvalidOperationException("ViewFinderFrameBufferAllocator not initialized");
if (_viewFinderTimer is not null ||
_viewFinderTimerElapsed is not null ||
_viewFinderRequestCompletedHandler is not null)
throw new InvalidOperationException();
ViewFinderStarted = true;
_viewFinderTimer = new System.Timers.Timer()
{
Interval = 1000 / _configuration.ViewFinderFps,
AutoReset = true,
};
try
{
var buffers = _viewFinderFrameBufferAllocator.Buffers(_viewFinderStream);
lock (ViewFinderBufferQueue)
foreach (var buffer in buffers)
ViewFinderBufferQueue.Add(buffer);
void RequestCompleted(object? sender, Camera.RequestCompletedEventArgs ea)
{
if (ea.Request.Cookie != 2)
return;
var buffer = ea.Request.FindBuffer(_viewFinderStream);
// _logger.LogInformation("Completed request for frame buffer {}", buffer);
lock (ViewFinderBufferQueue)
ViewFinderBufferQueue.Add(buffer);
}
_viewFinderRequestCompletedHandler = RequestCompleted;
_camera.RequestCompleted += _viewFinderRequestCompletedHandler;
void TimerElapsed(object? sender, System.Timers.ElapsedEventArgs ea)
{
if (IsImageCapturing)
return;
try
{
FrameBuffer buffer;
lock (ViewFinderBufferQueue)
{
if (ViewFinderBufferQueue.Count() > 0 && (
ViewFinderBufferQueue.First().Request.Cookie != 2 ||
ViewFinderBufferQueue.First().Request.Status == Request.StatusEnum.RequestComplete ||
ViewFinderBufferQueue.First().Request.Status == Request.StatusEnum.RequestCancelled))
{
buffer = ViewFinderBufferQueue.First();
ViewFinderBufferQueue.Remove(buffer);
}
else
return;
}
var request = _camera.CreateRequest(2);
request.AddBuffer(_viewFinderStream, buffer);
_camera.QueueRequest(request);
// _logger.LogInformation("Queued request for frame buffer {}", buffer);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while ViewFinder tick");
}
}
_viewFinderTimerElapsed = TimerElapsed;
_viewFinderTimer.Elapsed += _viewFinderTimerElapsed;
_viewFinderTimer.Start();
_logger.LogInformation("ViewFinder started");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while ViewFinder start");
_viewFinderTimer.Stop();
_viewFinderTimer.Dispose();
_viewFinderTimer = null;
_viewFinderTimerElapsed = null;
_camera.RequestCompleted -= _viewFinderRequestCompletedHandler;
_viewFinderRequestCompletedHandler = null;
lock (ViewFinderBufferQueue)
ViewFinderBufferQueue.Clear();
}
}
public void StopViewFinder()
{
ViewFinderStarted = false;
_viewFinderTimer?.Stop();
_viewFinderTimer?.Dispose();
_viewFinderTimer = null;
_viewFinderTimerElapsed = null;
_camera?.RequestCompleted -= _viewFinderRequestCompletedHandler;
_viewFinderRequestCompletedHandler = null;
lock (ViewFinderBufferQueue)
ViewFinderBufferQueue.Clear();
}
public (FrameBuffer, StreamConfiguration)? GrabViewFinderFrame()
{
if (IsImageCapturing)
return null;
FrameBuffer? buffer = null;
lock (ViewFinderBufferQueue)
if (ViewFinderBufferQueue.Count() > 0)
{
buffer = ViewFinderBufferQueue.LastOrDefault();
if (buffer is not null)
ViewFinderBufferQueue.Remove(buffer);
}
if (buffer is not null && _viewFinderStreamConfiguration is not null)
return (buffer, _viewFinderStreamConfiguration);
return null;
}
public void ReturnViewFinderFrame(FrameBuffer frameBuffer)
{
lock (ViewFinderBufferQueue)
ViewFinderBufferQueue.Insert(0, frameBuffer);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
@@ -986,8 +754,6 @@ public class CameraService : IDisposable
}
_imageCaptureMappedBuffers.Clear();
}
if (_state.HasFlag(State.ViewFinderFrameBufferAllocated) && _viewFinderStream is not null)
_viewFinderFrameBufferAllocator?.Free(_viewFinderStream);
if (_state.HasFlag(State.ImageCaptureFrameBufferAllocated) && _imageCaptureStream is not null)
_imageCaptureFrameBufferAllocator?.Free(_imageCaptureStream);
if (_state.HasFlag(State.CameraStarted))
@@ -997,23 +763,17 @@ public class CameraService : IDisposable
if (_state.HasFlag(State.CameraManagerStarted))
_cameraManager?.Stop();
// _viewFinderStream?.Dispose();
// _imageCaptureStream?.Dispose();
// _viewFinderFrameBufferAllocator?.Dispose();
// _imageCaptureFrameBufferAllocator?.Dispose();
_cameraManager?.Dispose();
}
catch (Exception e)
catch (Exception ex)
{
_logger.LogError(e, "Error while CameraService cleanup");
_logger.LogError(ex, "Error while CameraService cleanup");
}
finally
{
_cameraManager = null;
_camera = null;
_viewFinderFrameBufferAllocator = null;
_imageCaptureFrameBufferAllocator = null;
_viewFinderStream = null;
_imageCaptureStream = null;
_imageCaptureMappedBuffers = null;
_state = State.NotInitialized;
@@ -1,143 +0,0 @@
using System.Globalization;
using GSS2.Core.Extensions;
using Microsoft.Extensions.Logging;
namespace GSS2.Core.Hardware;
public class ComputeResourcesService
{
public record CpuUsage(double AllUsagePercent, List<double> CoreUsagePercents);
public record CpuTemperature(double Current, double Critical);
public record MemoryUsage(
long TotalB, long UsedB, long AvailableB,
double TotalkB, double UsedkB, double AvailablekB,
double TotalMB, double UsedMB, double AvailableMB,
double TotalGB, double UsedGB, double AvailableGB)
{
public MemoryUsage(long totalB, long usedB, long availableB) :
this(
TotalB: totalB, UsedB: usedB, AvailableB: availableB,
TotalkB: totalB / 1000, UsedkB: usedB / 1000, AvailablekB: availableB / 1000,
TotalMB: totalB / Math.Pow(1000.0, 2), UsedMB: usedB / Math.Pow(1000.0, 2), AvailableMB: availableB / Math.Pow(1000.0, 2),
TotalGB: totalB / Math.Pow(1000.0, 3), UsedGB: usedB / Math.Pow(1000.0, 3), AvailableGB: availableB / Math.Pow(1000.0, 3)
)
{ }
}
public record ComputeResourcesSnapshot(CpuUsage CpuUsage, MemoryUsage MemoryUsage, CpuTemperature CpuTemperature, DateTime Timestamp);
private readonly ILogger<ComputeResourcesService> _logger;
private (long idle, long total)? _prevAll = null;
private Dictionary<int, (long idle, long total)?>? _prevCores = null;
public ComputeResourcesService(ILogger<ComputeResourcesService> logger)
{
_logger = logger;
_logger.LogInformation("Initialization");
_logger.LogInformation("Initialized");
}
public ComputeResourcesSnapshot GetSnapshot()
{
return new ComputeResourcesSnapshot(
CpuUsage: GetCpuUsage(),
CpuTemperature: GetCpuTemperature(),
MemoryUsage: GetMemoryUsage(),
Timestamp: DateTime.UtcNow
);
}
private CpuUsage GetCpuUsage()
{
static (double usage, long idle, long total) ParseLines(string line, (long idle, long total)? prev)
{
var parts = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
long user = long.Parse(parts[1]);
long nice = long.Parse(parts[2]);
long system = long.Parse(parts[3]);
long idle = long.Parse(parts[4]);
long iowait = line.Length > 5 ? long.Parse(parts[5]) : 0;
long irq = line.Length > 6 ? long.Parse(parts[6]) : 0;
long softirq = line.Length > 7 ? long.Parse(parts[7]) : 0;
long idleTime = idle + iowait;
long totalTime = idleTime + user + nice + system + irq + softirq;
double usage = 0;
if (prev is not null)
{
long totalDiff = totalTime - prev.Value.total;
long idleDiff = idleTime - prev.Value.idle;
if (totalDiff > 0)
usage = (double)(totalDiff - idleDiff) / totalDiff * 100.0;
}
return (usage, idleTime, totalTime);
}
var lines = File.ReadLines("/proc/stat").Where(l => l.StartsWith("cpu"));
if (lines.Count() == 0)
return new CpuUsage(0, new List<double>());
var (allUsage, allIdle, allTotal) = ParseLines(lines.First(), _prevAll);
_prevAll = (allIdle, allTotal);
var coreUsages = new List<double>();
foreach (var (i, line) in lines.Skip(1).Enumerate())
{
if (_prevCores is null)
_prevCores = new Dictionary<int, (long idle, long total)?>();
if (!_prevCores.ContainsKey(i))
_prevCores.Add(i, null);
var (coreUsage, coreIdle, coreTotal) = ParseLines(line, _prevCores[i]);
_prevCores[i] = (coreIdle, coreTotal);
coreUsages.Add(coreUsage);
}
return new CpuUsage(
AllUsagePercent: allUsage,
CoreUsagePercents: coreUsages
);
}
private CpuTemperature GetCpuTemperature()
{
var currentStr = File.ReadAllText("/sys/class/thermal/thermal_zone0/temp").Trim();
var current = int.Parse(currentStr) / 1000.0;
var criticalFile = Directory.GetFiles("/sys/class/thermal/thermal_zone0/", "trip_point_*_type")
.FirstOrDefault(f => File.ReadAllText(f).Trim() == "critical")?
.Replace("_type", "_temp");
double critical = 100.0;
if (criticalFile is not null)
{
var criticalStr = File.ReadAllText(criticalFile).Trim();
critical = int.Parse(criticalStr) / 1000.0;
}
return new CpuTemperature(
Current: current,
Critical: critical
);
}
private MemoryUsage GetMemoryUsage()
{
var memInfo = File.ReadAllLines("/proc/meminfo")
.Select(l => l.Split(':', 2))
.ToDictionary(
p => p[0],
p => long.Parse(p[1].Trim().Split(' ')[0], CultureInfo.InvariantCulture) * 1024
);
long total = memInfo["MemTotal"];
long available = memInfo.ContainsKey("MemAvailable") ? memInfo["MemAvailable"] : memInfo["MemFree"];
long used = total - available;
return new MemoryUsage(totalB: total, usedB: used, availableB: available);
}
}
+8 -2
View File
@@ -38,12 +38,18 @@ public class LightsService
if (configuration is null)
{
configuration = LightsServiceConfiguration.Default;
_logger.LogWarning("Configuration not set. Using default configuration.");
}
_configuration = configuration;
_logger.LogInformation("Using following configuration: \n{}", _configuration.ToString("\n", 4));
_client = new Lights.LightsClient(GrpcChannel.ForAddress(_configuration.ServerAddress));
var channelOptions = new GrpcChannelOptions
{
Credentials = ChannelCredentials.Insecure,
HttpHandler = new SocketsHttpHandler(),
MaxReconnectBackoff = TimeSpan.FromSeconds(10)
};
_client = new Lights.LightsClient(GrpcChannel.ForAddress(_configuration.ServerAddress, channelOptions));
}
catch (Exception e)
{
@@ -1,172 +0,0 @@
using System.Device.I2c;
using Microsoft.Extensions.Logging;
using GSS2.Core.Abstractions;
namespace GSS2.Core.Hardware;
public class TemperatureHumidityService : IDisposable
{
public record TemperatureHumidityServiceConfiguration : ServiceConfigurationBase
{
public static TemperatureHumidityServiceConfiguration Default => new TemperatureHumidityServiceConfiguration
{
Bus = 6,
Address = 0x45
};
public int Bus { get; init; }
public int Address { get; init; }
}
private bool _disposedValue;
private readonly ILogger<TemperatureHumidityService> _logger;
private readonly TemperatureHumidityServiceConfiguration _configuration;
private readonly I2cDevice _sensor;
public TemperatureHumidityService(ILogger<TemperatureHumidityService> logger, TemperatureHumidityServiceConfiguration? configuration = null)
{
_logger = logger;
_logger.LogInformation("Initialization");
try
{
if (configuration is null)
{
configuration = TemperatureHumidityServiceConfiguration.Default;
_logger.LogWarning("Configuration not set. Using default configuration.");
}
_configuration = configuration;
_logger.LogInformation("Using following configuration: \n{}", _configuration.ToString("\n", 4));
_sensor = I2cDevice.Create(new I2cConnectionSettings(_configuration.Bus, _configuration.Address));
}
catch (Exception e)
{
_logger.LogCritical(e, "An exception occurred while initialization");
throw;
}
_logger.LogInformation("Initialized");
}
public (double temperature, double relativeHumidity) Measure(int measurementsCount = 1, bool checkCrc = true, CancellationToken cancellationToken = default)
{
// В соответствии с даташитом на SHT45-AD1B-R2 псевдокод измерения с высокой точностью следующий:
// i2c_write(i2c_addr=0x44, tx_bytes=[0xFD])
// wait_seconds(0.01)
// rx_bytes = i2c_read(i2c_addr=0x44, number_of_bytes=6)
// t_ticks = rx_bytes[0] * 256 + rx_bytes[1]
// checksum_t = rx_bytes[2]
// rh_ticks = rx_bytes[3] * 256 + rx_bytes[4]
// checksum_rh = rx_bytes[5]
// t_degC = -45 + 175 * t_ticks/65535
// rh_pRH = -6 + 125 * rh_ticks/65535
// if (rh_pRH > 100):
// rh_pRH = 100
// if (rh_pRH < 0):
// rh_pRH = 0
if (measurementsCount <= 0)
throw new ArgumentOutOfRangeException(nameof(measurementsCount));
double sumTemperature = 0;
double sumRelativeHumidity = 0;
int successMeasurementsCount = 0;
Span<byte> command = stackalloc byte[] { 0xFD };
Span<byte> data = stackalloc byte[6];
for (int i = 0; i < measurementsCount; i++)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
// Отправляем команду
_sensor.Write(command);
// 10 мс ожидания
Task.Delay(10, cancellationToken);
// Читаем данные
_sensor.Read(data);
// Распаковываем данные [2 * 8-bit T-data; 8-bit CRC; 2 * 8-bit RH-data; 8-bit CRC]
byte temperatureChecksum = data[2];
byte relativeHumidityChecksum = data[5];
ushort temperatureTicks = BitConverter.ToUInt16(data.Slice(0, 2));
int relativeHumidityTicks = BitConverter.ToUInt16(data.Slice(3, 2));
// Проверка по CRC
if (checkCrc && (!CheckCrc(data.Slice(0, 2), temperatureChecksum) ||
!CheckCrc(data.Slice(3, 2), relativeHumidityChecksum)))
{
_logger.LogWarning("Temperature and humidity sensor CRC error");
continue;
}
// Пересчёт
double temperature = -45 + 175 * (temperatureTicks / 65535.0);
double relativeHumidity = -6 + 125 * (relativeHumidityTicks / 65535.0);
relativeHumidity = Math.Clamp(relativeHumidity, 0, 100);
// Сумма для усреднения
sumTemperature += temperature;
sumRelativeHumidity += relativeHumidity;
successMeasurementsCount++;
}
catch (Exception e)
{
_logger.LogWarning(e, "Temperature and humidity sensor not available on I2C bus");
}
}
if (successMeasurementsCount == 0)
{
_logger.LogError("Temperature and humidity sensor: no successful measurements");
return (double.NaN, double.NaN);
}
return (sumTemperature / successMeasurementsCount, sumRelativeHumidity / successMeasurementsCount);
}
private static bool CheckCrc(Span<byte> data, byte expectedCrc)
{
byte crc = 0xFF;
foreach (var b in data)
{
crc ^= b;
for (int i = 0; i < 8; i++)
{
if ((crc & 0x80) != 0)
crc = (byte)((crc << 1) ^ 0x31);
else
crc <<= 1;
}
}
return crc == expectedCrc;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
try
{
_sensor?.Dispose();
}
catch { }
}
_disposedValue = true;
}
}
void IDisposable.Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}