TTlockClient (TTlockClient v2.1.0)

TTLock client library for Elixir.

This library provides a clean, centralized interface to the TTLock Open Platform API with automatic OAuth 2.0 token management and refresh capabilities.

Getting Started

First, configure your client credentials:

TTlockClient.configure("your_client_id", "your_client_secret")

Then authenticate with your TTLock app credentials:

TTlockClient.authenticate("your_username", "your_password")

Now you can get valid tokens for API requests:

{:ok, token} = TTlockClient.get_valid_token()

Configuration

The library supports configuration through application config:

config :ex_ttlock,
  client_id: "your_client_id",
  client_secret: "your_client_secret",
  base_url: "https://euapi.ttlock.com"  # optional

Architecture

This library implements a centralized authentication state manager using GenServer that handles all token lifecycle management. This eliminates the need for module-specific authentication logic and provides thread-safe access to tokens across your application.

Key features:

  • Automatic token refresh before expiry
  • Thread-safe token access
  • Centralized authentication state
  • Error handling and recovery
  • Proper OTP supervision

Summary

Functions

Adds a permanent passcode to a lock via gateway.

Adds a temporary passcode to a lock via gateway.

Authenticates with TTLock using your app credentials.

Changes only a passcode's validity period.

Configures the TTLock client with your application credentials.

Deletes a passcode from a lock via gateway.

Convenience function to delete a passcode via gateway.

Gets all locks for the authenticated user (handles pagination automatically).

Gets the list of locks for a specified gateway.

Gets the list of gateways for the authenticated user account.

Gets detailed information about a specific lock.

Gets all passcodes for a lock (convenience function).

Gets the list of locks for the authenticated user.

Gets the user ID of the currently authenticated user.

Gets a valid access token for API requests.

Helper function to check if the client is ready for API calls.

Manually refreshes the current access token.

Resets all authentication state.

Searches passcodes by name or passcode value.

Convenience function to configure and authenticate in one call.

Convenience function to start with environment variables.

Gets the current authentication status.

Functions

add_passcode(lock_id, passcode, name \\ nil, passcode_type \\ 3, start_date \\ nil, end_date \\ nil, add_type \\ 2)

@spec add_passcode(
  integer(),
  integer(),
  String.t() | nil,
  integer(),
  integer() | nil,
  integer() | nil,
  integer()
) :: {:ok, map()} | {:error, term()}

Adds a custom passcode with full control over parameters.

Parameters

  • lock_id - The lock ID to add the passcode to
  • passcode - The 4-9 digit passcode
  • name - Optional name/alias for the passcode
  • passcode_type - 2 = permanent, 3 = period
  • start_date - Start time in milliseconds (required for period type)
  • end_date - End time in milliseconds (required for period type)
  • add_type - 2 = Gateway/WiFi

Examples

# Permanent passcode via gateway
{:ok, result} = TTlockClient.add_passcode(12345, 123456, "Guest", 2, nil, nil, 2)

Returns

  • {:ok, response} - Contains keyboardPwdId
  • {:error, reason} - Request failed

add_permanent_passcode(lock_id, passcode, name \\ nil)

@spec add_permanent_passcode(integer(), integer(), String.t() | nil) ::
  {:ok, map()} | {:error, term()}

Adds a permanent passcode to a lock via gateway.

Parameters

  • lock_id - The lock ID to add the passcode to
  • passcode - The 4-9 digit passcode
  • name - Optional name/alias for the passcode

Examples

{:ok, %{keyboardPwdId: passcode_id}} = TTlockClient.add_permanent_passcode(12345, 123456, "Guest")

Returns

  • {:ok, response} - Contains keyboardPwdId
  • {:error, reason} - Request failed

add_temporary_passcode(lock_id, passcode, start_date, end_date, name \\ nil)

@spec add_temporary_passcode(
  integer(),
  integer(),
  DateTime.t() | integer(),
  DateTime.t() | integer(),
  String.t() | nil
) :: {:ok, map()} | {:error, term()}

Adds a temporary passcode to a lock via gateway.

Parameters

  • lock_id - The lock ID to add the passcode to
  • passcode - The 4-9 digit passcode
  • start_date - Start time (DateTime or milliseconds)
  • end_date - End time (DateTime or milliseconds)
  • name - Optional name/alias for the passcode

Examples

start_time = DateTime.utc_now()
end_time = DateTime.add(start_time, 7, :day)
{:ok, result} = TTlockClient.add_temporary_passcode(12345, 987654, start_time, end_time, "Week Access")

Returns

  • {:ok, response} - Contains keyboardPwdId
  • {:error, reason} - Request failed

authenticate(username, password)

@spec authenticate(String.t(), String.t()) :: :ok | {:error, term()}

Authenticates with TTLock using your app credentials.

Important: Use your TTLock mobile app credentials here, not your developer portal credentials. The username should be the same one you use to log into the TTLock mobile application.

Parameters

  • username - Your TTLock app username (often a phone number like "+8618966498228")
  • password - Your TTLock app password (will be MD5 hashed automatically)

Examples

TTlockClient.authenticate("+8618966498228", "your_password")
TTlockClient.authenticate("your_email@example.com", "your_password")

Returns

  • :ok - Authentication successful, tokens stored
  • {:error, reason} - Authentication failed

Common error reasons:

  • :not_configured - Need to call configure/2 first
  • %{error_code: code, description: msg} - TTLock API error
  • Network/HTTP errors

change_passcode(lock_id, passcode_id, new_name \\ nil, new_passcode \\ nil, start_date \\ nil, end_date \\ nil)

@spec change_passcode(
  integer(),
  integer(),
  String.t() | nil,
  integer() | nil,
  DateTime.t() | integer() | nil,
  DateTime.t() | integer() | nil
) :: {:ok, map()} | {:error, term()}

Changes a passcode's name, value, or validity period.

Can change any combination of passcode properties. At least one of the optional parameters must be provided to perform a change.

Parameters

  • lock_id - The lock ID containing the passcode
  • passcode_id - The passcode ID to change
  • new_name - Optional new name for the passcode
  • new_passcode - Optional new passcode value (4-9 digits)
  • start_date - Optional new start time (DateTime or milliseconds)
  • end_date - Optional new end time (DateTime or milliseconds)

Examples

# Change name only
{:ok, result} = TTlockClient.change_passcode(12345, 67890, "New Name")

# Change passcode value only
{:ok, result} = TTlockClient.change_passcode(12345, 67890, nil, 999888)

# Change validity period only
start_time = DateTime.utc_now()
end_time = DateTime.add(start_time, 30, :day)
{:ok, result} = TTlockClient.change_passcode(12345, 67890, nil, nil, start_time, end_time)

# Change multiple properties
{:ok, result} = TTlockClient.change_passcode(12345, 67890, "Updated", 888999, start_time, end_time)

Returns

  • {:ok, response} - Success with status information
  • {:error, reason} - Request failed

change_passcode_name(lock_id, passcode_id, new_name)

@spec change_passcode_name(integer(), integer(), String.t()) ::
  {:ok, map()} | {:error, term()}

Changes only a passcode's name.

Parameters

  • lock_id - The lock ID containing the passcode
  • passcode_id - The passcode ID to change
  • new_name - The new name for the passcode

Examples

{:ok, result} = TTlockClient.change_passcode_name(12345, 67890, "Updated Guest Access")

Returns

  • {:ok, response} - Success with status information
  • {:error, reason} - Request failed

change_passcode_period(lock_id, passcode_id, start_date, end_date)

@spec change_passcode_period(
  integer(),
  integer(),
  DateTime.t() | integer(),
  DateTime.t() | integer()
) :: {:ok, map()} | {:error, term()}

Changes only a passcode's validity period.

Parameters

  • lock_id - The lock ID containing the passcode
  • passcode_id - The passcode ID to change
  • start_date - New start time (DateTime or milliseconds)
  • end_date - New end time (DateTime or milliseconds)

Examples

start_time = DateTime.utc_now()
end_time = DateTime.add(start_time, 30, :day)
{:ok, result} = TTlockClient.change_passcode_period(12345, 67890, start_time, end_time)

Returns

  • {:ok, response} - Success with status information
  • {:error, reason} - Request failed

change_passcode_value(lock_id, passcode_id, new_passcode)

@spec change_passcode_value(integer(), integer(), integer()) ::
  {:ok, map()} | {:error, term()}

Changes only a passcode's value.

Parameters

  • lock_id - The lock ID containing the passcode
  • passcode_id - The passcode ID to change
  • new_passcode - The new passcode value (4-9 digits)

Examples

{:ok, result} = TTlockClient.change_passcode_value(12345, 67890, 999888)

Returns

  • {:ok, response} - Success with status information
  • {:error, reason} - Request failed

configure(client_id, client_secret, base_url \\ "https://euapi.ttlock.com")

@spec configure(String.t(), String.t(), String.t()) :: :ok | {:error, term()}

Configures the TTLock client with your application credentials.

Parameters

  • client_id - Your application's client ID from the TTLock developer portal
  • client_secret - Your application's client secret from the TTLock developer portal
  • base_url - Optional API base URL (defaults to EU endpoint)

Examples

TTlockClient.configure("your_client_id", "your_client_secret")

# For different regions
TTlockClient.configure("client_id", "client_secret", "https://usapi.ttlock.com")

Returns

  • :ok - Configuration successful
  • {:error, reason} - Configuration failed

delete_passcode(lock_id, passcode_id)

@spec delete_passcode(integer(), integer()) :: {:ok, map()} | {:error, term()}

Deletes a passcode from a lock via gateway.

The passcode will be deleted directly via the cloud API for WiFi locks or locks connected to a gateway.

Parameters

  • lock_id - The lock ID containing the passcode
  • passcode_id - The passcode ID to delete

Examples

{:ok, result} = TTlockClient.delete_passcode(12345, 67890)

Returns

  • {:ok, response} - Success with status information
  • {:error, reason} - Request failed

delete_passcode_via_gateway(lock_id, passcode_id)

@spec delete_passcode_via_gateway(integer(), integer()) ::
  {:ok, map()} | {:error, term()}

Convenience function to delete a passcode via gateway.

This is an alias for delete_passcode/2 for clarity.

Parameters

  • lock_id - The lock ID containing the passcode
  • passcode_id - The passcode ID to delete

Examples

{:ok, result} = TTlockClient.delete_passcode_via_gateway(12345, 67890)

Returns

  • {:ok, response} - Success with status information
  • {:error, reason} - Request failed

get_all_locks(lock_alias \\ nil, group_id \\ nil)

@spec get_all_locks(String.t() | nil, integer() | nil) ::
  {:ok, [map()]} | {:error, term()}

Gets all locks for the authenticated user (handles pagination automatically).

Parameters

  • lock_alias - Optional filter by lock alias
  • group_id - Optional filter by group ID

Examples

{:ok, all_locks} = TTlockClient.get_all_locks()
{:ok, filtered_locks} = TTlockClient.get_all_locks("Front Door")

Returns

  • {:ok, [lock_records]} - List of all locks
  • {:error, reason} - Request failed

get_gateway_locks(gateway_id)

@spec get_gateway_locks(integer()) :: {:ok, map()} | {:error, term()}

Gets the list of locks for a specified gateway.

Parameters

  • gateway_id - Gateway ID

Examples

{:ok, locks} = TTlockClient.get_gateway_locks(12345)

Returns

  • {:ok, %{list: [lock_info]}} - List of locks with their information
  • {:error, reason} - Request failed

get_gateways(page_no \\ 1, page_size \\ 20, order_by \\ 1)

@spec get_gateways(integer(), integer(), integer()) :: {:ok, map()} | {:error, term()}

Gets the list of gateways for the authenticated user account.

Parameters

  • page_no - Page number (starting from 1, default 1)
  • page_size - Number of items per page (max 200, default 20)
  • order_by - Sort order: 0 = by name, 1 = reverse by time, 2 = reverse by name (default 1)

Examples

{:ok, response} = TTlockClient.get_gateways()
{:ok, response} = TTlockClient.get_gateways(2, 50, 0)

Returns

  • {:ok, response} - List of gateways with pagination info
  • {:error, reason} - Request failed

get_lock(lock_id)

@spec get_lock(integer()) :: {:ok, map()} | {:error, term()}

Gets detailed information about a specific lock.

Parameters

  • lock_id - The ID of the lock to retrieve

Examples

{:ok, lock_detail} = TTlockClient.get_lock(12345)

Returns

  • {:ok, lock_detail} - Detailed lock information
  • {:error, reason} - Request failed or lock not found

get_lock_passcodes(lock_id, search_str \\ nil)

@spec get_lock_passcodes(integer(), String.t() | nil) ::
  {:ok, map()} | {:error, term()}

Gets all passcodes for a lock (convenience function).

Parameters

  • lock_id - The lock ID to get passcodes for
  • search_str - Optional search string

Examples

{:ok, %{list: passcodes}} = TTlockClient.get_lock_passcodes(12345)
{:ok, results} = TTlockClient.get_lock_passcodes(12345, "Guest")

Returns

  • {:ok, response} - Contains list of passcodes
  • {:error, reason} - Request failed

get_locks(page_no \\ 1, page_size \\ 20, lock_alias \\ nil, group_id \\ nil)

@spec get_locks(integer(), integer(), String.t() | nil, integer() | nil) ::
  {:ok, map()} | {:error, term()}

Gets the list of locks for the authenticated user.

Parameters

  • page_no - Page number (default 1)
  • page_size - Items per page (default 20, max 1000)
  • lock_alias - Optional filter by lock alias
  • group_id - Optional filter by group ID

Examples

# Get first page with defaults
{:ok, locks} = TTlockClient.get_locks()

# Get specific page
{:ok, locks} = TTlockClient.get_locks(2, 50)

# Filter by lock alias
{:ok, locks} = TTlockClient.get_locks(1, 20, "Front Door")

Returns

  • {:ok, response} - Contains list, pagination info
  • {:error, reason} - Request failed

get_passcodes(lock_id, search_str \\ nil, page_no \\ 1, page_size \\ 20, order_by \\ 1)

@spec get_passcodes(integer(), String.t() | nil, integer(), integer(), integer()) ::
  {:ok, map()} | {:error, term()}

Gets all passcodes for a lock.

Parameters

  • lock_id - The lock ID to get passcodes for
  • search_str - Optional search keyword (fuzzy search by name or exact match by passcode)
  • page_no - Page number (default 1)
  • page_size - Items per page (default 20, max 200)
  • order_by - Sorting: 0 = by name, 1 = reverse by time, 2 = reverse by name (default 1)

Examples

# Get all passcodes for a lock
{:ok, %{list: passcodes, total: count}} = TTlockClient.get_passcodes(12345)

# Search for specific passcodes
{:ok, response} = TTlockClient.get_passcodes(12345, "Guest")

# Get specific page
{:ok, response} = TTlockClient.get_passcodes(12345, nil, 2, 50, 1)

Returns

  • {:ok, response} - Contains list, pagination info
  • {:error, reason} - Request failed

get_user_id()

@spec get_user_id() :: {:ok, integer()} | {:error, :not_authenticated}

Gets the user ID of the currently authenticated user.

Examples

{:ok, user_id} = TTlockClient.get_user_id()

Returns

  • {:ok, user_id} - Current user's ID
  • {:error, :not_authenticated} - No user currently authenticated

get_valid_token()

@spec get_valid_token() :: {:ok, String.t()} | {:error, term()}

Gets a valid access token for API requests.

This function will automatically refresh the token if it's expired or near expiry. The returned token can be used immediately for TTLock API requests.

Examples

case TTlockClient.get_valid_token() do
  {:ok, token} ->
    # Use token in your API requests
    headers = [{"Authorization", "Bearer " <> token}]

  {:error, :not_authenticated} ->
    # Need to authenticate first
    TTlockClient.authenticate("username", "password")

  {:error, reason} ->
    # Handle other errors
    Logger.error("Token error: " <> inspect(reason))
end

Returns

  • {:ok, access_token} - Valid token ready for use
  • {:error, :not_authenticated} - Need to authenticate first
  • {:error, reason} - Token refresh or other error

ready?()

@spec ready?() :: boolean()

Helper function to check if the client is ready for API calls.

Examples

if TTlockClient.ready?() do
  {:ok, token} = TTlockClient.get_valid_token()
  # Make API calls
else
  # Need to configure/authenticate
end

Returns

  • true - Client is authenticated and ready
  • false - Client needs configuration or authentication

refresh_token()

@spec refresh_token() :: :ok | {:error, term()}

Manually refreshes the current access token.

Normally you don't need to call this as tokens are refreshed automatically, but this can be useful for testing or if you need to force a refresh.

Examples

TTlockClient.refresh_token()

Returns

  • :ok - Token refreshed successfully
  • {:error, reason} - Refresh failed

reset()

@spec reset() :: :ok

Resets all authentication state.

This clears stored tokens and credentials. You'll need to configure and authenticate again after calling this.

Examples

TTlockClient.reset()
TTlockClient.configure("new_client_id", "new_client_secret")
TTlockClient.authenticate("username", "password")

Returns

  • :ok - State cleared successfully

search_passcodes(lock_id, search_term)

@spec search_passcodes(integer(), String.t()) :: {:ok, map()} | {:error, term()}

Searches passcodes by name or passcode value.

Parameters

  • lock_id - The lock ID to search in
  • search_term - Search term (name or exact passcode match)

Examples

{:ok, results} = TTlockClient.search_passcodes(12345, "Guest")
{:ok, results} = TTlockClient.search_passcodes(12345, "123456")

Returns

  • {:ok, response} - Contains matching passcodes
  • {:error, reason} - Request failed

start(client_id, client_secret, username, password, base_url \\ "https://euapi.ttlock.com")

@spec start(String.t(), String.t(), String.t(), String.t(), String.t()) ::
  :ok | {:error, term()}

Convenience function to configure and authenticate in one call.

Parameters

  • client_id - Application client ID
  • client_secret - Application client secret
  • username - TTLock app username
  • password - TTLock app password
  • base_url - Optional API base URL

Examples

TTlockClient.start("client_id", "client_secret", "username", "password")

Returns

  • :ok - Configuration and authentication successful
  • {:error, reason} - Setup failed

start_with_env()

@spec start_with_env() :: :ok | {:error, term()}

Convenience function to start with environment variables.

Reads TTLOCK_CLIENT_ID, TTLOCK_CLIENT_SECRET, TTLOCK_USERNAME, and TTLOCK_PASSWORD from environment variables and configures/authenticates.

Examples

# Ensure your .env file has the required variables
TTlockClient.start_with_env()

Returns

  • :ok - Configuration and authentication successful
  • {:error, :missing_env_vars} - Required environment variables not set
  • {:error, reason} - Setup failed

status()

@spec status() :: :not_configured | :configured | :authenticated

Gets the current authentication status.

Examples

case TTlockClient.status() do
  :not_configured ->
    # Need to call configure/2
  :configured ->
    # Configured but not authenticated
  :authenticated ->
    # Ready to make API calls
end

Returns

  • :not_configured - Client credentials not set
  • :configured - Client configured but not authenticated
  • :authenticated - Ready for API requests