Skip to content

Commit

Permalink
Add code to detect bad values from sensor, and bad converted values o…
Browse files Browse the repository at this point in the history
…utside operating range (#30)
  • Loading branch information
doawoo authored Nov 28, 2023
1 parent 94058e6 commit 7ad07ae
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 6 deletions.
2 changes: 1 addition & 1 deletion lib/sht4x.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule SHT4X do
The possible values can be:
- `:fresh` - This is a recent sample. See the `:stale_threshold`.
- `:stale` - This is an old sample that should be used with caution.
- `:unusable` - This is a default sample when no measurements are available.
- `:unusable` - This is a default sample when no measurements are available, or, the sensor is giving know bad values (see: https://github.com/elixir-sensors/sht4x/issues/29)
- `:converging` - This is optionally set by the temperature compensation algorithm to indicate that it was recently restarted without historic state information and needs more time to give accurate values
"""
@type quality :: :fresh | :stale | :unusable | :converging
Expand Down
62 changes: 57 additions & 5 deletions lib/sht4x/measurement.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ defmodule SHT4X.Measurement do
@moduledoc """
One sensor measurement
"""
require Logger

use TypedStruct

# Raw readings that, when converted, equate to the min and max operating
# ranges of Rh and temp [0 - 100] for Rh and [-40 - 125] for temperature.
# Expand range by 1 to avoid ceiling/floor issues.
@min_raw_rh 0x0C4A - 1
@max_raw_rh 0xD915 + 1

@min_raw_t 0x0750 - 1
@max_raw_t 0xF8AE + 1

typedstruct do
field(:dew_point_c, float)
field(:humidity_rh, float, enforce: true)
Expand All @@ -22,11 +32,30 @@ defmodule SHT4X.Measurement do
interpreted temperature and humidity. It does not apply any compensation so
this is real temperature and humidity detected.
"""
@spec from_raw(<<_::48>>) :: t()
def from_raw(<<raw_t::16, _crc1, raw_rh::16, _crc2>>) do
timestamp_ms = System.monotonic_time(:millisecond)
temperature_c = temperature_c_from_raw(raw_t)
humidity_rh = humidity_rh_from_raw(raw_rh)

if raw_reading_valid?(raw_t, raw_rh) do
make_measurement(raw_t, raw_rh, timestamp_ms)
else
# Raw readings invalid, don't even attempt to convert them
Logger.warning("Your SHT4X is returning values that could indicate it is damaged!")

__struct__(
temperature_c: 0.0,
humidity_rh: 0.0,
dew_point_c: 0.0,
raw_reading_temperature: raw_t,
raw_reading_humidity: raw_rh,
timestamp_ms: timestamp_ms,
quality: :unusable
)
end
end

defp make_measurement(raw_t, raw_rh, timestamp_ms) do
temperature_c = raw_to_temperature_c(raw_t)
humidity_rh = raw_to_humidity_rh(raw_rh)

__struct__(
temperature_c: temperature_c,
Expand All @@ -39,11 +68,34 @@ defmodule SHT4X.Measurement do
)
end

defp humidity_rh_from_raw(raw_rh) do
@spec raw_to_humidity_rh(0..0xFFFF) :: float()
def raw_to_humidity_rh(raw_rh) do
-6 + 125 * raw_rh / (0xFFFF - 1)
end

defp temperature_c_from_raw(raw_t) do
@spec humidity_rh_to_raw(float()) :: integer()
def humidity_rh_to_raw(rh) do
round((rh + 6) / 125 * (0xFFFF - 1))
end

@spec raw_to_temperature_c(0..0xFFFF) :: float()
def raw_to_temperature_c(raw_t) do
-45 + 175 * raw_t / (0xFFFF - 1)
end

@spec temperature_c_to_raw(float()) :: integer()
def temperature_c_to_raw(t) do
round((t + 45) / 175 * (0xFFFF - 1))
end

# Function to check the raw values read from the sensor
# A few bad values are known: 0x8000 and 0x8001 (according to Sensirion)
defp raw_reading_valid?(0x8000, 0x8000), do: false
defp raw_reading_valid?(0x8001, 0x8000), do: false

# Ensure raw values would be within min/max operating ranges
defp raw_reading_valid?(t, _rh) when t not in @min_raw_t..@max_raw_t, do: false
defp raw_reading_valid?(_t, rh) when rh not in @min_raw_rh..@max_raw_rh, do: false

defp raw_reading_valid?(_t, _rh), do: true
end
28 changes: 28 additions & 0 deletions test/sht4x/measurement_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,39 @@ defmodule SHT4X.MeasurementTest do
test "converts raw measurement" do
result = Measurement.from_raw(<<101, 233, 234, 109, 229, 160>>)

assert result.quality == :fresh
assert result.raw_reading_temperature == 26_089
assert result.raw_reading_humidity == 28_133

assert_in_delta result.temperature_c, 24.67, 0.01
assert_in_delta result.humidity_rh, 47.66, 0.01
assert_in_delta result.dew_point_c, 12.82, 0.01
end

test "detects possible damaged sensor by looking for 0x8000 in both RH and Temp values" do
result = Measurement.from_raw(<<128, 0, 162, 128, 0, 162>>)
assert result.quality == :unusable
end

test "detects possible damaged sensor by looking for 0x8001 in raw Temp value, and 0x8000 in raw Rh value" do
result = Measurement.from_raw(<<128, 1, 162, 128, 0, 162>>)
assert result.quality == :unusable
end

test "detects possible damaged sensor by looking for values outside of operating range (Rh)" do
result = Measurement.from_raw(<<101, 233, 234, 0xFF, 0xFF, 172>>)
assert result.quality == :unusable
end

test "detects possible damaged sensor by looking for values outside of operating range (C)" do
result = Measurement.from_raw(<<0xFF, 0xFF, 172, 109, 229, 160>>)
assert result.quality == :unusable
end

test "converts boundary values to expected raw values" do
assert Measurement.humidity_rh_to_raw(0) == 0xC4A
assert Measurement.humidity_rh_to_raw(100) == 0xD915
assert Measurement.temperature_c_to_raw(-40) == 0x750
assert Measurement.temperature_c_to_raw(125) == 0xF8AE
end
end

0 comments on commit 7ad07ae

Please sign in to comment.