An Elixir client library for the TTLock Open Platform API with centralized OAuth 2.0 authentication management.
- Centralized Authentication: Single GenServer manages all OAuth token lifecycle
- Automatic Token Refresh: Proactive token refresh before expiry
- Thread-Safe: Safe concurrent access to authentication state
- OTP Compliant: Proper supervision and fault tolerance
- Zero Module Dependencies: No authentication logic scattered across modules
Add ex_ttlock
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_ttlock, "~> 0.1.0"}
]
end
Configure your TTLock application credentials:
# Option A: Direct configuration
TTlockClient.configure("your_client_id", "your_client_secret")
# Option B: Use environment variables (automatically loads from .env in dev/test)
TTlockClient.configure(
System.get_env("TTLOCK_CLIENT_ID"),
System.get_env("TTLOCK_CLIENT_SECRET")
)
# Option C: One-liner with .env file
TTlockClient.start_with_env() # Reads all vars from environment
Create a .env
file in your project root:
# .env
TTLOCK_CLIENT_ID=your_actual_client_id
TTLOCK_CLIENT_SECRET=your_actual_client_secret
TTLOCK_USERNAME=your_ttlock_username
TTLOCK_PASSWORD=your_ttlock_password
Important: Add .env
to your .gitignore
!
# With environment variables
TTlockClient.authenticate(
System.get_env("TTLOCK_USERNAME"),
System.get_env("TTLOCK_PASSWORD")
)
# Or use the all-in-one helper
TTlockClient.start_with_env() # Configure + authenticate in one call
Get valid tokens for your API requests:
case TTlockClient.get_valid_token() do
{:ok, token} ->
# Token is automatically refreshed if needed
headers = [{"Authorization", "Bearer #{token}"}]
# Make your TTLock API calls
{:error, :not_authenticated} ->
# Need to authenticate first
TTlockClient.authenticate("username", "password")
{:error, reason} ->
# Handle authentication errors
Logger.error("Auth error: #{inspect(reason)}")
end
# Option 1: With .env file (recommended)
case TTlockClient.start_with_env() do
:ok ->
{:ok, token} = TTlockClient.get_valid_token()
# Ready to make API calls
{:error, reason} ->
# Handle setup error
end
# Option 2: Direct configuration
case TTlockClient.start("client_id", "client_secret", "username", "password") do
:ok ->
{:ok, token} = TTlockClient.get_valid_token()
# Ready to make API calls
{:error, reason} ->
# Handle setup error
end
case TTlockClient.status() do
:not_configured ->
# Need to call TTlockClient.configure/2
:configured ->
# Configured but need to authenticate
:authenticated ->
# Ready for API calls
end
# Or use the convenience function
if TTlockClient.ready?() do
# Make API calls
end
# Force token refresh (usually not needed)
TTlockClient.refresh_token()
# Get current user ID
{:ok, user_id} = TTlockClient.get_user_id()
# Reset all authentication state
TTlockClient.reset()
The library automatically loads .env
files in development and test environments:
# .env (in your project root)
TTLOCK_CLIENT_ID=your_client_id
TTLOCK_CLIENT_SECRET=your_client_secret
TTLOCK_USERNAME=your_username
TTLOCK_PASSWORD=your_password
Then use the simple setup:
# Reads all environment variables and sets up authentication
TTlockClient.start_with_env()
# config/config.exs
config :ex_ttlock,
client_id: System.get_env("TTLOCK_CLIENT_ID"),
client_secret: System.get_env("TTLOCK_CLIENT_SECRET"),
base_url: "https://euapi.ttlock.com" # optional
export TTLOCK_CLIENT_ID="your_client_id"
export TTLOCK_CLIENT_SECRET="your_client_secret"
export TTLOCK_USERNAME="your_username"
export TTLOCK_PASSWORD="your_password"
configure/2,3
- Set client credentialsauthenticate/2
- Authenticate with username/passwordget_valid_token/0
- Get current valid access tokenget_user_id/0
- Get authenticated user IDrefresh_token/0
- Manually refresh tokenstatus/0
- Get authentication statusready?/0
- Check if ready for API callsreset/0
- Clear all authentication statestart/4,5
- Configure and authenticate in one callstart_with_env/0
- Configure and authenticate using environment variables
get_locks/0,1,2,3,4
- Get paginated list of locksget_lock/1
- Get detailed information about a specific lockget_all_locks/0,1,2
- Get all locks (handles pagination automatically)
add_permanent_passcode/2,3
- Add a permanent passcode via gatewayadd_temporary_passcode/4,5
- Add a time-limited passcode via gatewayadd_passcode/2,3,4,5,6,7,8
- Add passcode with full parameter controlchange_passcode/2,3,4,5,6,7
- Change passcode name, value, or validity periodchange_passcode_name/3
- Change only the passcode namechange_passcode_value/3
- Change only the passcode valuechange_passcode_period/4
- Change only the passcode validity perioddelete_passcode/2
- Delete a passcode via gatewaydelete_passcode_via_gateway/2
- Delete a passcode via gateway (alias)get_passcodes/1,2,3,4,5
- Get paginated list of passcodes for a lockget_lock_passcodes/1,2
- Get all passcodes for a lock (convenience function)search_passcodes/2
- Search passcodes by name or passcode value
TTlockClient.Locks.get_lock_list/1
- Direct lock list API callTTlockClient.Locks.get_lock_detail/1
- Direct lock detail API callTTlockClient.Passcodes.add_passcode/1
- Direct passcode add API callTTlockClient.Passcodes.change_passcode/1
- Direct passcode change API callTTlockClient.Passcodes.delete_passcode/1
- Direct passcode delete API callTTlockClient.Passcodes.get_passcode_list/1
- Direct passcode list API callTTlockClient.Types.*
- Type definitions and helper functions
:not_configured
- No client credentials set:configured
- Client configured but not authenticated:authenticated
- Fully authenticated and ready
# Add a permanent passcode
{:ok, %{keyboardPwdId: passcode_id}} =
TTlockClient.add_permanent_passcode(12345, 123456, "Guest Access")
# Add a temporary passcode (valid for 1 week)
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")
# Add with full control over parameters
{:ok, result} =
TTlockClient.add_passcode(12345, 555999, "Custom", 3, start_ms, end_ms, 2)
# Change passcode name only
{:ok, %{errcode: 0, errmsg: "success"}} =
TTlockClient.change_passcode_name(12345, 67890, "Updated Guest Access")
# Change passcode value only
{:ok, result} =
TTlockClient.change_passcode_value(12345, 67890, 999888)
# Change validity period only
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)
# Change multiple properties at once
{:ok, result} =
TTlockClient.change_passcode(12345, 67890, "New Name", 888999, start_time, end_time)
Note: Passcode changes work via the cloud API for WiFi locks or locks connected to a gateway. At least one change parameter must be provided (name, passcode value, or validity period).
# Delete a passcode (works for WiFi locks or locks connected to a gateway)
{:ok, %{errcode: 0, errmsg: "success"}} =
TTlockClient.delete_passcode(12345, 67890)
# Alternative method (same functionality)
{:ok, result} = TTlockClient.delete_passcode_via_gateway(12345, 67890)
Note: Passcode deletion works via the cloud API for WiFi locks or locks connected to a gateway. The passcode will be removed from both the cloud and the physical lock.
# Get all passcodes for a lock
{:ok, %{list: passcodes, total: count}} = TTlockClient.get_lock_passcodes(12345)
# Search for specific passcodes
{:ok, results} = TTlockClient.search_passcodes(12345, "Guest")
# Get paginated results with custom parameters
{:ok, response} = TTlockClient.get_passcodes(12345, nil, 1, 50, 1)
The library uses a centralized authentication pattern with a GenServer that manages all OAuth state:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your App │───▶│ TTlockClient │───▶│ TTlockClient │
│ │ │ .API │ │ .AuthManager │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌───────▼────────┐
│ TTlockClient │
│ .OAuthClient │
└────────────────┘
- No Module Dependencies: Each module gets tokens without knowing about OAuth
- Automatic Refresh: Tokens refreshed 5 minutes before expiry
- Thread Safety: Safe concurrent access from multiple processes
- Fault Tolerance: Proper OTP supervision and error recovery
- Centralized Logic: All authentication logic in one place
The library provides detailed error information:
case TTlockClient.authenticate("username", "password") do
:ok ->
# Success
{:error, %{error_code: 10001, description: "Invalid credentials"}} ->
# TTLock API error
{:error, :not_configured} ->
# Need to configure client first
{:error, {:transport_error, reason}} ->
# Network error
{:error, reason} ->
# Other errors
end
10001
- Invalid credentials10004
- Token expired (handled automatically)10005
- Invalid client credentials
# Run tests
mix test
# Run with coverage
mix test.coverage
# Run specific test file
mix test test/ttlock_client_test.exs
# Watch mode for development
mix test.watch
The library includes several example scripts:
# Basic authentication example
elixir example.exs
# Simple setup with .env
elixir example.exs simple
# Real-time token monitoring
elixir example.exs monitor
# Lock management examples
elixir locks_example.exs
# Advanced lock operations
elixir locks_example.exs detail
# Passcode management examples
elixir passcodes_example.exs
# Advanced passcode operations
elixir passcodes_example.exs advanced
# Passcode deletion examples
elixir passcodes_example.exs delete
# Passcode change examples
elixir passcodes_example.exs change
# Passcode time helper examples
elixir passcodes_example.exs helpers
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Follow the style guides:
- Add tests for your changes
- Ensure all tests pass (
mix test
) - Run code analysis (
mix credo
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
For complete TTLock API documentation, visit:
- Create an issue for bug reports or feature requests
- Check existing issues before creating new ones
- Provide clear reproduction steps for bugs