Files
GSS2Rework/GSS2.Core/Hardware/TemperatureHumidityService.cs
T

173 lines
5.8 KiB
C#

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);
}
}