defmodule Grizzly.ZWave.CommandClasses.SensorMultilevel do
@moduledoc """
"SensorMultilevel" Command Class
The Multilevel Sensor Command Class is used to advertise numerical sensor readings.
"""
@behaviour Grizzly.ZWave.CommandClass
alias Grizzly.ZWave.DecodeError
@sensor_types [
# byte 1
[
:air_temperature,
:general_purpose,
:luminance,
:power,
:humidity,
:velocity,
:direction,
:atmospheric_pressure
],
# byte 2
[
:barometric_pressure,
:solar_radiation,
:dew_point,
:rain_rate,
:tide_level,
:weight,
:voltage,
:current
],
# byte 3
[
:carbon_dioxide_level,
:air_flow,
:tank_capacity,
:distance,
:angle_position,
:rotation,
:water_temperature,
:soil_temperature
],
# byte 4
[
:seismic_intensity,
:seismic_magnitude,
:ultraviolet,
:electrical_resistivity,
:electrical_conductivity,
:loudness,
:moisture,
:frequency
],
# byte 5
[
:time,
:target_temperature,
:particulate_matter_2_5,
:formaldehyde_level,
:radon_concentration,
:methane_density,
:volatile_organic_compound_level,
:carbon_monoxide_level
],
# byte 6
[
:soil_humidity,
:soil_reactivity,
:soil_salinity,
:heart_rate,
:blood_pressure,
:muscle_mass,
:fat_mass,
:bone_mass
],
# byte 7
[
:total_body_water,
:basis_metabolic_rate,
:body_mass_index,
:acceleration_x_axis,
:acceleration_y_axis,
:acceleration_z_axis,
:smoke_density,
:water_flow
],
# byte 8
[
:water_pressure,
:rf_signal_strength,
:particulate_matter_10,
:respiratory_rate,
:relative_modulation_level,
:boiler_water_temperature,
:domestic_hot_water_temperature,
:outside_temperature
],
# byte 9
[
:exhaust_temperature,
:water_chlorine_level,
:water_acidity,
:water_oxidation_reduction_potential,
:heart_rate_lf_hf_ratio,
:motion_direction,
:applied_force,
:return_air_temperature
],
# byte 10
[
:supply_air_temperature,
:condensor_coil_temperature,
:evaporator_coil_temperature,
:liquid_line_temperature,
:discharge_line_temperature,
:suction_pressure,
:discharge_pressure,
:defrost_temperature
],
# byte 11
[
:ozone,
:sulfur_dioxide,
:nitrogen_dioxide,
:ammonia,
:lead,
:particulate_matter_1,
:unknown,
:unknown
]
]
@impl true
def byte(), do: 0x31
@impl true
def name(), do: :sensor_multilevel
def encode_sensor_type(:temperature), do: 0x01
def encode_sensor_type(:general), do: 0x02
def encode_sensor_type(:luminance), do: 0x03
def encode_sensor_type(:power), do: 0x04
def encode_sensor_type(:humidity), do: 0x05
def encode_sensor_type(:velocity), do: 0x06
def encode_sensor_type(:direction), do: 0x07
def encode_sensor_type(:atmospheric_pressure), do: 0x08
def encode_sensor_type(:barometric_pressure), do: 0x09
def encode_sensor_type(:solar_radiation), do: 0x0A
def encode_sensor_type(:dew_point), do: 0x0B
def encode_sensor_type(:rain_rate), do: 0x0C
def encode_sensor_type(:tide_level), do: 0x0D
def encode_sensor_type(:weight), do: 0x0E
def encode_sensor_type(:voltage), do: 0x0F
def encode_sensor_type(:current), do: 0x10
def encode_sensor_type(:co2_level), do: 0x11
def encode_sensor_type(:air_flow), do: 0x12
def encode_sensor_type(:tank_capacity), do: 0x13
def encode_sensor_type(:distance), do: 0x14
def encode_sensor_type(:angle_position), do: 0x15
def encode_sensor_type(:rotation), do: 0x16
def encode_sensor_type(:water_temperature), do: 0x17
def encode_sensor_type(:soil_temperature), do: 0x18
def encode_sensor_type(:seismic_intensity), do: 0x19
def encode_sensor_type(:seismic_magnitude), do: 0x1A
def encode_sensor_type(:ultraviolet), do: 0x1B
def encode_sensor_type(:electrical_resistivity), do: 0x1C
def encode_sensor_type(:electrical_conductivity), do: 0x1D
def encode_sensor_type(:loudness), do: 0x1E
def encode_sensor_type(:moisture), do: 0x1F
def encode_sensor_type(:frequency), do: 0x20
def encode_sensor_type(:time), do: 0x21
def encode_sensor_type(:target_temperature), do: 0x22
def decode_sensor_type(0x01), do: {:ok, :temperature}
def decode_sensor_type(0x02), do: {:ok, :general}
def decode_sensor_type(0x03), do: {:ok, :luminance}
def decode_sensor_type(0x04), do: {:ok, :power}
def decode_sensor_type(0x05), do: {:ok, :humidity}
def decode_sensor_type(0x06), do: {:ok, :velocity}
def decode_sensor_type(0x07), do: {:ok, :direction}
def decode_sensor_type(0x08), do: {:ok, :atmospheric_pressure}
def decode_sensor_type(0x09), do: {:ok, :barometric_pressure}
def decode_sensor_type(0x0A), do: {:ok, :solar_radiation}
def decode_sensor_type(0x0B), do: {:ok, :dew_point}
def decode_sensor_type(0x0C), do: {:ok, :rain_rate}
def decode_sensor_type(0x0D), do: {:ok, :tide_level}
def decode_sensor_type(0x0E), do: {:ok, :weight}
def decode_sensor_type(0x0F), do: {:ok, :voltage}
def decode_sensor_type(0x10), do: {:ok, :current}
def decode_sensor_type(0x11), do: {:ok, :co2_level}
def decode_sensor_type(0x12), do: {:ok, :air_flow}
def decode_sensor_type(0x13), do: {:ok, :tank_capacity}
def decode_sensor_type(0x14), do: {:ok, :distance}
def decode_sensor_type(0x15), do: {:ok, :angle_position}
def decode_sensor_type(0x16), do: {:ok, :rotation}
def decode_sensor_type(0x17), do: {:ok, :water_temperature}
def decode_sensor_type(0x18), do: {:ok, :soil_temperature}
def decode_sensor_type(0x19), do: {:ok, :seismic_intensity}
def decode_sensor_type(0x1A), do: {:ok, :seismic_magnitude}
def decode_sensor_type(0x1B), do: {:ok, :ultraviolet}
def decode_sensor_type(0x1C), do: {:ok, :electrical_resistivity}
def decode_sensor_type(0x1D), do: {:ok, :electrical_conductivity}
def decode_sensor_type(0x1E), do: {:ok, :loudness}
def decode_sensor_type(0x1F), do: {:ok, :moisture}
def decode_sensor_type(0x20), do: {:ok, :frequency}
def decode_sensor_type(0x21), do: {:ok, :time}
def decode_sensor_type(0x22), do: {:ok, :target_temperature}
def decode_sensor_type(byte),
do:
{:error, %DecodeError{value: byte, param: :sensor_type, command: :sensor_multilevel_report}}
@spec decode_sensor_types(binary) :: {:ok, [sensor_types: [atom]]} | {:error, %DecodeError{}}
def decode_sensor_types(binary) do
sensor_types =
:binary.bin_to_list(binary)
|> Enum.map(&bit_set_indices(<<&1>>))
|> Enum.with_index()
|> Enum.map(fn {bit_indices, byte} -> Enum.map(bit_indices, &decode_sensor(byte, &1)) end)
|> List.flatten()
if Enum.any?(sensor_types, &(&1 == nil)) do
{:error,
%DecodeError{
value: binary,
param: :sensor_types,
command: :sensor_multilevel_supported_sensor_report
}}
else
{:ok, [sensor_types: sensor_types]}
end
end
@spec encode_sensor_types([atom]) :: binary
def encode_sensor_types(sensor_types) do
for bit_list <- byte_indices(sensor_types) do
for bit <- Enum.reverse(bit_list), into: <<>>, do: <<bit::1>>
end
|> :binary.list_to_bin()
end
defp byte_indices(sensor_types) do
for byte <- (Enum.count(@sensor_types) - 1)..0 do
sensor_types_per_byte = Enum.at(@sensor_types, byte)
for index <- 0..7 do
if Enum.at(sensor_types_per_byte, index) in sensor_types, do: 1, else: 0
end
end
|> Enum.drop_while(fn indices -> Enum.all?(indices, &(&1 == 0)) end)
|> Enum.reverse()
end
defp bit_set_indices(byte) do
for(<<x::1 <- byte>>, do: x)
|> Enum.reverse()
|> Enum.with_index()
|> Enum.reduce([], fn {bit, index}, acc ->
if bit == 1, do: [index | acc], else: acc
end)
end
defp decode_sensor(byte, bit_index) do
Enum.at(@sensor_types, byte) |> Enum.at(bit_index)
end
end