Metadata-Version: 2.4
Name: controlid-sdk
Version: 0.2.1
Summary: A production-ready asynchronous Python SDK for ControlID access control devices, supporting biometrics, networking, and complex time schedules.
Author-email: Tulio Amancio <root@tsuriu.com.br>
Project-URL: Homepage, https://github.com/tulioamancio/controlid-python-sdk
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Requires-Dist: pydantic>=2.0.0

# ControlID Python SDK (Async)

A production-ready, high-performance asynchronous Python SDK for ControlID access control devices (iDAccess, iDFace, iDFlex, iDBlock, etc.). 

Built on top of `httpx` and `pydantic` v2, this SDK provides robust abstractions over the ControlID API, gracefully handling undocumented firmware quirks, session management, and complex data schemas.

## Features

- ⚡️ **Fully Asynchronous**: Built exclusively on `async/await` and `httpx` for high-concurrency non-blocking I/O.
- 🔑 **Automatic Session Management**: Handles logins and injects secure, URL-encoded tokens into requests.
- 🛡️ **Pydantic Validation**: Robust, type-hinted data models (`User`, `Card`, `AccessRule`, etc.).
- 🛠️ **Quirk Resolution**: Automatically manages device-specific API quirks (e.g., nested `where` clauses, strict `modify_objects` routing, implicit schema limitations).
- 📸 **Facial Recognition**: Push direct binary payload image uploads for iDFace enrollment.
- 📱 **QR Code Support**: Generate and register CRC-32 QR Code credentials safely.
- 🚪 **Advanced Hardware Control**: Interact with relays, GPIO pins, SecBoxes, turnstiles (catras), and ballot boxes.
- 📋 **Dynamic Schemas**: Native support for creating and interacting with custom device fields (`c_users` table).

## Installation

```bash
pip install controlid-sdk
```

## Quick Start

```python
import asyncio
from controlid import ControlIDClient, User

async def main():
    # Use as an async context manager for automatic session cleanup.
    # We use verify=False typically because devices use self-signed SSL certificates.
    async with ControlIDClient(
        host="https://192.168.0.100", 
        user="admin", 
        password="password", 
        verify=False
    ) as client:
        
        # 1. Fetch Users
        users = await client.get_users()
        print(f"Found {len(users)} users on device.")

        # 2. Add a new user
        user_id = await client.add_user(User(name="Jane Doe", registration="EMP-001"))
        print(f"Created user with ID: {user_id}")

        # 3. Open a Door (using internal relay)
        await client.open_door(door_id=1)
        
        # Or using an external SecBox (standard on iDFace/iDFlex installations)
        # await client.open_sec_box(box_id=65793)

if __name__ == "__main__":
    asyncio.run(main())
```

## Supported Workflows

This SDK provides extensive high-level wrappers. Below are just a few examples. 

*(For complete runnable scripts, check the `examples/` directory).*

### 📸 Facial Recognition & Photo Upload
Unlike standard JSON requests, facial photo enrollment requires raw binary streaming. 
The iDFace firmware is famously strict: if you upload a high-resolution, dense portrait (like a 2MB iPhone photo) or images where the face isn't perfectly centered, the device will silently reject the neural-network validation with a `400 Bad Request`.

Our SDK examples handle this elegantly by using `Pillow` to instantly downscale huge images to `< 40KB` and `< 600x600px` before uploading:

```python
from examples import helper

# Automatically loops through photos, shrinks them flawlessly, and registers the face!
await helper.enhance_user(client, user_id=10, user_ref="EMP")
```

Alternatively, you can skip local uploads entirely and trigger the device's physical screen so the person can enroll themselves live at the gate!

```python
# The iDFace terminal will open its camera, display a 5-second countdown,
# analyze liveness, and automatically save the biometric hash!
await client.remote_enroll_face(user_id=10, auto=True, countdown=5)
```

### 📱 QR Code Credentials
The device expects CRC-32 integers when scanning standard QR codes. The SDK can help you register them correctly.

```python
import binascii

def qr_hash(text: str) -> int:
    return binascii.crc32(text.encode()) & 0xFFFFFFFF

# Give a user a QR code credential that matches the text "VISITOR-99"
await client.add_qr_code(user_id=10, qr_value=qr_hash("VISITOR-99"))
```
*Note: Our `examples/helper.py` will automatically generate and save physical `.png` files of the QR Codes so you can test them instantly on your screen or phone.*

### 🏢 Departments, Groups, and Access Rules
Organize your users logically and restrict their access using Rules and Groups.

```python
# 1. Create a Department 
group_id = await client.create_group("Engineering")

# 2. Create an Access Rule (e.g., 1 = Always Allowed, Priority = 0)
rule_id = await client.create_access_rule("24/7 Access", rule_type=1)

# 3. Link the Group to the Rule
await client.assign_group_access_rule(group_id, rule_id)

# 4. Add the user to the Group
await client.add_user_to_group(user_id=10, group_id=group_id)
```

### 🧩 Custom Fields (`c_users`)
Extend the device's native database schema dynamically to store your own business logic (e.g., CPF, Department Code).

```python
from controlid import CustomField

# 1. Create the schema column (run once per device)
await client.add_custom_field(CustomField(
    column_name="cpf",
    name="CPF Number",
    type="TEXT"
))

# 2. Write data for a specific user
await client.set_custom_user_data(user_id=10, cpf="123.456.789-00")

# 3. Read it back
custom_data = await client.get_custom_user_data(user_id=10)
print(custom_data) # {'cpf': '123.456.789-00', 'user_id': 10}
```

### ⚙️ System Monitoring
Get full diagnostic data from the device including uptime, memory usage, network details, and firmware version.

```python
# Fetch extensive system telemetry
info = await client.get_system_information()

print(f"Uptime:  {info['uptime']['days']} days")
print(f"Memory:  {info['memory']['ram']['free']} / {info['memory']['ram']['total']} bytes")
print(f"MAC:     {info['network']['mac']}")
print(f"Serial:  {info['serial']}")
print(f"Version: {info['version']}")
```

### 🕒 Time Zones & Access Schedules
Define exactly when certain users or groups are allowed to enter.

```python
from controlid import TimeSpan

# 1. Create a fuso horário
tz_id = await client.create_time_zone("Standard Business")

# 2. Add specific spans (intervals)
# Example: Mon-Fri, 08:00 (28800s) to 18:00 (64800s)
await client.add_time_spans([
    TimeSpan(time_zone_id=tz_id, start=28800, end=64800, mon=1, tue=1, wed=1, thu=1, fri=1)
])
```

### 🧩 Generic Object API

## Directory Structure
- `src/controlid/client.py`: Core asynchronous logic and endpoints.
- `src/controlid/models.py`: Pydantic V2 definitions.
- `src/controlid/constants.py`: Enums and endpoint tables.
- `examples/*.py`: Fully self-contained scripts demonstrating every major use-case. Including a comprehensive `run_tests.py` that validates the entire API sequentially against live hardware.

## License
MIT
