defmodule QMI.Codec.DeviceManagement do
@moduledoc """
Codec for making device management requests
"""
@get_device_mfr 0x0021
@get_device_model_id 0x0022
@get_device_rev_id 0x0023
@get_device_serial_numbers 0x0025
@get_device_hardware_rev 0x002C
@get_operating_mode 0x002D
@set_operating_mode 0x002E
@typedoc """
The serial numbers assigned to the device
* `esn` - for 3GPP2 devices
* `imei` - for 3GPP devices
* `meid` - for 3GPP and 3GPP2 devices
* `imeisv_svn` - for 3GPP devices
"""
@type serial_numbers() :: %{
esn: binary() | nil,
imei: binary() | nil,
meid: binary() | nil,
imeisv_svn: binary() | nil
}
@doc """
Get the device hardware revision
"""
@spec get_device_hardware_rev() :: QMI.request()
def get_device_hardware_rev() do
%{
service_id: 0x02,
payload: [<<@get_device_hardware_rev::16-little, 0, 0>>],
decode: &parse_get_device_hardware_rev/1
}
end
@doc """
Get the device manufacturer
"""
@spec get_device_mfr() :: QMI.request()
def get_device_mfr() do
%{
service_id: 0x02,
payload: [<<@get_device_mfr::16-little, 0, 0>>],
decode: &parse_get_device_mfr/1
}
end
@doc """
Get the device model
"""
@spec get_device_model_id() :: QMI.request()
def get_device_model_id() do
%{
service_id: 0x02,
payload: [<<@get_device_model_id::16-little, 0, 0>>],
decode: &parse_get_device_model_id/1
}
end
@doc """
Get the firmware revision id
"""
@spec get_device_rev_id() :: QMI.request()
def get_device_rev_id() do
%{
service_id: 0x02,
payload: [<<0x23::16-little, 0x00, 0x00>>],
decode: &parse_get_device_rev_id/1
}
end
defp parse_get_device_hardware_rev(
<<@get_device_hardware_rev::16-little, _size::16-little, _result_tlv::7*8, 1,
len::16-little, hardware_rev::size(len)-binary>>
) do
{:ok, hardware_rev}
end
defp parse_get_device_mfr(
<<@get_device_mfr::16-little, _size::16-little, _result_tlv::7*8, 1, len::16-little,
mfr::size(len)-binary>>
) do
{:ok, mfr}
end
defp parse_get_device_model_id(
<<@get_device_model_id::16-little, _size::16-little, _result_tlv::7*8, 1, len::16-little,
model::size(len)-binary>>
) do
{:ok, model}
end
defp parse_get_device_rev_id(<<@get_device_rev_id::16-little, _size::16-little, tlvs::binary>>) do
parse_firmware_rev_id(tlvs)
end
defp parse_firmware_rev_id(<<>>) do
# This is an unexpected response becuase the specification says at least
# the firmware revision id this is a manditory TLV.
{:error, :unexpected_response}
end
defp parse_firmware_rev_id(<<0x01, size::16-little, rev_id::size(size)-binary, _rest::binary>>) do
{:ok, rev_id}
end
defp parse_firmware_rev_id(<<_type, size::16-little, _values::size(size)-binary, rest::binary>>) do
parse_firmware_rev_id(rest)
end
@doc """
Request for the serial numbers of the device
"""
@spec get_device_serial_numbers() :: QMI.request()
def get_device_serial_numbers() do
%{
service_id: 0x02,
payload: [<<@get_device_serial_numbers::16-little>>, 0x00, 0x00],
decode: &parse_device_serial_numbers/1
}
end
defp parse_device_serial_numbers(
<<@get_device_serial_numbers::16-little, _size::16-little, tlvs::binary>>
) do
serial_numbers = %{
esn: nil,
imei: nil,
meid: nil,
imeisv_svn: nil
}
parse_device_serial_numbers(serial_numbers, tlvs)
end
defp parse_device_serial_numbers(serial_numbers, <<>>), do: {:ok, serial_numbers}
defp parse_device_serial_numbers(
serial_numbers,
<<0x10, length::16-little, esn::binary-size(length), rest::binary>>
) do
serial_numbers
|> Map.put(:esn, esn)
|> parse_device_serial_numbers(rest)
end
defp parse_device_serial_numbers(
serial_numbers,
<<0x11, length::16-little, imei::binary-size(length), rest::binary>>
) do
serial_numbers
|> Map.put(:imei, imei)
|> parse_device_serial_numbers(rest)
end
defp parse_device_serial_numbers(
serial_numbers,
<<0x12, length::16-little, meid::binary-size(length), rest::binary>>
) do
serial_numbers
|> Map.put(:meid, meid)
|> parse_device_serial_numbers(rest)
end
defp parse_device_serial_numbers(
serial_numbers,
<<0x13, length::16-little, imeisv_svn::binary-size(length), rest::binary>>
) do
serial_numbers
|> Map.put(:imeisv_svn, imeisv_svn)
|> parse_device_serial_numbers(rest)
end
defp parse_device_serial_numbers(
serial_numbers,
<<_type, length::16-little, _values::binary-size(length), rest::binary>>
) do
parse_device_serial_numbers(serial_numbers, rest)
end
@operating_modes %{
0x00 => :online,
0x01 => :low_power,
0x02 => :factory_test,
0x03 => :offline,
0x04 => :resetting,
0x05 => :shutting_down,
0x06 => :persistent_low_power,
0x07 => :mode_only_low_power,
0x08 => :network_test_gw
}
@type operating_mode ::
:online
| :low_power
| :factory_test
| :offline
| :resetting
| :shutting_down
| :persistent_low_power
| :mode_only_low_power
| :network_test_gw
@type offline_reason ::
:host_image_misconfiguration
| :pri_image_misconfiguration
| :pri_version_incompatible
| :device_memory_full
@type operating_mode_response :: %{
required(:operating_mode) => operating_mode(),
optional(:offline_reason) => offline_reason(),
optional(:hardware_controlled_mode?) => boolean()
}
@doc """
Get the operating mode of a device
"""
@spec get_operating_mode() :: QMI.request()
def get_operating_mode() do
%{
service_id: 0x02,
payload: <<@get_operating_mode::16-little, 0, 0>>,
decode: &parse_operating_mode(%{}, &1)
}
end
defp parse_operating_mode(
result,
<<@get_operating_mode::16-little, _size::16-little, _result_tlv::7*8, 1, 1::16-little,
mode_num, tlvs::binary>>
) do
result
|> Map.put(:operating_mode, @operating_modes[mode_num])
|> parse_operating_mode(tlvs)
end
defp parse_operating_mode(result, <<0x10, 2::16-little, offline_num::16-little, rest::binary>>) do
offline_reason =
case offline_num do
0x0001 -> :host_image_misconfiguration
0x0002 -> :pri_image_misconfiguration
0x0004 -> :pri_version_incompatible
0x0008 -> :device_memory_full
end
Map.put(result, :offline_reason, offline_reason)
|> parse_operating_mode(rest)
end
defp parse_operating_mode(result, <<0x11, 1::16-little, hw_ctl_num, rest::binary>>) do
Map.put(result, :hardware_controlled_mode?, hw_ctl_num == 1)
|> parse_operating_mode(rest)
end
defp parse_operating_mode(result, <<>>), do: {:ok, result}
@doc """
Set operating mode of the device
"""
@spec set_operating_mode(operating_mode()) :: QMI.request()
def set_operating_mode(mode) do
[mode_num] = for {i, ^mode} <- @operating_modes, do: i
%{
service_id: 0x02,
payload: <<@set_operating_mode::16-little, 4::16-little, 1, 1::16-little, mode_num>>,
decode: fn _ -> :ok end
}
end
end