Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/devices/Dhtxx/Devices/Dht21.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal override Ratio GetHumidity(byte[] readBuff)

internal override Temperature GetTemperature(byte[] readBuff)
{
var temp = (readBuff[2] & 0x7F) + readBuff[3] * 0.1;
var temp = ((readBuff[2] & 0x7F) << 8 | readBuff[3]) * 0.1;
// if MSB = 1 we have negative temperature
temp = ((readBuff[2] & 0x80) == 0 ? temp : -temp);

Expand Down
2 changes: 1 addition & 1 deletion src/devices/Dhtxx/Devices/Dht22.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal override Ratio GetHumidity(byte[] readBuff)

internal override Temperature GetTemperature(byte[] readBuff)
{
var temp = ((readBuff[2] & 0x7F) * 256 + readBuff[3]) * 0.1;
var temp = ((readBuff[2] & 0x7F) << 8 | readBuff[3]) * 0.1;
// if MSB = 1 we have negative temperature
temp = ((readBuff[2] & 0x80) == 0 ? temp : -temp);

Expand Down
28 changes: 20 additions & 8 deletions src/devices/Dhtxx/DhtBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ public abstract class DhtBase : IDisposable
protected readonly int _pin;

/// <summary>
/// I2C device used to communicate with the device
/// True to dispose the Gpio Controller
/// </summary>
protected readonly I2cDevice _i2cDevice;
protected readonly bool _shouldDispose;

/// <summary>
/// <see cref="GpioController"/> related with the <see cref="_pin"/>.
/// I2C device used to communicate with the device
/// </summary>
protected readonly GpioController _controller;
protected I2cDevice _i2cDevice;

/// <summary>
/// True to dispose the Gpio Controller
/// <see cref="GpioController"/> related with the <see cref="_pin"/>.
/// </summary>
protected readonly bool _shouldDispose;
protected GpioController _controller;

// wait about 1 ms
private readonly uint _loopCount = 10000;
Expand All @@ -58,7 +58,7 @@ public abstract class DhtBase : IDisposable
/// Get the last read temperature
/// </summary>
/// <remarks>
/// If last read was not successfull, it returns <code>default(Temperature)</code>
/// If last read was not successful, it returns <code>default(Temperature)</code>
/// </remarks>
public virtual Temperature Temperature
{
Expand All @@ -73,7 +73,7 @@ public virtual Temperature Temperature
/// Get the last read of relative humidity in percentage
/// </summary>
/// <remarks>
/// If last read was not successfull, it returns <code>default(Ratio)</code>
/// If last read was not successful, it returns <code>default(Ratio)</code>
/// </remarks>
public virtual Ratio Humidity
{
Expand Down Expand Up @@ -280,9 +280,21 @@ public void Dispose()
if (_shouldDispose)
{
_controller?.Dispose();
_controller = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to insist: this needs to be outside the if (or in both cases). Otherwise, if shouldDispose is false, this still bombs on the second Dispose call.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _shouldDispose is false, the key principal is not to dispose. As an example, with the FT4222, you can only create 1 GPIO controller. If you have 2 devices which are using GPIO, and the first one dispose it, then the second one will raise an exception.
So it's important not to dispose at all the GPIO controller if the ```shouldDispose```` flag is false.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. I'm not talking about disposing, I'm talking about setting the controller to null. This doesn't do an implicit dispose or something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pgrawehr I think I really miss something then. Can you please then write the full dispose class how you think it should be for me to understand what you mean.
Again, here is the principle:

  • Whatever happens the I2C device is disposed
  • the GPIO Controller is disposed only if _shouldDispose is true
  • if _shouldDispose is flase, the open pins are closed and the controller is not disposed at all
    To prevent some mistakes:
  • _shouldDipose is set to true if the GPIO Controller is created in the class. So even if you pass false, if the controller = null in the class constructor, a controller will be created and _shouldDiposed will be set to true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do that. It really looks like we're talking different things. But give me some time, as you probably know, I'm traveling around, and good WiFi is hard to find.

Copy link
Contributor

@pgrawehr pgrawehr Sep 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a patch file for your branch. I've also fixed a few minor other issues I just saw. In addition to your list above (which are all sattisfied), this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I understand now. So then, rather than raising an exception, why not then use the other pattern, for example present in some of the classes like the Explorer Hat:

private bool _disposedValue = false;

So having a variable to avoid double disposing? Advantage: it doesn't raise an exception. I'm not much in favor of raising exceptions in the Dispose function. Those are exceptions usually no one really trap.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception is not thrown in Dispose (thats what we try to avoid), but if the instance is otherwise used after Disposal, i.e when trying to read from the sensor after disposing.
An ObjectDisposedException is rightly (almost) never handled in user code, since that is a programming error, and they should stop the application typically in the debugger.

Of course the same as what I did can be achieved by an extra bool variable, but that doesn't change much conceptionally.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so I adjusted only the dispose part to make sure that is multiple dispose are called, it won't create any issue.
In case of a programming error and the class is called once disposed, an exception will be raised anyway.
So it keeps the solution simple enough and easy to read. I hope it's a good compromise.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would still set it to null, but I'm ok with this as well.

}
else
{
if (_controller != null)
{
if (_controller.IsPinOpen(_pin))
{
_controller.ClosePin(_pin);
}
}
}

_i2cDevice?.Dispose();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_i2cDevice?.Dispose();
_i2cDevice?.Dispose();
_controller = null;
  • Need to make _controller non-readonly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's readonly, what is the advantage of making it non readonly and setting it up to null? Also if setup to null, it will have to go to line 287, not here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because if it is not reset to null, the change to _controller?.ClosePin(_pin) doesn't change anything. We need to make sure that ClosePin is not called a second time, and this requires the _controller to be null the second time. Since we Dispose the controller in the first case of the if, we can set it to null in both cases. (Additional advantage: Using the class after dispose will throw exceptions).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But in this case, we want to close the pin and not dispose the controller

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I didn't say it should be disposed in the second case. It should be set to null, but in both cases.

_i2cDevice = null;
}
}
}
104 changes: 92 additions & 12 deletions src/devices/Dhtxx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ The DHT temperature and humidity sensors are very popular. This projects support
| | DHT10 | DHT11 | DHT12 | DHT21 | DHT22 |
| :------: | :------: | :------: | :------: | :------: | :------: |
| Image | <img src="imgs/dht10.jpg" height="60"/> | <img src="imgs/dht11.jpg" height="60"/> | <img src="imgs/dht12.jpg" height="60"/> | <img src="imgs/dht21.jpg" height="60"/> | <img src="imgs/dht22.jpg" height="60"/> |
| Temperature Range | -40 ~ 80 ℃ | -20 ~ 60 ℃ | -20 ~ 60 ℃ | -40 ~ 80 ℃ | -40 ~ 80 ℃ |
| Humidity Range | 0 ~ 99.9 % | 5 ~ 95 % | 20 ~ 95 % | 0 ~ 99.9 % | 0 ~ 99.9 % |
| Temperature Range | -40 ~ 80 ℃ | 0 ~ 60 ℃ | -20 ~ 60 ℃ | -40 ~ 80 ℃ | -40 ~ 80 ℃ |
| Humidity Range | 0 ~ 99.9 % | 2 ~ 95 % | 20 ~ 95 % | 0 ~ 99.9 % | 0 ~ 99.9 % |
| Temperature Accuracy | ±0.5 ℃ | ±2 ℃ | ±0.5 ℃ | ±0.5 ℃ | ±0.5 ℃ |
| Humidity Accuracy | ±3 % | ±5 % | ±4 % | ±3 % | ±2 % |
| Protocol | I2C | 1-Wire | I2C, 1-Wire | 1-Wire | 1-Wire |
Expand All @@ -21,13 +21,29 @@ The DHT temperature and humidity sensors are very popular. This projects support
// GPIO Pin
using (Dht11 dht = new Dht11(26))
{
Temperature temperature = dht.Temperature;
double humidity = dht.Humidity;
var temperature = dht.Temperature;
var humidity = dht.Humidity;
// You can only display temperature and humidity if the read is successful otherwise, this will raise an exception as
// both temperature and humidity are NAN
if (dht.IsLastReadSuccessful)
{
Console.WriteLine($"Temperature: {temperature.DegreesCelsius} \u00B0C, Humidity: {humidity.Percent} %");

// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine(
$"Heat index: {WeatherHelper.CalculateHeatIndex(temperature, humidity).Celsius:0.#}\u00B0C");
Console.WriteLine(
$"Dew point: {WeatherHelper.CalculateDewPoint(temperature, humidity).Celsius:0.#}\u00B0C");
}
else
{
Console.WriteLine("Error reading DHT sensor");
}
}
```
**Note:** _Specifically on the RPi with the DHT22, 1-Wire works using Raspian but not with IoT-Core. The device has to switch the 1-wire pin between input and output and vice versa. It seems that Windows IoT-Core OS can't switch the pin direction quick enough. There have been suggestions for using two pins; one for input and one for output. This solution has not been implemented here, but these are some handy links that may help setting that up:_
**Note:** _On the RPi with any of the DHT sensor, 1-Wire works using Raspian but not with Windows 10 IoT Core. The device has to switch the 1-wire pin between input and output and vice versa. It seems that Windows IoT Core OS can't switch the pin direction quick enough. There have been suggestions for using two pins; one for input and one for output. This solution has not been implemented here, but these are some handy links that may help setting that up:_
- https://github.com/ms-iot/samples/tree/develop/GpioOneWire
- And on Hackster.io (https://www.hackster.io/porrey/go-native-c-with-the-dht22-a8e8eb
- And on Hackster.io: https://www.hackster.io/porrey/go-native-c-with-the-dht22-a8e8eb

### I2C Protocol

Expand All @@ -41,18 +57,82 @@ using (Dht12 dht = new Dht12(device))
{
var tempValue = dht.Temperature;
var humValue = dht.Humidity;
if (dht.IsLastReadSuccessful)
{
Console.WriteLine($"Temperature: {tempValue.Celsius:0.#}\u00B0C");
Console.WriteLine($"Relative humidity: {humValue:0.#}%");

// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine(
$"Heat index: {WeatherHelper.CalculateHeatIndex(tempValue, humValue).Celsius:0.#}\u00B0C");
Console.WriteLine(
$"Dew point: {WeatherHelper.CalculateDewPoint(tempValue, humValue).Celsius:0.#}\u00B0C");
}
else
{
Console.WriteLine("Error reading DHT sensor");
}
}
```

## Reading frequency and quality measurement

In the case of I2C or GPIO, any type of DHT needs a bit of time between 2 readings. DHT22 documentation refer to a sensing period of 2 seconds and a collecting period higher than 1.7 seconds.
Measuring with higher frequency won't give you more accurate numbers. As you can see from the specifications, the accuracy depends on the sensor type, it goes from ±2 ℃ for the DHT11 to ±0.5 ℃ for the others.
Even if the parity check can come clear, we do recommend to check that the data are in a normal range. For example of humidity is higher than 100%, then it means that measurement is wrong.
This check has not been done in the binding itself, so you may consider adding a check on your application side.

The DHT sensors are very sensitive, avoid too long cables, electromagnetic perturbations and compile the code as release not debug to increase the quality of measurement.

## FAQ

**I always get wrong measurements, what's happening?**

Please check that the sensor is plugged correctly, make sure you are using the correct pin.

Please check you are using the correct sensor, only DHT10 and DHT12 supports I2C. All others support only GPIO with 1 wire protocol. DHT12 supports both.

**The data I measure are not correct, humidity seems ok but temperature is always weird, what's the problem?**

Please check you are using the correct sensor. Refer to the top part of this page to check which sensor you have. Using a DHT11 instead of a DHT22 will give you a wrong temperature.

**I am trying to get a temperature and humidity 5 times per seconds but I mainly get wrong measurements, why?**

This is absolutely normal, you should check the measurements once every 2 seconds approximately. Don't try to get more measures than once every 2 seconds.

**When reading the temperature and humidity and trying to write the data in the console, I get an exception, why?**

You need to check first if the measurement has been successful. If the measurement hasn't been successful, the default values will be NaN and so you won't be able to convert the temperature or humidity and you'll get an exception. This is the correct way of first reading the sensor and then checking the reading was correct and finally using the temperature and humidity data:

```csharp
var tempValue = dht.Temperature;
var humValue = dht.Humidity;
if (dht.IsLastReadSuccessful)
{
Console.WriteLine($"Temperature: {tempValue.Celsius:0.#}\u00B0C");
Console.WriteLine($"Relative humidity: {humValue:0.#}%");

// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine(
$"Heat index: {WeatherHelper.CalculateHeatIndex(tempValue, humValue).Celsius:0.#}\u00B0C");
Console.WriteLine(
$"Dew point: {WeatherHelper.CalculateDewPoint(tempValue, humValue).Celsius:0.#}\u00B0C");
}
```

**I have a Raspberry Pi 4 and I get an exception when creating the DHT sensor**

See this [issue 1145](https://github.com/dotnet/iot/issues/1145). We're actively trying to fix it automatically. You will have to force using either the Raspberry Pi 3 driver, either the LibGpiodDriver. This is how you can force using a specific drive, in this case the Raspberry Pi 3 one which will work:

```csharp
GpioDriver driver = new RaspberryPi3Driver();
var controller = new GpioController(PinNumberingScheme.Logical, driver);
// This uses pin 4 in the logical schema so pin 7 in the physical schema
var dht = new Dht11(4, gpioController: controller);
```

**My DHT sensor using 1 wire protocol is not working on my Raspberry Pi with Windows 10 IoT Core, what can I do?**

On the RPi with any of the DHT sensor, 1-Wire works using Raspian but not with Windows 10 IoT Core. The device has to switch the 1-wire pin between input and output and vice versa. It seems that Windows IoT Core OS can't switch the pin direction quick enough. There have been suggestions for using two pins; one for input and one for output. This solution has not been implemented here, but these are some handy links that may help setting that up:_
- https://github.com/ms-iot/samples/tree/develop/GpioOneWire
- And on Hackster.io: https://www.hackster.io/porrey/go-native-c-with-the-dht22-a8e8eb

Now if your sensor is an I2C sensor, it should just work perfectly on Windows 10 IoT Core.

## References

* **DHT10** [datasheet (Currently only Chinese)](http://www.aosong.com/userfiles/files/media/DHT10%E8%A7%84%E6%A0%BC%E4%B9%A6.pdf)
Expand Down
108 changes: 94 additions & 14 deletions src/devices/Dhtxx/samples/DhtSensor.sample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,109 @@ internal class Program
public static void Main(string[] args)
{
Console.WriteLine("Hello DHT!");
Console.WriteLine("Select the DHT sensor you want to use:");
Console.WriteLine(" 1. DHT10 on I2C");
Console.WriteLine(" 2. DHT11 on GPIO");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:+1, Good example code

Console.WriteLine(" 3. DHT12 on GPIO");
Console.WriteLine(" 4. DHT21 on GPIO");
Console.WriteLine(" 5. DHT22 on GPIO");
var choice = Console.ReadKey();
Console.WriteLine();
if (choice.KeyChar == '1')
{
Console.WriteLine("Press any key to stop the reading");
// Init DHT10 through I2C
I2cConnectionSettings settings = new I2cConnectionSettings(1, Dht10.DefaultI2cAddress);
I2cDevice device = I2cDevice.Create(settings);

// Init DHT10 through I2C
I2cConnectionSettings settings = new I2cConnectionSettings(1, Dht10.DefaultI2cAddress);
I2cDevice device = I2cDevice.Create(settings);
using (Dht10 dht = new Dht10(device))
{
Dht(dht);
}

using (Dht10 dht = new Dht10(device))
return;
}

Console.WriteLine("Which pin do you want to use in the logical pin schema?");
var pinChoise = Console.ReadLine();
int pin;
try
{
while (true)
{
var tempValue = dht.Temperature;
var humValue = dht.Humidity;
pin = Convert.ToInt32(pinChoise);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
Console.WriteLine("Can't convert pin number.");
return;
}

Console.WriteLine("Press any key to stop the reading");

switch (choice.KeyChar)
{
case '2':
Console.WriteLine($"Reading temperature and humidity on DHT11, pin {pin}");
using (var dht11 = new Dht11(pin))
{
Dht(dht11);
}

break;
case '3':
Console.WriteLine($"Reading temperature and humidity on DHT12, pin {pin}");
using (var dht12 = new Dht12(pin))
{
Dht(dht12);
}

Console.WriteLine($"Temperature: {tempValue.DegreesCelsius:0.#}\u00B0C");
Console.WriteLine($"Relative humidity: {humValue:0.#}%");
break;
case '4':
Console.WriteLine($"Reading temperature and humidity on DHT21, pin {pin}");
using (var dht21 = new Dht21(pin))
{
Dht(dht21);
}

break;
case '5':
Console.WriteLine($"Reading temperature and humidity on DHT22, pin {pin}");
using (var dht22 = new Dht22(pin))
{
Dht(dht22);
}

break;
default:
Console.WriteLine("Please select one of the option.");
break;
}
}

private static void Dht(DhtBase dht)
{
while (!Console.KeyAvailable)
{
var temp = dht.Temperature;
var hum = dht.Humidity;
// You can only display temperature and humidity if the read is successful otherwise, this will raise an exception as
// both temperature and humidity are NAN
if (dht.IsLastReadSuccessful)
{
Console.WriteLine($"Temperature: {temp.DegreesCelsius}\u00B0C, Relative humidity: {hum.Percent}%");

// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine(
$"Heat index: {WeatherHelper.CalculateHeatIndex(tempValue, humValue).DegreesCelsius:0.#}\u00B0C");
$"Heat index: {WeatherHelper.CalculateHeatIndex(temp, hum).DegreesCelsius:0.#}\u00B0C");
Console.WriteLine(
$"Dew point: {WeatherHelper.CalculateDewPoint(tempValue, humValue).DegreesCelsius:0.#}\u00B0C");

Thread.Sleep(2000);
$"Dew point: {WeatherHelper.CalculateDewPoint(temp, hum).DegreesCelsius:0.#}\u00B0C");
}
else
{
Console.WriteLine("Error reading DHT sensor");
}

// You must wait some time before trying to read the next value
Thread.Sleep(2000);
}
}
}
Expand Down
Loading