% @doc API for the
%
% PmodHYGRO
% .
%
% Start the driver with
% ```
% 1> grisp:add_device(i2c, pmod_hygro).
% '''
% @end
-module(pmod_hygro).
-behaviour(gen_server).
% API
-export([start_link/2]).
-export([temp/0]).
-export([humid/0]).
-export([measurements/0]).
% Callbacks
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([code_change/3]).
-export([terminate/2]).
-define(DEVICE_ADR, 16#40).
-define(REG_TEMPERATURE, 16#00).
-define(REG_MANUFACTURER_ID, 16#FE).
-define(REG_DEVICE_ID, 16#FF).
-define(DELAY_TIME, 15).
-define(MANUFACTURER_ID, 16#5449).
-define(DEVICE_ID, 16#1050).
%--- Records -------------------------------------------------------------------
%
-record(state, {bus}).
%--- API -----------------------------------------------------------------------
% @private
start_link(Slot, _Opts) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Slot, []).
% @doc Measure the temperature in °C.
%
% === Example ===
% ```
% 2> pmod_hygro:temp().
% [{temp,24.6746826171875}]
% '''
-spec temp() -> [{temp, float()}].
temp() ->
gen_server:call(?MODULE, temp).
% @doc Measure the humidity in %.
%
% === Example ===
% ```
% 2> pmod_hygro:humid().
% [{humid,50.225830078125}]
% '''
-spec humid() -> [{humid, float()}].
humid() ->
gen_server:call(?MODULE, humid).
% @doc Measure the temperature and humidity.
%
% === Example ===
% ```
% 2> pmod_hygro:measurements().
% [{temp,24.52362060546875},{humid,50.823974609375}]
% '''
-spec measurements() -> [{temp, float()}|{humid, float()}].
measurements() ->
gen_server:call(?MODULE, measurements).
%--- Callbacks -----------------------------------------------------------------
% @private
init(i2c = Slot) ->
Bus = grisp_i2c:open(i2c1),
verify_device(Bus),
grisp_devices:register(Slot, ?MODULE),
{ok, #state{bus = Bus}}.
% @private
handle_call(temp, _From, #state{bus = Bus} = State) ->
{ok, <>} = device_request(Bus, ?REG_TEMPERATURE, ?DELAY_TIME, 2),
Temp = evaluate_temp(T),
{reply, [{temp, Temp}], State};
handle_call(humid, _From, #state{bus = Bus} = State) ->
{ok, <<_:14, _:2, H:14/unsigned-big, _:2>>} = device_request(Bus, ?REG_TEMPERATURE, ?DELAY_TIME, 4),
Humid = evaluate_humid(H),
{reply, [{humid, Humid}], State};
handle_call(measurements, _From, #state{bus = Bus} = State) ->
{ok, <>} = device_request(Bus, ?REG_TEMPERATURE, ?DELAY_TIME, 4),
Temp = evaluate_temp(T),
Humid = evaluate_humid(H),
{reply, [{temp, Temp}, {humid, Humid}], State}.
% @private
handle_cast(Request, _State) -> error({unknown_cast, Request}).
% @private
handle_info(Info, _State) -> error({unknown_info, Info}).
% @private
code_change(_OldVsn, State, _Extra) -> {ok, State}.
% @private
terminate(_Reason, _State) -> ok.
%--- Internal ------------------------------------------------------------------
verify_device(Bus) ->
{ok, <>} = device_request(Bus, ?REG_MANUFACTURER_ID, 0, 2),
{ok, <>} = device_request(Bus, ?REG_DEVICE_ID, 0, 2),
case {ManufacturerID, DeviceID} of
{?MANUFACTURER_ID, ?DEVICE_ID} -> ok;
Other -> error({device_mismatch, Other})
end.
device_request(Bus, Register, Delay, BytesToRead) ->
[ok] = grisp_i2c:transfer(Bus, [{write, ?DEVICE_ADR, 0, <>}]),
timer:sleep(Delay),
[Response] = grisp_i2c:transfer(Bus, [{read, ?DEVICE_ADR, 0, BytesToRead}]),
{ok, Response}.
evaluate_temp(T) ->
(T / 16384) * 165 - 40.
evaluate_humid(H) ->
(H / 16384) * 100.