Skip to content

Commit

Permalink
Merge pull request #1175 from fehdem/master
Browse files Browse the repository at this point in the history
Add support for AHTxx temperature and humidity sensor devices and MH-Z19B CO2 gas sensor
  • Loading branch information
Ellerbach authored Oct 20, 2020
2 parents fdf749d + e12ff57 commit 526af54
Show file tree
Hide file tree
Showing 21 changed files with 911 additions and 0 deletions.
27 changes: 27 additions & 0 deletions src/devices/Ahtxx/Aht10.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Device.I2c;

namespace Iot.Device.Ahtxx
{
/// <summary>
/// AHT10/15 temperature and humidity sensor binding.
/// </summary>
public class Aht10 : AhtBase
{
/// <summary>
/// Initialization command acc. to datasheet
/// </summary>
private const byte Aht10InitCommand = 0b1110_0001;

/// <summary>
/// Initializes a new instance of the <see cref="Aht10"/> class.
/// </summary>
public Aht10(I2cDevice i2cDevice)
: base(i2cDevice, Aht10InitCommand)
{
}
}
}
27 changes: 27 additions & 0 deletions src/devices/Ahtxx/Aht20.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Device.I2c;

namespace Iot.Device.Ahtxx
{
/// <summary>
/// AHT20 temperature and humidity sensor binding.
/// </summary>
public class Aht20 : AhtBase
{
/// <summary>
/// Initialization command acc. to datasheet
/// </summary>
private const byte Aht20InitCommand = 0b1011_1110;

/// <summary>
/// Initializes a new instance of the <see cref="Aht20"/> class.
/// </summary>
public Aht20(I2cDevice i2cDevice)
: base(i2cDevice, Aht20InitCommand)
{
}
}
}
178 changes: 178 additions & 0 deletions src/devices/Ahtxx/AhtBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Device.I2c;
using System.Threading;
using UnitsNet;

namespace Iot.Device.Ahtxx
{
/// <summary>
/// Base class for common functions of the AHT10/15 and AHT20 sensors.
/// </summary>
public abstract class AhtBase : IDisposable
{
/// <summary>
/// Address of AHT10/15/20 device (0x38). This address is fix and cannot be changed.
/// This implies that only one device can be attached to a single I2C bus at a time.
/// </summary>
public const int DefaultI2cAddress = 0x38;

private readonly byte _initCommand;
private I2cDevice _i2cDevice;
private double _temperature;
private double _humidity;

/// <summary>
/// Initializes a new instance of the binding for a sensor connected through I2C interface.
/// </summary>
/// <paramref name="i2cDevice">Reference to the initialized I2C interface device</paramref>
/// <paramref name="initCommand">Type specific command for device initialization</paramref>
public AhtBase(I2cDevice i2cDevice, byte initCommand)
{
_i2cDevice = i2cDevice ?? throw new ArgumentNullException(nameof(i2cDevice));
_initCommand = initCommand;

// even if not clearly stated in datasheet, start with a software reset to assure clear start conditions
SoftReset();

// check whether the device indicates the need for a calibration cycle
// and perform calibration if indicated ==> c.f. datasheet, version 1.1, ch. 5.4
if (!IsCalibrated())
{
Initialize();
}
}

/// <summary>
/// Gets the current temperature reading from the sensor.
/// Reading the temperature takes between 10 ms and 80 ms.
/// </summary>
/// <returns>Temperature reading</returns>
public Temperature GetTemperature()
{
Measure();
return Temperature.FromDegreesCelsius(_temperature);
}

/// <summary>
/// Gets the current relative humidity reading from the sensor.
/// Reading the humidity takes between 10 ms and 80 ms.
/// </summary>
/// <returns>Relative humidity reading</returns>
public Ratio GetHumidity()
{
Measure();
return Ratio.FromPercent(_humidity);
}

/// <summary>
/// Perform sequence to retrieve current readings from device
/// </summary>
private void Measure()
{
Span<byte> buffer = stackalloc byte[3]
{
// command parameters c.f. datasheet, version 1.1, ch. 5.4
(byte)CommonCommand.Measure,
0x33,
0x00
};

_i2cDevice.Write(buffer);

// According to the datasheet the measurement takes 80 ms and completion is indicated by the status bit.
// However, it seems to be faster at around 10 ms and sometimes up to 50 ms.
while (IsBusy())
{
Thread.Sleep(10);
}

buffer = stackalloc byte[6];
_i2cDevice.Read(buffer);

// data format: 20 bit humidity, 20 bit temperature
// 7 0 7 0 7 4 0 7 0 7 0
// [humidity 19..12] [humidity 11..4] [humidity 3..0|temp 19..16] [temp 15..8] [temp 7..0]
// c.f. datasheet ch. 5.4.5
Int32 rawHumidity = (buffer[1] << 12) | (buffer[2] << 4) | (buffer[3] >> 4);
Int32 rawTemperature = ((buffer[3] & 0xF) << 16) | (buffer[4] << 8) | buffer[5];
// RH[%] = Hraw / 2^20 * 100%, c.f. datasheet ch. 6.1
_humidity = (rawHumidity * 100.0) / 0x100000;
// T[°C] = Traw / 2^20 * 200 - 50, c.f. datasheet ch. 6.1
_temperature = ((rawTemperature * 200.0) / 0x100000) - 50;
}

/// <summary>
/// Perform soft reset command sequence
/// </summary>
private void SoftReset()
{
_i2cDevice.WriteByte((byte)CommonCommand.SoftReset);
// reset requires 20ms at most, c.f. datasheet version 1.1, ch. 5.5
Thread.Sleep(20);
}

/// <summary>
/// Perform initialization (calibration) command sequence
/// </summary>
private void Initialize()
{
Span<byte> buffer = stackalloc byte[3]
{
_initCommand,
0x08, // command parameters c.f. datasheet, version 1.1, ch. 5.4
0x00
};

_i2cDevice.Write(buffer);
// wait 10ms c.f. datasheet, version 1.1, ch. 5.4
Thread.Sleep(10);
}

private byte GetStatus()
{
_i2cDevice.WriteByte(0x71);
// whithout this delay the reading the status fails often.
Thread.Sleep(10);
byte status = _i2cDevice.ReadByte();
return status;
}

private bool IsBusy()
{
return (GetStatus() & (byte)StatusBit.Busy) == (byte)StatusBit.Busy;
}

private bool IsCalibrated()
{
return (GetStatus() & (byte)StatusBit.Calibrated) == (byte)StatusBit.Calibrated;
}

/// <inheritdoc cref="IDisposable" />
public void Dispose() => Dispose(true);

/// <inheritdoc cref="IDisposable" />
protected virtual void Dispose(bool disposing)
{
_i2cDevice?.Dispose();
_i2cDevice = null;
}

// datasheet version 1.1, table 10
[Flags]
private enum StatusBit : byte
{
Calibrated = 0b_0000_1000,
Busy = 0b1000_0000
}

private enum CommonCommand : byte
{
SoftReset = 0b1011_1010,
Measure = 0b1010_1100
}
}
}
15 changes: 15 additions & 0 deletions src/devices/Ahtxx/Ahtxx.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<!--Disabling default items so samples source won't get build by the main library-->
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>

<ItemGroup>
<Compile Include="*.cs" />
<None Include="README.md" />
<ProjectReference Include="$(MainLibraryPath)System.Device.Gpio.csproj" />
</ItemGroup>

</Project>
53 changes: 53 additions & 0 deletions src/devices/Ahtxx/Ahtxx.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ahtxx", "Ahtxx.csproj", "{B82C190A-642B-465B-BD3F-DB56FFF22253}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{6A4DE7B1-03F3-4EE0-BF73-A0BAEF88BA2B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ahtxx.Samples", "samples\Ahtxx.Samples.csproj", "{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x64.ActiveCfg = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x64.Build.0 = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x86.ActiveCfg = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Debug|x86.Build.0 = Debug|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|Any CPU.Build.0 = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x64.ActiveCfg = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x64.Build.0 = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x86.ActiveCfg = Release|Any CPU
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF}.Release|x86.Build.0 = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x64.ActiveCfg = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x64.Build.0 = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x86.ActiveCfg = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Debug|x86.Build.0 = Debug|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|Any CPU.Build.0 = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x64.ActiveCfg = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x64.Build.0 = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x86.ActiveCfg = Release|Any CPU
{B82C190A-642B-465B-BD3F-DB56FFF22253}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3CFA13D6-1D29-4C87-B0C1-01A6901A50EF} = {6A4DE7B1-03F3-4EE0-BF73-A0BAEF88BA2B}
EndGlobalSection
EndGlobal
49 changes: 49 additions & 0 deletions src/devices/Ahtxx/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# AHT10/15/20 Temperature and Humidity Sensor Modules

## Summary
The AHT10/15 and AHT20 sensors are high-precision, calibrated temperature and relative humidity sensor modules with an I2C digital interface.

## Binding Notes
### Supported Devices
The binding supports the following types:
* AHT10 - http://www.aosong.com/en/products-40.html
* AHT15 - http://www.aosong.com/en/products-45.html
* AHT20 - http://www.aosong.com/en/products-32.html

### Functions
The binding supports the following sensor functions:
* acquiring the temperature and relative humidty readings
* reading status
* issueing calibration and reset commands


### Sensor classes
You need to choose the class depending on the sensor type.

|Sensor|Required class|
|-----|---------------|
|AHT10|Aht10 |
|Aht15|Aht10 |
|Aht20|Aht20 |


### Basic Usage

The binding gets instantiated using an existing ```I2cDevice`` instance. The AHT-sensor modules support only the default I2C address.

Setup for an AHT20 sensor module:
```
const int I2cBus = 1;
I2cConnectionSettings i2cSettings = new I2cConnectionSettings(I2cBus, Aht20.DefaultI2cAddress);
I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);
Aht20 sensor = new Aht20(i2cDevice);
```

The temperature and humidity readings are acquired by using the following methods:

```
public Temperature GetTemperature()
public Ratio GetHumidity()
```

Refer to the sample application for a complete example.
2 changes: 2 additions & 0 deletions src/devices/Ahtxx/category.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hygrometers
thermometers
36 changes: 36 additions & 0 deletions src/devices/Ahtxx/samples/Ahtxx.Sample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Device.I2c;
using System.Threading;

namespace Iot.Device.Ahtxx.Samples
{
/// <summary>
/// Samples for Aht10 and Aht20 bindings
/// </summary>
internal class Program
{
/// <summary>
/// Main entry point
/// </summary>
public static void Main(string[] args)
{
const int I2cBus = 1;
I2cConnectionSettings i2cSettings = new I2cConnectionSettings(I2cBus, Aht20.DefaultI2cAddress);
I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);

// For AHT10 or AHT15 use:
// Aht10 sensor = new Aht10(i2cDevice);
// For AHT20 use:
Aht20 sensor = new Aht20(i2cDevice);
while (true)
{
Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: {sensor.GetTemperature().DegreesCelsius:F1}°C, {sensor.GetHumidity().Percent:F0}%");
Thread.Sleep(1000);
}
}
}
}
Loading

0 comments on commit 526af54

Please sign in to comment.