# TTlockClient
An Elixir client library for the TTLock Open Platform API with centralized OAuth 2.0 authentication management.
## Features
- **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
## Installation
Add `ex_ttlock` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_ttlock, "~> 0.1.0"}
]
end
```
## Quick Start
### 1. Configuration
Configure your TTLock application credentials:
```elixir
# 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
```
### 2. Environment Setup (Recommended)
Create a `.env` file in your project root:
```bash
# .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`!
### 3. Authentication
```elixir
# 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
```
### 3. Making API Calls
Get valid tokens for your API requests:
```elixir
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
```
## Advanced Usage
### All-in-One Setup
```elixir
# 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
```
### Status Checking
```elixir
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
```
### Manual Token Management
```elixir
# 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()
```
## Configuration Options
### Using .env Files (Recommended for Development)
The library automatically loads `.env` files in development and test environments:
```bash
# .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:
```elixir
# Reads all environment variables and sets up authentication
TTlockClient.start_with_env()
```
### Application Configuration
```elixir
# 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
```
### Environment Variables (Production)
```bash
export TTLOCK_CLIENT_ID="your_client_id"
export TTLOCK_CLIENT_SECRET="your_client_secret"
export TTLOCK_USERNAME="your_username"
export TTLOCK_PASSWORD="your_password"
```
## API Reference
### Authentication
- `configure/2,3` - Set client credentials
- `authenticate/2` - Authenticate with username/password
- `get_valid_token/0` - Get current valid access token
- `get_user_id/0` - Get authenticated user ID
- `refresh_token/0` - Manually refresh token
- `status/0` - Get authentication status
- `ready?/0` - Check if ready for API calls
- `reset/0` - Clear all authentication state
- `start/4,5` - Configure and authenticate in one call
- `start_with_env/0` - Configure and authenticate using environment variables
### Lock Management
- `get_locks/0,1,2,3,4` - Get paginated list of locks
- `get_lock/1` - Get detailed information about a specific lock
- `get_all_locks/0,1,2` - Get all locks (handles pagination automatically)
### Passcode Management
- `add_permanent_passcode/2,3` - Add a permanent passcode via gateway
- `add_temporary_passcode/4,5` - Add a time-limited passcode via gateway
- `add_passcode/2,3,4,5,6,7,8` - Add passcode with full parameter control
- `change_passcode/2,3,4,5,6,7` - Change passcode name, value, or validity period
- `change_passcode_name/3` - Change only the passcode name
- `change_passcode_value/3` - Change only the passcode value
- `change_passcode_period/4` - Change only the passcode validity period
- `delete_passcode/2` - Delete a passcode via gateway
- `delete_passcode_via_gateway/2` - Delete a passcode via gateway (alias)
- `get_passcodes/1,2,3,4,5` - Get paginated list of passcodes for a lock
- `get_lock_passcodes/1,2` - Get all passcodes for a lock (convenience function)
- `search_passcodes/2` - Search passcodes by name or passcode value
### Low-Level API
- `TTlockClient.Locks.get_lock_list/1` - Direct lock list API call
- `TTlockClient.Locks.get_lock_detail/1` - Direct lock detail API call
- `TTlockClient.Passcodes.add_passcode/1` - Direct passcode add API call
- `TTlockClient.Passcodes.change_passcode/1` - Direct passcode change API call
- `TTlockClient.Passcodes.delete_passcode/1` - Direct passcode delete API call
- `TTlockClient.Passcodes.get_passcode_list/1` - Direct passcode list API call
- `TTlockClient.Types.*` - Type definitions and helper functions
### Authentication States
- `:not_configured` - No client credentials set
- `:configured` - Client configured but not authenticated
- `:authenticated` - Fully authenticated and ready
## Passcode Management Examples
### Adding Passcodes
```elixir
# 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)
```
### Changing Passcodes
```elixir
# 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).
### Deleting Passcodes
```elixir
# 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.
### Listing and Searching Passcodes
```elixir
# 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)
```
## Architecture
The library uses a centralized authentication pattern with a GenServer that manages all OAuth state:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your App │───▶│ TTlockClient │───▶│ TTlockClient │
│ │ │ .API │ │ .AuthManager │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌───────▼────────┐
│ TTlockClient │
│ .OAuthClient │
└────────────────┘
```
### Key Benefits
1. **No Module Dependencies**: Each module gets tokens without knowing about OAuth
2. **Automatic Refresh**: Tokens refreshed 5 minutes before expiry
3. **Thread Safety**: Safe concurrent access from multiple processes
4. **Fault Tolerance**: Proper OTP supervision and error recovery
5. **Centralized Logic**: All authentication logic in one place
## Error Handling
The library provides detailed error information:
```elixir
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
```
### Common Error Codes
- `10001` - Invalid credentials
- `10004` - Token expired (handled automatically)
- `10005` - Invalid client credentials
## Testing
```bash
# 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
```
## Examples
The library includes several example scripts:
```bash
# 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
```
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Follow the style guides:
- [Elixir Style Guide](https://github.com/christopheradams/elixir_style_guide)
- [Erlang Guidelines](https://github.com/inaka/erlang_guidelines)
4. Add tests for your changes
5. Ensure all tests pass (`mix test`)
6. Run code analysis (`mix credo`)
7. Commit your changes (`git commit -m 'Add amazing feature'`)
8. Push to the branch (`git push origin feature/amazing-feature`)
9. Open a Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## TTLock API Documentation
For complete TTLock API documentation, visit:
- [TTLock Open Platform](https://open.ttlock.com/)
- [API Documentation](https://open.ttlock.com/doc/api)
## Support
- Create an issue for bug reports or feature requests
- Check existing issues before creating new ones
- Provide clear reproduction steps for bugs