forked from amkovkov/GranuSightSoftware2
173 lines
5.8 KiB
C#
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);
|
|
}
|
|
}
|