未验证 提交 f710a7fe 编写于 作者: C Cory Nelson 提交者: GitHub

Add drivers for Sensirion SHT4x and SCD4x (#1668)

* Remove unneeded dependency in csproj; fixes build error introduced in https://github.com/dotnet/iot/pull/1624

* Add SHT4x and SCD4x devices.

* Update API with async; add telemetry-based API for Scd4x.

* Update readmes

* update README.md
上级 46158869
......@@ -37,7 +37,8 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [AGS01DB - MEMS VOC Gas Sensor](Ags01db/README.md)
* [BMxx80 Device Family](Bmxx80/README.md)
* [CCS811 Gas sensor](Ccs811/README.md)
* [MH-Z19B CO2-Sensor](Mhz19b/README.md)
* [MH-Z19B CO<sub>2</sub> sensor](Mhz19b/README.md)
* [SCD4x - CO<sub>2</sub>, humidity, temperature sensor](Scd4x/README.md)
### Liquid sensors
......@@ -57,6 +58,7 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [BMP180 - barometer, altitude and temperature sensor](Bmp180/README.md)
* [BMxx80 Device Family](Bmxx80/README.md)
* [SCD4x - CO<sub>2</sub>, humidity, temperature sensor](Scd4x/README.md)
* [LPS25H - Piezoresistive pressure and thermometer sensor](Lps25h/README.md)
* [Sense HAT](SenseHat/README.md)
* [SensorHub - Environmental sensor](SensorHub/README.md)
......@@ -84,7 +86,9 @@ Our vision: the majority of .NET bindings are written completely in .NET languag
* [OpenHardwareMonitor client library](HardwareMonitor/README.md)
* [Sense HAT](SenseHat/README.md)
* [SensorHub - Environmental sensor](SensorHub/README.md)
* [SCD4x - CO<sub>2</sub>, humidity, temperature sensor](Scd4x/README.md)
* [SHT3x - Temperature & Humidity Sensor](Sht3x/README.md)
* [SHT4x - Temperature & Humidity Sensor](Sht4x/README.md)
* [SHTC3 - Temperature & Humidity Sensor](Shtc3/README.md)
* [Si7021 - Temperature & Humidity Sensor](Si7021/README.md)
* [μFire ISE Probe - pH, ORP and temperature sensor](UFireIse/README.md)
......
# SCD4x - CO<sub>2</sub>, Temperature & Humidity Sensor
SCD4x is a CO<sub>2</sub>, temperature & humidity sensor from Sensirion. This project supports the SCD40 and SCD41 sensors.
## Documentation
- SCD4x [datasheet](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD4x_Datasheet.pdf)
## Usage
### Hardware Required
- SCD40.
### Code (telemetry / properties)
Less efficient, but simple to use and compatible with telemetry system.
```csharp
I2cConnectionSettings settings =
new I2cConnectionSettings(1, Scd4x.DefaultI2cAddress);
using I2cDevice device = I2cDevice.Create(settings);
using Scd4x sensor = new Scd4x(device);
while (true)
{
// Reading more than once per measurement
// period will result in duplicate values.
Thread.Sleep(Scd4x.MeasurementPeriod);
// read co2 (PPM)
double co2 = sensor.Co2.PartsPerMillion;
// read temperature (℃)
double temperature = sensor.Temperature.Celsius;
// read humidity (%)
double humidity = sensor.RelativeHumidity.Percent;
}
```
### Code (synchronous)
```csharp
I2cConnectionSettings settings =
new I2cConnectionSettings(1, Scd4x.DefaultI2cAddress);
using I2cDevice device = I2cDevice.Create(settings);
using Scd4x sensor = new Scd4x(device);
while (true)
{
// Read the measurement.
// This call will block until the next measurement period.
(VolumeConcentration? co2, RelativeHumidity? hum, Temperature? temp) =
sensor.ReadPeriodicMeasurement();
if (co2 is null || hum is null || temp is null)
{
throw new Exception("CRC failure");
}
// read co2 (PPM)
double co2 = co2.Value.PartsPerMillion;
// read temperature (℃)
double temperature = temp.Value.Celsius;
// read humidity (%)
double humidity = hum.Value.Percent;
}
```
### Calibrating pressure
Giving the device the current barometric pressure will increase accuracy until reset.
```c#
Scd4x sensor = ...;
Pressure currentPressure = Pressure.FromKilopascals(100);
sensor.SetPressureCalibration(currentPressure);
```
### Code (asynchronous)
```csharp
I2cConnectionSettings settings =
new I2cConnectionSettings(1, Scd4x.DefaultI2cAddress);
I2cDevice device = I2cDevice.Create(settings);
Scd4x sensor = new Scd4x(device);
while (true)
{
// Read the measurement.
// This async operation will not finish until the next measurement period.
(VolumeConcentration? co2, RelativeHumidity? hum, Temperature? temp) =
await sensor.ReadPeriodicMeasurementAsync();
if (co2 is null || hum is null || temp is null)
{
throw new Exception("CRC failure");
}
// read co2 (PPM)
double co2 = co2.Value.PartsPerMillion;
// read temperature (℃)
double temperature = temp.Value.Celsius;
// read humidity (%)
double humidity = hum.Value.Percent;
}
```
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers.Binary;
using System.Device.I2c;
using System.Device.Model;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using UnitsNet;
namespace Iot.Device.Scd4x
{
/// <summary>
/// CO₂, Humidity, and Temperature sensor SCD4x
/// </summary>
[Interface("CO₂, Humidity, and Temperature sensor SCD4x")]
public sealed class Scd4x : IDisposable
{
private const int MeasurementPeriodMs = 5000;
/// <summary>
/// The default I²C address of this device.
/// </summary>
public const int DefaultI2cAddress = 0x62;
/// <summary>
/// The period to wait for each measurement: five seconds.
/// </summary>
public static TimeSpan MeasurementPeriod => TimeSpan.FromTicks(TimeSpan.TicksPerMillisecond * MeasurementPeriodMs);
private static ReadOnlySpan<byte> StartPeriodicMeasurementBytes => new byte[] { 0x21, 0xB1 };
private static ReadOnlySpan<byte> CheckDataReadyStatusBytes => new byte[] { 0xE4, 0xB8 };
private static ReadOnlySpan<byte> ReadPeriodicMeasurementBytes => new byte[] { 0xEC, 0x05 };
private static ReadOnlySpan<byte> StopPeriodicMeasurementBytes => new byte[] { 0x3F, 0x86 };
private static ReadOnlySpan<byte> ReInitBytes => new byte[] { 0x36, 0x46 };
private readonly I2cDevice _device;
private VolumeConcentration _lastCo2;
private RelativeHumidity _lastHum;
private Temperature _lastTemp;
private int _nextReadPeriod;
private bool _started;
/// <summary>
/// The most recent CO₂ measurement.
/// </summary>
[Telemetry]
public VolumeConcentration Co2
{
get
{
RefreshIfInNextPeriod();
return _lastCo2;
}
}
/// <summary>
/// The most recent relative humidity measurement.
/// </summary>
[Telemetry]
public RelativeHumidity RelativeHumidity
{
get
{
RefreshIfInNextPeriod();
return _lastHum;
}
}
/// <summary>
/// The most recent temperature measurement.
/// </summary>
[Telemetry]
public Temperature Temperature
{
get
{
RefreshIfInNextPeriod();
return _lastTemp;
}
}
/// <summary>
/// Instantiates a new <see cref="Scd4x"/>.
/// </summary>
/// <param name="device">The I²C device to operate on.</param>
public Scd4x(I2cDevice device)
{
_device = device;
Reset();
}
/// <inheritdoc/>
public void Dispose()
{
if (_started)
{
StopPeriodicMeasurements();
}
_device.Dispose();
}
private void RefreshIfInNextPeriod()
{
if (!_started || _nextReadPeriod - Environment.TickCount <= 0)
{
_ = ReadPeriodicMeasurement();
}
}
/// <summary>
/// Resets the device.
/// </summary>
public void Reset()
{
StopPeriodicMeasurements();
_device.Write(ReInitBytes);
Thread.Sleep(20);
}
/// <summary>
/// Calibrates the sensor to operate at a specific barometric pressure.
/// </summary>
/// <param name="pressure">The pressure to use when calibrating the sensor.</param>
public void SetPressureCalibration(Pressure pressure)
{
int delay = SetPressureCalibrationImpl(pressure);
Thread.Sleep(delay);
}
/// <inheritdoc cref="SetPressureCalibration(Pressure)"/>
public Task SetPressureCalibrationAsync(Pressure pressure)
{
try
{
int delay = SetPressureCalibrationImpl(pressure);
return Task.Delay(delay);
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}
private int SetPressureCalibrationImpl(Pressure pressure)
{
Span<byte> buffer = stackalloc byte[5];
BinaryPrimitives.WriteUInt16BigEndian(buffer, 0xE000);
Sensirion.WriteUInt16BigEndianAndCRC8(buffer.Slice(2), (ushort)(Math.Max(0.0, Math.Min(pressure.Pascals, 1.0)) * (1.0 / 100.0)));
_device.Write(buffer);
return 1;
}
/// <summary>
/// <para>
/// Instructs the sensor to start performing periodic measurements.
/// </para>
///
/// <para>
/// Every period, the length of which is available in <see cref="MeasurementPeriod"/>, the measurement can then be read via <see cref="ReadPeriodicMeasurement"/>.
/// </para>
///
/// <para>
/// Periodic measurement can be stopped with <see cref="StopPeriodicMeasurements"/>.
/// </para>
/// </summary>
public void StartPeriodicMeasurements()
{
_device.Write(StartPeriodicMeasurementBytes);
_nextReadPeriod = Environment.TickCount + MeasurementPeriodMs;
_started = true;
}
/// <summary>
/// <para>
/// Reads the next periodic CO₂, humidity, and temperature measurement from the sensor.
/// </para>
/// </summary>
/// <returns>
/// A tuple of CO₂, humidity, and temperature.
/// If a CRC check failed for a measurement, it will be <see langword="null"/>.
/// </returns>
public (VolumeConcentration? CarbonDioxide, RelativeHumidity? RelativeHumidity, Temperature? Temperature) ReadPeriodicMeasurement()
{
var ret = ReadPeriodicMeasurementImpl(CancellationToken.None, async: false);
Debug.Assert(ret.IsCompleted, "An async=false call should complete synchronously.");
return ret.Result;
}
/// <inheritdoc cref="ReadPeriodicMeasurement"/>
public ValueTask<(VolumeConcentration? CarbonDioxide, RelativeHumidity? RelativeHumidity, Temperature? Temperature)> ReadPeriodicMeasurementAsync(CancellationToken cancellationToken = default) =>
ReadPeriodicMeasurementImpl(cancellationToken, async: true);
private async ValueTask<(VolumeConcentration? CarbonDioxide, RelativeHumidity? RelativeHumidity, Temperature? Temperature)> ReadPeriodicMeasurementImpl(CancellationToken cancellationToken, bool async)
{
cancellationToken.ThrowIfCancellationRequested();
if (!_started)
{
StartPeriodicMeasurements();
}
// Wait for the next period.
int delay = _nextReadPeriod - Environment.TickCount;
if (delay > 0)
{
await DelayAsync(Math.Min(delay, MeasurementPeriodMs), cancellationToken, async);
}
// Wait for the device to have a measurement.
// When this loops, it is due to a small desync in device clocks.
int waitStart = Environment.TickCount;
while (true)
{
BeginCheckDataReady();
await DelayAsync(1, CancellationToken.None, async);
if (EndCheckDataReady())
{
break;
}
if (Environment.TickCount - waitStart > (MeasurementPeriodMs + 1000))
{
throw new Exception("SCD4x not responding.");
}
await DelayAsync(100, cancellationToken, async);
}
// Record the next period to expect data ready.
_nextReadPeriod = Environment.TickCount + 5000;
// Retrieve the measurements.
BeginReadPeriodicMeasurement();
await DelayAsync(2, CancellationToken.None, async);
return EndReadPeriodicMeasurement();
static Task DelayAsync(int delay, CancellationToken cancellationToken, bool async)
{
if (async)
{
return Task.Delay(delay, cancellationToken);
}
Thread.Sleep(delay);
return Task.CompletedTask;
}
}
private void BeginCheckDataReady() =>
_device.Write(CheckDataReadyStatusBytes);
private bool EndCheckDataReady()
{
Span<byte> buffer = stackalloc byte[3];
_device.Read(buffer);
return Sensirion.ReadUInt16BigEndianAndCRC8(buffer) is ushort response && (response & 0x7FF) != 0;
}
private void BeginReadPeriodicMeasurement() =>
_device.Write(ReadPeriodicMeasurementBytes);
private (VolumeConcentration? CarbonDioxide, RelativeHumidity? RelativeHumidity, Temperature? Temperature) EndReadPeriodicMeasurement()
{
Span<byte> buffer = stackalloc byte[9];
_device.Read(buffer);
VolumeConcentration? co2 = Sensirion.ReadUInt16BigEndianAndCRC8(buffer) switch
{
ushort sco2 => VolumeConcentration.FromPartsPerMillion(sco2),
null => (VolumeConcentration?)null
};
Temperature? temp = Sensirion.ReadUInt16BigEndianAndCRC8(buffer.Slice(3, 3)) switch
{
ushort st => Temperature.FromDegreesCelsius(st * (35.0 / 13107.0) - 45.0),
null => (Temperature?)null
};
RelativeHumidity? humidity = Sensirion.ReadUInt16BigEndianAndCRC8(buffer.Slice(6, 3)) switch
{
ushort srh => RelativeHumidity.FromPercent(srh * (100.0 / 65535.0)),
null => (RelativeHumidity?)null
};
if (co2 is not null)
{
_lastCo2 = co2.GetValueOrDefault();
}
if (temp is not null)
{
_lastTemp = temp.GetValueOrDefault();
}
if (humidity is not null)
{
_lastHum = humidity.GetValueOrDefault();
}
return (co2, humidity, temp);
}
/// <summary>
/// Instructs the sensor to stop performing periodic measurements.
/// </summary>
public void StopPeriodicMeasurements()
{
_device.Write(StopPeriodicMeasurementBytes);
_started = false;
Thread.Sleep(500);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(DefaultBindingTfms)</TargetFrameworks>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="Scd4x.cs" />
<Compile Include="..\Shared\Sensirion.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="README.md" />
</ItemGroup>
</Project>
\ No newline at end of file

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31710.8
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scd4x", "Scd4x.csproj", "{91955027-9C88-478E-BC6A-CE0C9F5D29A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Scd4x.Samples", "samples\Scd4x.Samples.csproj", "{7F05F2F1-F8D2-4ED8-8895-7F5A2A146647}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B14195D9-602C-4057-A32B-1ECE385BDC34}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{91955027-9C88-478E-BC6A-CE0C9F5D29A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91955027-9C88-478E-BC6A-CE0C9F5D29A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91955027-9C88-478E-BC6A-CE0C9F5D29A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91955027-9C88-478E-BC6A-CE0C9F5D29A7}.Release|Any CPU.Build.0 = Release|Any CPU
{7F05F2F1-F8D2-4ED8-8895-7F5A2A146647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F05F2F1-F8D2-4ED8-8895-7F5A2A146647}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F05F2F1-F8D2-4ED8-8895-7F5A2A146647}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F05F2F1-F8D2-4ED8-8895-7F5A2A146647}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7F05F2F1-F8D2-4ED8-8895-7F5A2A146647} = {B14195D9-602C-4057-A32B-1ECE385BDC34}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A6935864-7401-42B8-AE98-AD542C55CADE}
EndGlobalSection
EndGlobal
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Device.I2c;
using System.Threading;
using Iot.Device.Common;
using Iot.Device.Scd4x;
using UnitsNet;
using var cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
Console.WriteLine("Stopping...");
cts.Cancel();
};
I2cConnectionSettings settings = new(1, Scd4x.DefaultI2cAddress);
using I2cDevice device = I2cDevice.Create(settings);
using Scd4x sensor = new(device);
// Async loop.
for (int i = 0; i < 3; ++i)
{
Console.WriteLine("Waiting for measurement...");
Console.WriteLine();
(VolumeConcentration? co2, RelativeHumidity? hum, Temperature? temp) = await sensor.ReadPeriodicMeasurementAsync(cts.Token);
Console.WriteLine(co2 is not null
? $"CO₂: {co2.Value}"
: $"CO₂: CRC check failed.");
Console.WriteLine(temp is not null
? $"Temperature: {temp.Value}"
: "Temperature: CRC check failed.");
Console.WriteLine(hum is not null
? $"Relative humidity: {hum.Value}"
: "Relative humidity: CRC check failed.");
if (temp is not null && hum is not null)
{
// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine($"Heat index: {WeatherHelper.CalculateHeatIndex(temp.Value, hum.Value).DegreesCelsius:0.#}\u00B0C");
Console.WriteLine($"Dew point: {WeatherHelper.CalculateDewPoint(temp.Value, hum.Value).DegreesCelsius:0.#}\u00B0C");
}
Console.WriteLine();
}
// Sync loop
for (int i = 0; i < 3; ++i)
{
Console.WriteLine("Waiting for measurement...");
Console.WriteLine();
Thread.Sleep(Scd4x.MeasurementPeriod);
Console.WriteLine($"CO₂: {sensor.Co2}");
Console.WriteLine($"Temperature: {sensor.Temperature}");
Console.WriteLine($"Relative humidity: {sensor.RelativeHumidity}");
Console.WriteLine();
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Scd4x.csproj" />
<ProjectReference Include="..\..\Common\CommonHelpers.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Iot.Device
{
/// <summary>
/// Common code for Sensirion devices.
/// </summary>
internal static class Sensirion
{
/// <returns>If the CRC8 matched the data, a <see cref="ushort"/> of data. Otherwise, <see langword="null"/>.</returns>
public static ushort? ReadUInt16BigEndianAndCRC8(ReadOnlySpan<byte> bytes)
{
Debug.Assert(bytes.Length >= 3, $"{nameof(bytes)} must contain at least 3 bytes.");
_ = bytes[2];
byte hi = bytes[0];
byte lo = bytes[1];
byte crc = bytes[2];
return CRC8(hi, lo) == crc
? (ushort)((hi << 8) | lo)
: null;
}
public static void WriteUInt16BigEndianAndCRC8(Span<byte> bytes, ushort value)
{
Debug.Assert(bytes.Length >= 3, $"{nameof(bytes)} must contain at least 3 bytes.");
byte hi = (byte)((uint)value >> 8);
byte lo = (byte)value;
_ = bytes[2];
bytes[0] = hi;
bytes[1] = lo;
bytes[2] = CRC8(hi, lo);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte CRC8(byte byteA, byte byteB) =>
CrcLookup[CrcLookup[0xFF ^ byteA] ^ byteB];
private static ReadOnlySpan<byte> CrcLookup => new byte[]
{
0x00, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, 0xB9, 0x88, 0xDB, 0xEA, 0x7D, 0x4C, 0x1F, 0x2E,
0x43, 0x72, 0x21, 0x10, 0x87, 0xB6, 0xE5, 0xD4, 0xFA, 0xCB, 0x98, 0xA9, 0x3E, 0x0F, 0x5C, 0x6D,
0x86, 0xB7, 0xE4, 0xD5, 0x42, 0x73, 0x20, 0x11, 0x3F, 0x0E, 0x5D, 0x6C, 0xFB, 0xCA, 0x99, 0xA8,
0xC5, 0xF4, 0xA7, 0x96, 0x01, 0x30, 0x63, 0x52, 0x7C, 0x4D, 0x1E, 0x2F, 0xB8, 0x89, 0xDA, 0xEB,
0x3D, 0x0C, 0x5F, 0x6E, 0xF9, 0xC8, 0x9B, 0xAA, 0x84, 0xB5, 0xE6, 0xD7, 0x40, 0x71, 0x22, 0x13,
0x7E, 0x4F, 0x1C, 0x2D, 0xBA, 0x8B, 0xD8, 0xE9, 0xC7, 0xF6, 0xA5, 0x94, 0x03, 0x32, 0x61, 0x50,
0xBB, 0x8A, 0xD9, 0xE8, 0x7F, 0x4E, 0x1D, 0x2C, 0x02, 0x33, 0x60, 0x51, 0xC6, 0xF7, 0xA4, 0x95,
0xF8, 0xC9, 0x9A, 0xAB, 0x3C, 0x0D, 0x5E, 0x6F, 0x41, 0x70, 0x23, 0x12, 0x85, 0xB4, 0xE7, 0xD6,
0x7A, 0x4B, 0x18, 0x29, 0xBE, 0x8F, 0xDC, 0xED, 0xC3, 0xF2, 0xA1, 0x90, 0x07, 0x36, 0x65, 0x54,
0x39, 0x08, 0x5B, 0x6A, 0xFD, 0xCC, 0x9F, 0xAE, 0x80, 0xB1, 0xE2, 0xD3, 0x44, 0x75, 0x26, 0x17,
0xFC, 0xCD, 0x9E, 0xAF, 0x38, 0x09, 0x5A, 0x6B, 0x45, 0x74, 0x27, 0x16, 0x81, 0xB0, 0xE3, 0xD2,
0xBF, 0x8E, 0xDD, 0xEC, 0x7B, 0x4A, 0x19, 0x28, 0x06, 0x37, 0x64, 0x55, 0xC2, 0xF3, 0xA0, 0x91,
0x47, 0x76, 0x25, 0x14, 0x83, 0xB2, 0xE1, 0xD0, 0xFE, 0xCF, 0x9C, 0xAD, 0x3A, 0x0B, 0x58, 0x69,
0x04, 0x35, 0x66, 0x57, 0xC0, 0xF1, 0xA2, 0x93, 0xBD, 0x8C, 0xDF, 0xEE, 0x79, 0x48, 0x1B, 0x2A,
0xC1, 0xF0, 0xA3, 0x92, 0x05, 0x34, 0x67, 0x56, 0x78, 0x49, 0x1A, 0x2B, 0xBC, 0x8D, 0xDE, 0xEF,
0x82, 0xB3, 0xE0, 0xD1, 0x46, 0x77, 0x24, 0x15, 0x3B, 0x0A, 0x59, 0x68, 0xFF, 0xCE, 0x9D, 0xAC
};
}
}
# SHT4x - Temperature & Humidity Sensor
SHT4x is a temperature and humidity sensor from Sensirion. This project supports the SHT40, SHT41, and SHT45 sensors.
## Documentation
- SHT40 [datasheet](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT4x_Datasheet.pdf)
## Usage
### Hardware Required
- SHT40.
### Code (synchronous)
```csharp
I2cConnectionSettings settings =
new I2cConnectionSettings(1, Sht4x.DefaultI2cAddress);
using I2cDevice device = I2cDevice.Create(settings);
using Sht4x sensor = new Sht4x(device);
// read humidity (%)
double humidity = sensor.RelativeHumidity.Percent;
// read temperature (℃)
double temperature = sensor.Temperature.Celsius;
```
### Code (asynchronous)
```csharp
I2cConnectionSettings settings =
new I2cConnectionSettings(1, Sht4x.DefaultI2cAddress);
using I2cDevice device = I2cDevice.Create(settings);
using Sht4x sensor = new Sht4x(device);
// Read both humidity and temperature.
(RelativeHumidity? rh, Temperature? t) =
await sensor.ReadHumidityAndTemperatureAsync();
if(rh is null || t is null)
{
throw new Exception("CRC failure");
}
// read humidity (%)
double humidity = rh.Value.Percent;
// read temperature (℃)
double temperature = t.Value.Celsius;
```
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Device.I2c;
using System.Device.Model;
using System.Threading;
using System.Threading.Tasks;
using UnitsNet;
namespace Iot.Device.Sht4x
{
/// <summary>
/// Humidity and Temperature Sensor SHT4x
/// </summary>
[Interface("Humidity and Temperature Sensor SHT4x")]
public sealed class Sht4x : IDisposable
{
/// <summary>
/// The default I²C address of this device.
/// </summary>
public const int DefaultI2cAddress = 0x44;
private readonly I2cDevice _device;
private RelativeHumidity _lastHum;
private Temperature _lastTemp;
/// <summary>
/// The level of repeatability to use when measuring relative humidity.
/// </summary>
[Property]
public Sht4xRepeatability Repeatability { get; set; } = Sht4xRepeatability.High;
/// <summary>
/// The most recent relative humidity measurement.
/// </summary>
[Telemetry]
public RelativeHumidity RelativeHumidity
{
get
{
ReadHumidityAndTemperature(Repeatability);
return _lastHum;
}
}
/// <summary>
/// The most recent temperature measurement.
/// </summary>
[Telemetry]
public Temperature Temperature
{
get
{
ReadHumidityAndTemperature(Repeatability);
return _lastTemp;
}
}
/// <summary>
/// Instantiates a new <see cref="Sht4x"/>.
/// </summary>
/// <param name="device">The I²C device to operate on.</param>
public Sht4x(I2cDevice device)
{
_device = device;
Reset();
}
/// <inheritdoc/>
public void Dispose() =>
_device.Dispose();
/// <summary>
/// Resets the device.
/// </summary>
public void Reset()
{
_device.WriteByte(0x94);
Thread.Sleep(1);
}
/// <summary>
/// Reads relative humidity and temperature.
/// </summary>
/// <returns>
/// A tuple of relative humidity and temperature.
/// If a CRC check failed for a measurement, it will be <see langword="null"/>.
/// </returns>
public (RelativeHumidity? RelativeHumidity, Temperature? Temperature) ReadHumidityAndTemperature(Sht4xRepeatability repeatability = Sht4xRepeatability.High)
{
int delay = BeginReadHumidityAndTemperature(repeatability);
Thread.Sleep(delay);
return EndReadHumidityAndTemperature();
}
/// <inheritdoc cref="ReadHumidityAndTemperature(Sht4xRepeatability)"/>
public async ValueTask<(RelativeHumidity? RelativeHumidity, Temperature? Temperature)> ReadHumidityAndTemperatureAsync(Sht4xRepeatability repeatability = Sht4xRepeatability.High)
{
int delay = BeginReadHumidityAndTemperature(repeatability);
await Task.Delay(delay);
return EndReadHumidityAndTemperature();
}
private int BeginReadHumidityAndTemperature(Sht4xRepeatability repeatability)
{
(byte cmd, int delayInMs) = repeatability switch
{
Sht4xRepeatability.Low => ((byte)0xE0, 2),
Sht4xRepeatability.Medium => ((byte)0xF6, 5),
Sht4xRepeatability.High => ((byte)0xFD, 9),
_ => throw new ArgumentOutOfRangeException(nameof(repeatability))
};
_device.WriteByte(cmd);
return delayInMs;
}
private (RelativeHumidity? RelativeHumidity, Temperature? Temperature) EndReadHumidityAndTemperature()
{
Span<byte> buffer = stackalloc byte[6];
_device.Read(buffer);
Temperature? t = Sensirion.ReadUInt16BigEndianAndCRC8(buffer) switch
{
ushort deviceTemperature => Temperature.FromDegreesCelsius(deviceTemperature * (35.0 / 13107.0) - 45.0),
null => (Temperature?)null
};
RelativeHumidity? h = Sensirion.ReadUInt16BigEndianAndCRC8(buffer.Slice(3, 3)) switch
{
ushort deviceHumidity => RelativeHumidity.FromPercent(Math.Max(0.0, Math.Min(deviceHumidity * (100.0 / 52428.0) - (300.0 / 50.0), 100.0))),
null => (RelativeHumidity?)null
};
if (h is not null)
{
_lastHum = h.GetValueOrDefault();
}
if (t is not null)
{
_lastTemp = t.GetValueOrDefault();
}
return (h, t);
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(DefaultBindingTfms)</TargetFrameworks>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="Sht4x.cs" />
<Compile Include="Sht4xRepeatability.cs" />
<Compile Include="..\Shared\Sensirion.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="README.md" />
</ItemGroup>
</Project>
\ No newline at end of file

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31710.8
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sht4x", "Sht4x.csproj", "{6F1F02B2-1C0F-4BC1-8354-6C4F5255B6D7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sht4x.Samples", "samples\Sht4x.Samples.csproj", "{DF1C2C6B-71D1-4B40-BD85-79C4AA4A68FA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{D23EB428-8E64-4906-ACAB-48EB63459C45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6F1F02B2-1C0F-4BC1-8354-6C4F5255B6D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F1F02B2-1C0F-4BC1-8354-6C4F5255B6D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F1F02B2-1C0F-4BC1-8354-6C4F5255B6D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F1F02B2-1C0F-4BC1-8354-6C4F5255B6D7}.Release|Any CPU.Build.0 = Release|Any CPU
{DF1C2C6B-71D1-4B40-BD85-79C4AA4A68FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF1C2C6B-71D1-4B40-BD85-79C4AA4A68FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF1C2C6B-71D1-4B40-BD85-79C4AA4A68FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF1C2C6B-71D1-4B40-BD85-79C4AA4A68FA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{DF1C2C6B-71D1-4B40-BD85-79C4AA4A68FA} = {D23EB428-8E64-4906-ACAB-48EB63459C45}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {70971141-EF20-4FED-8937-CC5ACC0CDC26}
EndGlobalSection
EndGlobal
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Iot.Device.Sht4x
{
/// <summary>
/// Desired repeatability of relative humidity measurement.
/// </summary>
public enum Sht4xRepeatability
{
/// <summary>
/// 0.25% RH error
/// </summary>
Low,
/// <summary>
/// 0.15% RH error
/// </summary>
Medium,
/// <summary>
/// 0.08% RH error
/// </summary>
High
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Device.I2c;
using System.Threading;
using System.Threading.Tasks;
using Iot.Device.Common;
using Iot.Device.Sht4x;
using UnitsNet;
I2cConnectionSettings settings = new(1, Sht4x.DefaultI2cAddress);
using I2cDevice device = I2cDevice.Create(settings);
using Sht4x sensor = new(device);
// Async loop.
for (int i = 0; i < 3; ++i)
{
(RelativeHumidity? hum, Temperature? temp) = await sensor.ReadHumidityAndTemperatureAsync();
Console.WriteLine(temp is not null
? $"Temperature: {temp.Value}"
: "Temperature: CRC check failed.");
Console.WriteLine(hum is not null
? $"Relative humidity: {hum.Value}"
: "Relative humidity: CRC check failed.");
if (temp is not null && hum is not null)
{
// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine($"Heat index: {WeatherHelper.CalculateHeatIndex(temp.Value, hum.Value)}");
Console.WriteLine($"Dew point: {WeatherHelper.CalculateDewPoint(temp.Value, hum.Value)}");
}
Console.WriteLine();
await Task.Delay(1000);
}
// Property-based access.
for (int i = 0; i < 3; ++i)
{
Console.WriteLine($"Temperature: {sensor.Temperature}");
Console.WriteLine($"Relative humidity: {sensor.RelativeHumidity}");
Console.WriteLine();
Thread.Sleep(1000);
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Sht4x.csproj" />
<ProjectReference Include="..\..\Common\CommonHelpers.csproj" />
</ItemGroup>
</Project>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册