# ZKTeco architecture and remote restart findings

Reviewed on 2026-07-28. Repositories in this workspace:

- `Elevator/`: the Laravel control plane and UI.
- `ZKT-access-flask/`: our Python/Flask HTTP bridge.
- `pyzkaccess/`: third-party Python library used by the bridge to call the ZKTeco PULL SDK.

## System map

```text
Laravel UI / authenticated route
        |
        | controller user operations for C3/ACP models
        v
ZKT-access-flask HTTP API
        |
        v
queue_manager.py -> device_locks.py -> main.py
        |
        v
pyzkaccess -> vendor PULL SDK -> ZKTeco controller TCP port 4370
```

Laravel also has a second, direct path for `F22` and `F28` devices:

```text
Laravel TagService -> maliklibs/zkteco PHP package -> controller TCP 4370
```

The model split is implemented in `Elevator/app/Helpers/Helper.php` and used by
`TagService`: `C3-100`, `C3-200`, `C3-400`, and `ACP-200` use the Flask path;
`F22` and `F28` use the PHP package path.

## Normal communication

For supported C3/ACP controller user operations:

1. A Laravel service reads the block's `devices` relation. Each endpoint has an
   IP, port, and model (`Block::getDeviceEndpointsAttribute`).
2. `TagService` POSTs JSON to the host configured by `ZKTECO_IP`, for example
   `/controller/user/set/`, `/controller/user/remove/`, or `/controller/users/`.
3. Flask forwards the request to `queue_manager`, which serializes operations
   by `ip:port` and calls `main.py`.
4. `main.py` builds a PULL SDK connection string, maps the model to `ZK100`,
   `ZK200`, or `ZK400`, and opens TCP/4370 through `pyzkaccess`.
5. The result is returned as JSON and Laravel updates its tag state where
   appropriate.

The Python service has tests for route forwarding, retries, model resolution,
and per-device locking. The Laravel application also has a Redis device lock
around tag operations.

## Remote restart: implemented state

Remote restart is now wired end-to-end. The hardware/library capability exists
and the dashboard action reaches the selected controller endpoint.

### Capability present in the third-party library

`pyzkaccess/pyzkaccess/main.py` exposes:

```python
def restart(self) -> None:
    self.sdk.control_device(ControlOperation.restart.value, 0, 0, 0, 0)
```

Its test in `pyzkaccess/tests/test_main.py` verifies that the SDK restart
operation is called. The Laravel PHP dependency also exposes `ZKTeco::restart()`
and `ZKTeco::shutdown()` through `vendor/maliklibs/zkteco`.

### Python bridge wiring

`ZKT-access-flask/app.py` exposes `POST /controller/restart/`. It requires
`Authorization: Bearer <ZKTECO_SHARED_SECRET>`, validates the controller IP, and
calls `queue_manager.restart_device()`. The operation is protected by the
existing per-device lock before `main.py` calls `ZKAccess.restart()`.

### Laravel controller and UI wiring

`Elevator/routes/web.php` declares these authenticated POST routes:

- `block.enableDevice` -> `BlocksController::enable`
- `block.disableDevice` -> `BlocksController::disable`
- `block.restartDevice` -> `BlocksController::restart`
- `block.shutDown` -> `BlocksController::shutdown`

`BlocksController::restart()` now calls `DeviceControlService::restartBlock()`.
The service locks each `ip:port`, uses the Flask bridge for C3/ACP models, and
uses the direct PHP `maliklibs/zkteco` driver for F22/F28 models. The block page
now has a confirmation-protected `გადატვირთვა` button and reports full,
partial, or failed results.

### Important integration/security note

The restart endpoint enforces the Bearer token named `ZKTECO_SHARED_SECRET`.
Laravel reads the same value from the `ZKTECO_SHARED_SECRET` environment
variable and sends it only for restart.
Existing user-management bridge routes retain their previous authentication
behavior and should be secured in a separate compatibility change.

## Recommended implementation shape

Operational notes for the “კავშირის აღდგენა / გადატვირთვა” action:

1. Configure the same non-empty `ZKTECO_SHARED_SECRET` in Laravel and Flask.
2. Use the dashboard restart button only for the intended block; it restarts
   every configured endpoint for that block.
3. Expect the controller TCP connection to drop briefly. The success response
   means the command was accepted by the driver, not that the controller has
   already finished booting.
4. If a block has multiple controllers, inspect the partial-success message and
   restart failed endpoints individually after checking their network path.

## Useful entry points for future assistants

- Laravel routes: `Elevator/routes/web.php`
- Block endpoint mapping: `Elevator/app/Models/Block.php`
- Model/protocol split: `Elevator/app/Helpers/Helper.php` and
  `Elevator/app/Services/TagService.php`
- Laravel-to-Flask requests: `Elevator/app/Services/TagService.php`
- Flask routes: `ZKT-access-flask/app.py`
- Python orchestration: `ZKT-access-flask/queue_manager.py` and `main.py`
- Per-device locking: `ZKT-access-flask/device_locks.py`
- Third-party restart implementation: `pyzkaccess/pyzkaccess/main.py`
- PHP direct device commands: `Elevator/vendor/maliklibs/zkteco/src/Lib/Helper/Device.php`
