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 _logger; private readonly TemperatureHumidityServiceConfiguration _configuration; private readonly I2cDevice _sensor; public TemperatureHumidityService(ILogger 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 command = stackalloc byte[] { 0xFD }; Span 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 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); } }