Current section
Files
Jump to
Current section
Files
lib/ttlock_client/lock_management.ex
defmodule TTlockClient.LockManagement do
@moduledoc """
TTLock Lock Management client for retrieving lock information.
This module handles:
- Getting list of locks for which you are the top administrator
- Retrieving detailed information about specific locks
- Proper pagination and filtering support
## Usage
# Get lock list
{:ok, response} = TTlockClient.LockManagement.get_lock_list(
client_id: "your_client_id",
access_token: "your_access_token",
page_no: 1,
page_size: 20
)
# Get lock details
{:ok, response} = TTlockClient.LockManagement.get_lock_details(
client_id: "your_client_id",
access_token: "your_access_token",
lock_id: 532323
)
"""
@base_url "https://euapi.ttlock.com"
@lock_list_endpoint "/v3/lock/list"
@lock_detail_endpoint "/v3/lock/detail"
@type lock_list_config :: [
client_id: String.t(),
access_token: String.t(),
page_no: integer(),
page_size: integer(),
lock_alias: String.t() | nil,
group_id: integer() | nil
]
@type lock_detail_config :: [
client_id: String.t(),
access_token: String.t(),
lock_id: integer()
]
@type lock_info :: %{
lockId: integer(),
lockName: String.t(),
lockAlias: String.t(),
lockMac: String.t(),
electricQuantity: integer(),
featureValue: String.t(),
hasGateway: integer(),
lockData: String.t(),
groupId: integer() | nil,
groupName: String.t() | nil,
date: integer()
}
@type lock_list_response :: %{
list: [lock_info()],
pageNo: integer(),
pageSize: integer(),
pages: integer(),
total: integer()
}
@type lock_detail_response :: %{
lockId: integer(),
lockName: String.t(),
lockAlias: String.t(),
lockMac: String.t(),
noKeyPwd: String.t(),
featureValue: String.t(),
electricQuantity: integer(),
timezoneRawOffset: integer(),
modelNum: String.t(),
hardwareRevision: String.t(),
firmwareRevision: String.t(),
autoLockTime: integer(),
lockSound: integer(),
privacyLock: integer(),
tamperAlert: integer(),
resetButton: integer(),
openDirection: integer(),
passageMode: integer(),
passageModeAutoUnlock: integer(),
date: integer()
}
@doc """
Get list of locks for which you are the top administrator.
Returns all locks regardless of whether they were added via TTLock App or SDK.
Locks shared with you via eKeys are NOT returned - use the ekey list API for those.
## Parameters
- `client_id`: Application clientId
- `access_token`: Access token from OAuth
- `page_no`: Page number (starts from 1)
- `page_size`: Number of records per page (default 20, max 1000)
- `lock_alias`: Optional search by lock alias (fuzzy match)
- `group_id`: Optional filter by group ID
## Returns
- `{:ok, lock_list_response}` on success
- `{:error, reason}` on failure
## Example
{:ok, %{
"list" => [
%{
"lockId" => 532323,
"lockName" => "YS1003_c18c9c",
"lockAlias" => "Front door lock",
"lockMac" => "C5:40:E0:9C:8C:C1",
"electricQuantity" => 55,
"featureValue" => "3F0421C4F5F3",
"hasGateway" => 1,
"lockData" => "xxxxxxxxxxxxx",
"groupId" => 456,
"groupName" => "The 4th floor",
"date" => 1528878944000
}
],
"pageNo" => 1,
"pageSize" => 20,
"pages" => 1,
"total" => 1
}} = TTlockClient.LockManagement.get_lock_list(
client_id: "your_client_id",
access_token: "your_access_token",
page_no: 1,
page_size: 20
)
"""
@spec get_lock_list(lock_list_config()) :: {:ok, lock_list_response()} | {:error, term()}
def get_lock_list(opts \\ []) do
client_id = get_config_value(opts, :client_id)
access_token = get_access_token(opts)
page_no = Keyword.get(opts, :page_no, 1)
page_size = Keyword.get(opts, :page_size, 20)
lock_alias = Keyword.get(opts, :lock_alias)
group_id = Keyword.get(opts, :group_id)
params = [
{"clientId", client_id},
{"accessToken", access_token},
{"pageNo", Integer.to_string(page_no)},
{"pageSize", Integer.to_string(page_size)},
{"date", Integer.to_string(current_timestamp())}
]
params = maybe_add_param(params, "lockAlias", lock_alias)
params = maybe_add_param(params, "groupId", group_id && Integer.to_string(group_id))
make_get_request(@lock_list_endpoint, params)
end
@doc """
Get detailed information about a specific lock.
Only the administrator can request this. eKey users should use the Get one eKey API
to obtain lockData.
## Parameters
- `client_id`: Application clientId
- `access_token`: Access token from OAuth
- `lock_id`: Lock ID from Lock init or Lock List API
## Returns
- `{:ok, lock_detail_response}` on success
- `{:error, reason}` on failure
## Example
{:ok, %{
"lockId" => 532323,
"lockName" => "YS1003_c18c9c",
"lockAlias" => "Front door",
"lockMac" => "C5:40:E0:9C:8C:C1",
"noKeyPwd" => "0062386",
"featureValue" => "3F0421C4F5F3",
"electricQuantity" => 55,
"timezoneRawOffset" => 28800000,
"modelNum" => "SN167-NODFU_PV53",
"hardwareRevision" => "1.6",
"firmwareRevision" => "4.1.18.0520",
"autoLockTime" => -1,
"lockSound" => 1,
"privacyLock" => 2,
"tamperAlert" => 0,
"resetButton" => 1,
"openDirection" => 2,
"passageMode" => 2,
"passageModeAutoUnlock" => 2,
"date" => 1528878944000
}} = TTlockClient.LockManagement.get_lock_details(
client_id: "your_client_id",
access_token: "your_access_token",
lock_id: 532323
)
"""
@spec get_lock_details(lock_detail_config()) :: {:ok, lock_detail_response()} | {:error, term()}
def get_lock_details(opts \\ []) do
client_id = get_config_value(opts, :client_id)
access_token = get_access_token(opts)
lock_id = Keyword.fetch!(opts, :lock_id)
params = [
{"clientId", client_id},
{"accessToken", access_token},
{"lockId", Integer.to_string(lock_id)},
{"date", Integer.to_string(current_timestamp())}
]
make_get_request(@lock_detail_endpoint, params)
end
# Private functions
defp get_config_value(opts, key) do
case Keyword.get(opts, key) do
nil ->
env_key =
case key do
:client_id -> :ttlock_client_id
:client_secret -> :ttlock_client_secret
_ -> key
end
case Application.get_env(:ex_ttlock, env_key) do
nil -> raise ArgumentError, "#{key} is required either as option or in config"
value -> value
end
value ->
value
end
end
defp get_access_token(opts) do
case Keyword.get(opts, :access_token) do
nil ->
client_id = get_config_value(opts, :client_id)
client_secret = get_config_value(opts, :client_secret)
case TTlockClient.OAuth.get_valid_access_token(
client_id: client_id,
client_secret: client_secret
) do
{:ok, token} ->
token
{:error, :authentication_required} ->
raise RuntimeError,
"Access token expired and no valid refresh token available. Please authenticate again."
{:error, reason} ->
raise RuntimeError, "Failed to get access token: #{inspect(reason)}"
end
token ->
token
end
end
defp current_timestamp do
System.system_time(:millisecond)
end
defp maybe_add_param(params, _key, nil), do: params
defp maybe_add_param(params, key, value), do: [{key, value} | params]
defp make_get_request(endpoint, params) do
url = @base_url <> endpoint <> "?" <> URI.encode_query(params)
headers = [{"Content-Type", "application/x-www-form-urlencoded"}]
case HTTPoison.get(url, headers) do
{:ok, %HTTPoison.Response{status_code: 200, body: response_body}} ->
case Jason.decode(response_body) do
{:ok, data} -> {:ok, data}
{:error, _} = error -> error
end
{:ok, %HTTPoison.Response{status_code: status_code, body: response_body}} ->
case Jason.decode(response_body) do
{:ok, error_data} -> {:error, {status_code, error_data}}
{:error, _} -> {:error, {status_code, response_body}}
end
{:error, %HTTPoison.Error{} = error} ->
{:error, error}
end
end
end