# API Reference: common

All classes are importable from `brinkhaustools.common`:

```python
from brinkhaustools.common import (
    App, ShutdownHandler, LoggingHelper, Settings,
    SelfDiagnosisEngine, DiagnosisMessage,
    StatusEngine, StatusSource,
    HeartbeatEngine, VersionInformation,
)
```

---

## App

Central wiring object. Connects all components.

```python
App(
    name="my-service",
    settings_path="data/settings/settings_main.json",  # or None
    log_dir="data/logs/",                               # or None
    version_file="helper/version.json",                 # or None
    fleet_config_path="fleetManagementData/config.json",# or None
    heartbeat_timeout_sec=300,
    install_signal_handlers=True,
)
```

| Method           | Description                                         |
|------------------|-----------------------------------------------------|
| `start()`        | Start background workers (status, heartbeat, fleet)  |
| `stop()`         | Trigger shutdown and stop all workers                |
| `wait()`         | Block until shutdown is triggered                    |
| `get_instance()` | Return the most recently created App (static)        |

---

## ShutdownHandler

Coordinates graceful shutdown across threads.

| Method                  | Description                                          |
|-------------------------|------------------------------------------------------|
| `trigger(delay_sec=0)`  | Initiate shutdown, optionally after a delay          |
| `is_shutdown()`         | Returns `True` once shutdown has been triggered      |
| `wait(timeout=None)`    | Block until shutdown                                 |
| `sleep(seconds)`        | Interruptible sleep; returns `True` if interrupted   |
| `register_hook(cb)`     | Register a cleanup callback (runs once on shutdown)  |
| `event`                 | The underlying `threading.Event`                     |

---

## Settings

Thread-safe JSON settings with hierarchical key access.

```python
settings = Settings(file_path="config.json", auto_save=True)
```

| Method                                  | Description                                               |
|-----------------------------------------|-----------------------------------------------------------|
| `get(key, default=None)`                | Get value by key path; auto-creates if missing            |
| `set(key, value)`                       | Set value by key path                                     |
| `exist(key)`                            | Check if key path exists                                  |
| `length(key)`                           | Return length of list/dict at key, or -1                  |
| `delete_key(key)`                       | Delete a key; returns `True` on success                   |
| `move_key(old, new)`                    | Move value from one key path to another                   |
| `save()` / `reload()`                   | Persist to disk / reload from disk                        |
| `to_json()` / `load_json(s)`            | Serialize / deserialize full settings                     |
| `get_value_from_env(env, key, default)` | Read from env var, falling back to settings               |

Key paths accept lists (`["section", "key", 0]`) or dot-notation (`"section.key.0"`).

Type compatibility is enforced: if the stored type differs from the default's
type (and they are not both numeric), the default wins.

---

## SelfDiagnosisEngine

Tracks application health via numeric error codes.

| Method                                                    | Description                                    |
|-----------------------------------------------------------|------------------------------------------------|
| `notify(code, message, critical, information, timeout_sec)` | Report a diagnostic (deduplicated by code)  |
| `clear(code)`                                             | Resolve a diagnostic                           |
| `get_messages()`                                          | Snapshot of active messages (expired pruned)    |
| `get_status()`                                            | JSON-serialisable status dict                  |

### DiagnosisMessage

| Attribute       | Type   | Description                            |
|-----------------|--------|----------------------------------------|
| `code`          | `int`  | Numeric error code                     |
| `message`       | `str`  | Human-readable description             |
| `critical`      | `bool` | Whether this affects system status     |
| `information`   | `bool` | Informational-only flag                |
| `created_at_ms` | `int`  | Unix timestamp in milliseconds         |
| `timeout_at`    | `float`| Monotonic time when message expires    |

---

## StatusEngine / StatusSource

Periodic status collection from registered sources.

```python
class StatusSource(ABC):
    def get_status(self) -> dict: ...
    def get_name(self) -> str: ...
```

| StatusEngine Method       | Description                              |
|---------------------------|------------------------------------------|
| `register_source(source)` | Add a status source                      |
| `collect()`               | Poll all sources                         |
| `get_last_status()`       | Last collected status (thread-safe)      |
| `start(shutdown_handler)` | Start background collection thread       |
| `stop()`                  | Stop collection                          |

---

## HeartbeatEngine

Watchdog for detecting hung threads.

| Method                         | Description                               |
|--------------------------------|-------------------------------------------|
| `register(id, timeout_sec=0)` | Register or refresh a heartbeat           |
| `cancel(id)`                  | Stop watching a heartbeat                 |
| `heartbeats_ok()`             | `True` if all heartbeats are within timeout |
| `start(shutdown_handler)`     | Start background watchdog                 |
| `stop()`                      | Stop watchdog                             |

---

## VersionInformation

Loads CI-generated `version.json`.

| Property / Method | Description                               |
|-------------------|-------------------------------------------|
| `version`         | Version string (e.g. `"1.2.3"`)          |
| `data`            | Full version dict                         |
| `get_status()`    | StatusSource-compatible dict              |

---

## Fleet Management

`FleetMonitorClient` and `StatusMonitor` bridge diagnosis data to the
BrinkhausFleetManager via HTTPS.

### Configuration

Fleet management reads its settings from a JSON config file (default:
`fleetManagementData/config.json`):

```json
{
    "fleetmanagement": {
        "customer_name": "my-customer",
        "machine": "machine-01",
        "broker": "fleet.brinkhaus-gmbh.de",
        "heartbeat_interval_sec": 60,
        "token": "fmt_..."
    }
}
```

| Key                      | Type   | Default                      | Description                                           |
|--------------------------|--------|------------------------------|-------------------------------------------------------|
| `base_url`               | `str`  | —                            | Full API URL (e.g. `https://fleet.brinkhaus-gmbh.de`) |
| `broker`                 | `str`  | `fleet.brinkhaus-gmbh.de`    | Hostname; used as `https://{broker}` if `base_url` is not set |
| `customer_name`          | `str`  | `testCustomer`               | Customer slug in the fleet hierarchy                  |
| `machine`                | `str`  | `testMachine`                | Machine identifier                                    |
| `heartbeat_interval_sec` | `int`  | `60`                         | Seconds between automatic heartbeats                  |
| `token`                  | `str`  | —                            | API token (`fmt_...`), created in the FleetManager UI |

!!! note "Token format"
    Tokens are generated in the BrinkhausFleetManager UI under
    *Customers → Manage Tokens*. They start with `fmt_` and are sent as
    `Authorization: Bearer {token}` on every request.

### Environment variable overrides

| Variable                     | Overrides         |
|------------------------------|-------------------|
| `STATUS_MONITOR_CUSTOMERID`  | `customer_name`   |
| `STATUS_MONITOR_MACHINEID`   | `machine`         |
| `FLEET_HOST_IPS`             | IP addresses reported in heartbeats (comma-separated) |

### Usage via App

Normally used through `App` — no manual instantiation needed:

```python
from brinkhaustools.common import App

app = App(
    name="my-service",
    fleet_config_path="fleetManagementData/config.json",
)
app.start()   # starts heartbeat thread + diagnosis bridge
app.wait()
```

Pass `fleet_config_path=None` to disable fleet reporting entirely.

### Standalone usage

`FleetMonitorClient` can also be used directly without the full `App`:

```python
from brinkhaustools.common.fleet import FleetMonitorClient

client = FleetMonitorClient(
    base_url="https://fleet.brinkhaus-gmbh.de",
    customer="my-customer",
    machine="machine-01",
    software="my-app",
    token="fmt_...",
    heartbeat_interval=60,
    version="1.0.0",
)
client.start()  # starts heartbeat thread, sends initial "running" status

# Send diagnostics manually
client.send_diagnostics([
    (1000, "Database OK", 0),        # severity 0 = Info
    (2001, "Memory at 85%", 1),      # severity 1 = Warning
])

# Send status change
client.send_status("degraded", "High memory usage")

# On shutdown
client.stop()  # sends "stopped" status, joins heartbeat thread
```

### HTTPS ingest endpoints

| Endpoint | Payload |
|----------|---------|
| `POST /api/ingest/{customer}/{machine}/{software}/heartbeat` | `{ts, version, uptime_seconds, pid, ip_addresses}` |
| `POST /api/ingest/{customer}/{machine}/{software}/diagnostics` | `{ts, items: [{code, message, severity}]}` |
| `POST /api/ingest/{customer}/{machine}/{software}/status` | `{ts, status, message}` |

Severity: 0 = Info, 1 = Warning, 2 = Error.

Status values: `starting`, `running`, `degraded`, `error`, `stopping`, `stopped`.
