PHPackages                             track-any-device/core - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. track-any-device/core

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

track-any-device/core
=====================

Core package for the Track Any Device platform.

v0.2.0(2w ago)0171↓100%[10 issues](https://github.com/track-any-device/package-core/issues)4MITPHPPHP ^8.3CI passing

Since May 23Pushed 6d agoCompare

[ Source](https://github.com/track-any-device/package-core)[ Packagist](https://packagist.org/packages/track-any-device/core)[ RSS](/packages/track-any-device-core/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (10)Dependencies (5)Versions (7)Used By (4)

track-any-device/core
=====================

[](#track-any-devicecore)

Core package for the Track Any Device (TAD) platform. Provides models, migrations, seeders, middleware, jobs, services, and workflow infrastructure shared across all TAD applications.

---

Requirements
------------

[](#requirements)

DependencyVersionPHP^8.3Laravel^13.7laravel/fortify^1.0laravel/sanctum^4.0stancl/tenancy^3.10track-any-device/sms-gateway`*@dev`---

Installation
------------

[](#installation)

Add the package to your host application's `composer.json`:

```
{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/track-any-device/package-core"
        }
    ],
    "require": {
        "track-any-device/core": "dev-main"
    }
}
```

Then install:

```
composer install
```

`CoreServiceProvider` is auto-discovered via the `extra.laravel.providers` entry in `composer.json`. The remaining service providers must be registered manually in `bootstrap/providers.php` (see [Service Providers](#service-providers)).

---

Configuration
-------------

[](#configuration)

### Environment variables

[](#environment-variables)

KeyDefaultDescription`APP_SURFACE``core`Controls which surfaces load migrations. Set to `core` (or leave unset) to load all package migrations. Set to any other value to suppress them (e.g. for worker-only containers).`APP_LOGIN_DOMAIN`—Hostname of the dedicated identity server (e.g. `login.example.com`). Used by `AuthorizeTenantAccess` to build the OAuth authorize URL. Falls back to `APP_DOMAIN` when unset.`APP_DOMAIN``localhost`Central / root domain of the application.`APP_INTERNAL_SECRET`—Shared secret for internal service-to-service calls. Validated by `ValidateInternalSecret` middleware via the `X-Internal-Secret` header.### InfluxDB (telemetry)

[](#influxdb-telemetry)

Signals (device telemetry) are stored in InfluxDB when enabled. Add a `config/influxdb.php` file to your host application:

```
return [
    'enabled' => env('INFLUXDB_ENABLED', false),
    'url'     => env('INFLUXDB_URL', 'http://localhost:8086'),
    'token'   => env('INFLUXDB_TOKEN', ''),
    'bucket'  => env('INFLUXDB_BUCKET', 'signals'),
    'org'     => env('INFLUXDB_ORG', 'tad'),
];
```

KeyDefaultDescription`INFLUXDB_ENABLED``false`Set to `true` to enable time-series signal writes and queries. When `false`, `SignalService` no-ops silently.`INFLUXDB_URL``http://localhost:8086`InfluxDB instance URL`INFLUXDB_TOKEN`—Authentication token`INFLUXDB_BUCKET``signals`Bucket name for signal data`INFLUXDB_ORG``tad`InfluxDB organisation### Fleet thresholds

[](#fleet-thresholds)

Add a `config/fleet.php` file to configure alarm thresholds:

```
return [
    'overspeed_threshold'   => env('OVERSPEED_THRESHOLD_KMH', 80),
    'low_battery_threshold' => env('LOW_BATTERY_THRESHOLD', 20),
];
```

### Workflows

[](#workflows)

Add a `config/workflows.php` file to customise webhook retry backoff (useful in tests):

```
return [
    // Seconds to sleep between webhook retry attempts [attempt-1, attempt-2, ...]
    'webhook_backoff' => [2, 8],
];
```

---

Service Providers
-----------------

[](#service-providers)

`CoreServiceProvider` is auto-discovered. Register the rest manually in `bootstrap/providers.php`:

```
return [
    // Auto-discovered — listed here for clarity
    TrackAnyDevice\Core\CoreServiceProvider::class,

    // Must be registered manually
    TrackAnyDevice\Core\Providers\TenancyServiceProvider::class,
    TrackAnyDevice\Core\Providers\DeviceServiceProvider::class,
    TrackAnyDevice\Core\Providers\FortifyServiceProvider::class,
];
```

### CoreServiceProvider

[](#coreserviceprovider)

- Loads package migrations when `config('app.surface')` is `core` or unset.
- Registers Artisan commands.
- Registers the package factory name resolver.

### TenancyServiceProvider

[](#tenancyserviceprovider)

Configures `stancl/tenancy` events, boots the tenancy middleware priority stack, and maps `routes/tenant.php` from the host application (if it exists) under the `web` middleware group.

### DeviceServiceProvider

[](#deviceserviceprovider)

Registers device driver bindings keyed by `DeviceType` slug:

SlugDriver`p901``P901Driver``gf-07``GF07Driver``jt808``Jt808Driver``aot120``AOT120Driver``tad101`Registered by `track-any-device/tad101` packageResolve a driver in application code:

```
use TrackAnyDevice\Core\Providers\DeviceServiceProvider;

$driver = DeviceServiceProvider::driverFor($device->deviceType->slug);
```

### FortifyServiceProvider

[](#fortifyserviceprovider)

Configures Fortify views (Inertia), actions, and rate limiting. Requires the host application to provide:

- `App\Actions\Fortify\CreateNewUser`
- `App\Actions\Fortify\ResetUserPassword`

---

Migrations &amp; Seeders
------------------------

[](#migrations--seeders)

### Running migrations

[](#running-migrations)

```
php artisan migrate
```

Migrations are loaded automatically by `CoreServiceProvider` when `APP_SURFACE` is `core` (or unset).

### Available seeders

[](#available-seeders)

Run the full seed sequence:

```
php artisan db:seed --class="TrackAnyDevice\Core\Database\Seeders\DatabaseSeeder"
```

Individual seeders:

SeederDescription`AdminSeeder`Creates the default admin user`TenantSeeder`Seeds example tenant(s)`DeviceTypeSeeder`Seeds supported device type records`DriverSeeder`Seeds driver records`SensorSeeder`Seeds sensor definitions`GsmNetworkSeeder`Seeds GSM network records`CountrySeeder`Seeds country / dialling-code data`AlertRuleSeeder`Seeds default global alert rules`AssigneeTypeSeeder`Seeds default assignee type options`IncidentTaxonomySeeder`Seeds incident priority and status options`NavLinkSeeder`Seeds navigation link defaults`PolicyVersionSeeder`Seeds the current policy version`IndustrySeeder`Seeds industry classification records`HomePageSeeder`Seeds default home page CMS content`PublicPageSeeder`Seeds public-facing CMS pages`BlogSeeder`Seeds sample blog content`P901CatalogueSeeder`Seeds P901 product catalogue data`WorkflowSeeder`Seeds example workflows`SuthraPunjabTenantSeeder`Seeds the Suthra Punjab demo tenant---

Architecture
------------

[](#architecture)

### Tenancy model

[](#tenancy-model)

TAD uses `stancl/tenancy` in a **central-database** configuration — all data lives in one MySQL database. There are no per-tenant databases.

Tenant isolation is enforced at the query layer:

- Models that belong to one tenant (e.g. `Beat`, `Assignee`, `Incident`) use the `BelongsToTenant` trait, which attaches `TenantScope` as a global scope.
- Inside a tenant request (domain resolved by tenancy middleware), `TenantScope` automatically adds `WHERE tenant_id = ?` to every query on those models.
- Outside a tenant context (Filament admin, CLI), the scope is a no-op so admin queries see all rows.

```
use TrackAnyDevice\Core\Concerns\BelongsToTenant;
use TrackAnyDevice\Core\Concerns\UsesCentralConnection;

class MyModel extends Model
{
    use BelongsToTenant, UsesCentralConnection;
}
```

`UsesCentralConnection` pins the model to the connection defined in `tenancy.database.central_connection` (defaults to `mysql`). It exists to make a future transition to per-tenant databases non-breaking.

### Roles

[](#roles)

Role`isCentralStaff()`Description`admin`YesFull platform access`supervisor`YesCentral staff with supervisory rights`staff`YesCentral staff`tenant_user`NoMember of one or more tenants via `tenant_users` pivot`user`NoEnd-user / device owner---

Models
------

[](#models)

ModelConnectionNotes`User`CentralAuthenticatable; supports Fortify 2FA + Sanctum tokens`Tenant`CentralExtends stancl's BaseTenant; uses bigint PK`Device`CentralSoft-deletes; tracks status, onboarding, last signal`DeviceType`CentralDefines supported hardware variants`Driver`CentralMaps a device type to a driver class`Sensor`CentralSensor definitions attached to device types or devices`Beat`CentralGeo-fence zone with optional parent hierarchy`BeatAssignment`CentralLinks a device to a beat with time boundaries`Assignee`CentralPerson or asset that can be assigned a device`DeviceAssignment`CentralAssignment record with condition tracking`Incident`CentralAlert / alarm event tied to a device and optional beat`AlertRule`CentralGlobal or tenant-level rules; `tenant_id = null` means global`Workflow`CentralAutomation graph triggered by events or time`WorkflowRun`CentralExecution record for a single workflow run`Signal`InfluxDBTime-series telemetry; not an Eloquent model---

Services
--------

[](#services)

### `SignalService`

[](#signalservice)

Persists device telemetry to InfluxDB and updates the device's snapshot columns in MySQL.

```
use TrackAnyDevice\Core\Services\SignalService;

$service = app(SignalService::class);

// Record a signal
$signal = $service->record($signalObject, $device);

// Query history
$signals = $service->queryHistory(
    deviceId: $device->id,
    from: now()->subHours(6),
    to: now(),
    limit: 500,
    eventType: 'location',   // optional
);

// Latest signals (last 30 days)
$signals = $service->latestForDevice($device->id, limit: 100);
```

When `INFLUXDB_ENABLED=false`, all read methods return empty collections and write operations are silently skipped. `SignalCreatedEvent` is still dispatched so observers remain active.

### `AssignmentService`

[](#assignmentservice)

Manages the lifecycle of device-to-assignee assignments.

```
use TrackAnyDevice\Core\Services\AssignmentService;

$service = app(AssignmentService::class);

// Assign
$assignment = $service->assign($device, $assignee, $assignedBy);

// Transfer to another assignee
$assignment = $service->transfer(
    device: $device,
    newAssignee: $newAssignee,
    transferredBy: $user,
    forceIfCriticalIncidents: false, // throws if unresolved critical incidents
);

// Return
$assignment = $service->returnDevice($assignment, $returnedBy, conditionIn: 'good');
```

### `BeatAssignmentService`

[](#beatassignmentservice)

Manages assignment of devices to beats (geo-fence zones).

### `GeoFence`

[](#geofence)

Point-in-polygon and point-in-circle checks for beat violation detection.

```
use TrackAnyDevice\Core\Services\GeoFence;

$geo = app(GeoFence::class);

$inside = $geo->isInsideBeat($beat, $latitude, $longitude);

// Convert a legacy circle beat to polygon vertices
$polygon = $geo->circleToPolygon($lat, $lng, $radiusMetres, points: 64);

// Validate a child beat fits within its parent
$fits = $geo->childFitsWithinParent($parentBeat, $childCoordinates);
```

### `IncidentService`

[](#incidentservice)

Creates and manages incident records.

### `OfflineDeviceRecoveryService`

[](#offlinedevicerecoveryservice)

Detects devices that have gone silent and dispatches SMS recovery actions. Used by the `devices:detect-offline` command. Uses exponential backoff (5 → 10 → 20 → 40 → 80 → 160 → 320 → 360 min cap) with a hard limit of 8 attempts.

### `DeviceCommandService`

[](#devicecommandservice)

Queues outbound commands to devices.

---

Middleware
----------

[](#middleware)

Register these in your host application's `bootstrap/app.php` or `Http/Kernel.php` as needed.

MiddlewareDescription`AuthorizeTenantAccess`Gate tenant-domain requests; bounces unauthenticated visitors through OAuth SSO. Exempt paths: `/register`, `/sso/callback`.`CheckTenantApproved`Rejects requests to tenants that are not in `approved` status.`EnsureTenantDomain`Ensures the request is coming from a tenant subdomain (not central).`EnsureCentralDomain`Ensures the request is coming from the central domain.`EnsureLoginDomain`Ensures the request is coming from the login/identity domain.`EnsureMyDomain`Ensures the request host matches `APP_DOMAIN`.`EnsureTenantApiScope`Validates that a Sanctum token has a required ability: `->middleware('tenant.scope:devices.read')`.`EnsurePhoneVerified`Rejects access when the user's phone number is not verified.`RequireSmsChallenge`Requires a one-time SMS OTP challenge after login (session-based).`InitializeTenancyForRequest`Thin wrapper around stancl's tenancy initialiser.`GateTenantRegistration`Blocks tenant self-registration when `registration_enabled = false`.`BeatScopedAccess`Restricts resource access to the beats a user is assigned to.`CaptureAuthLocation`Records the user's browser geolocation on successful authentication.`ValidateInternalSecret`Validates the `X-Internal-Secret` header for service-to-service routes.`HandleAppearance`Applies the tenant's color scheme / theme to the response.`HandleInertiaRequests`Shares Inertia props (auth user, tenant, flash) on every request.### `ValidateInternalSecret` usage

[](#validateinternalsecret-usage)

Protect internal-only routes:

```
// routes/api.php
Route::middleware('App\Http\Middleware\ValidateInternalSecret')
    ->prefix('internal')
    ->group(function () {
        Route::post('/signal', SignalIngestController::class);
    });
```

The calling service must send:

```
X-Internal-Secret:

```

---

Artisan Commands
----------------

[](#artisan-commands)

CommandScheduleDescription`devices:detect-offline`Every 5 minDetect silent in-service devices and dispatch SMS recovery`workflows:run-scheduled`Every minuteDispatch time-triggered workflows that are due`sms:poll-inbox`Every minutePoll the SMS gateway inbox and store unprocessed messages`otp:prune`DailyDelete expired OTP codes from the database`beats:normalize-to-polygon`One-time migrationConvert legacy circle beats to polygon vertex arrays### Recommended schedule

[](#recommended-schedule)

Add to `routes/console.php` in the host application:

```
use Illuminate\Support\Facades\Schedule;

Schedule::command('devices:detect-offline')->everyFiveMinutes();
Schedule::command('workflows:run-scheduled')->everyMinute();
Schedule::command('sms:poll-inbox')->everyMinute();
Schedule::command('otp:prune')->daily();
```

### One-time migration

[](#one-time-migration)

After deployment, run the beat normalisation command to migrate any legacy circle-format beats to polygons:

```
# Preview (no writes)
php artisan beats:normalize-to-polygon --dry-run

# Apply
php artisan beats:normalize-to-polygon
```

---

Workflows
---------

[](#workflows-1)

Workflows are automation graphs stored as JSON in the `workflows` table. Each workflow has a trigger type, a graph of nodes and edges, and optional tenant scoping.

### Trigger types

[](#trigger-types)

TypeDescription`incident_opened`Fires when a new incident is created`incident_escalated`Fires when an incident is escalated`time`Fires on a cron schedule (driven by `workflows:run-scheduled`)### Action types

[](#action-types)

TypeHandlerDescription`notify``NotifyUsersAction`Send notifications to users or assignees`send_command``SendDeviceCommandAction`Queue a command to the incident's device`escalate_incident``EscalateIncidentAction`Change the incident status to `escalated``webhook``CallWebhookAction`POST the run context to a tenant-supplied URL (3 retries, 10s timeout)`wait``WaitDelayAction`Pause execution for a specified number of seconds (max 180s)### Dispatching a workflow from code

[](#dispatching-a-workflow-from-code)

```
use TrackAnyDevice\Core\Workflows\WorkflowDispatcher;
use TrackAnyDevice\Core\Enums\WorkflowTriggerType;

$dispatcher = app(WorkflowDispatcher::class);
$count = $dispatcher->dispatchForIncident($incident, WorkflowTriggerType::IncidentOpened);
```

### Time-triggered workflow config

[](#time-triggered-workflow-config)

Store in the `trigger_config` JSON column:

```
{
    "cron": "*/15 * * * *",
    "timezone": "Asia/Karachi"
}
```

`timezone` is optional and defaults to `config('app.timezone')`.

---

Jobs
----

[](#jobs)

JobQueueDescription`OnboardDeviceJob`defaultRuns the driver's onboarding action for a device (idempotent)`ProcessAlarmEvents`defaultCreates/resolves incidents from active alarm flags`CheckBeatViolation`defaultLevel-aware beat geo-fence violation checker`QueueDeviceCommand`defaultSends a queued command through the device's driver`RunWorkflowJob`defaultExecutes a workflow graph for a given trigger context---

Events
------

[](#events)

EventPayloadDescription`SignalCreatedEvent``$deviceId`, `SignalObject`Fired after every signal is recorded`DeviceOnboardedEvent``Device`Fired after `OnboardDeviceJob` completes onboarding`DeviceUpdatedEvent``Device`Fired when device snapshot columns change`DeviceLogEvent``Device`, log dataFired when a device log entry is created---

Enums
-----

[](#enums)

All database-persisted enums are backed PHP enums (string-backed). Key enums:

EnumValues`Role``admin`, `supervisor`, `staff`, `tenant_user`, `user``DeviceStatus``warehouse`, `inventory`, `available`, `assigned`, `in_service``OnboardingStatus``pending`, `sim_added`, `configured`, `verified``IncidentStatus``open`, `acknowledged`, `escalated`, `resolved`, `closed``IncidentPriority``low`, `medium`, `high`, `critical``AlertRuleEventType``sos`, `overspeed`, `low_battery`, `power_failure`, `vibration`, `beat_violation``WorkflowTriggerType``incident_opened`, `incident_escalated`, `time``WorkflowRunStatus``running`, `completed`, `failed`---

Factories
---------

[](#factories)

Factories are included for testing. The package registers a factory name guesser in `CoreServiceProvider` so `Model::factory()` resolves to `TrackAnyDevice\Core\Database\Factories\Factory`.

Available factories: `User`, `Tenant`, `Domain`, `Device`, `DeviceType`, `DeviceCommand`, `DeviceOrder`, `DeviceAssignment`, `Assignee`, `AssigneeType`, `Beat`, `BeatAssignment`, `BeatTemplate`, `AlertRule`, `Incident`, `SsoToken`, `Workflow`.

---

License
-------

[](#license)

MIT

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance98

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

5

Last Release

15d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/83275f3144d4cc4092e54dd8d5acf6a048536f6d7e454d741ae82a61f904c737?d=identicon)[ahmadkokab](/maintainers/ahmadkokab)

---

Top Contributors

[![afaryab](https://avatars.githubusercontent.com/u/68786248?v=4)](https://github.com/afaryab "afaryab (18 commits)")[![claude](https://avatars.githubusercontent.com/u/81847?v=4)](https://github.com/claude "claude (1 commits)")

### Embed Badge

![Health badge](/badges/track-any-device-core/health.svg)

```
[![Health](https://phpackages.com/badges/track-any-device-core/health.svg)](https://phpackages.com/packages/track-any-device-core)
```

###  Alternatives

[unopim/unopim

UnoPim Laravel PIM

10.1k2.2k](/packages/unopim-unopim)[markwalet/nova-modal-response

A Laravel Nova asset for Modal responses on an action.

17818.7k](/packages/markwalet-nova-modal-response)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
