IMEI ingest + commands: migrations, models, DeviceApiController, routes

This commit is contained in:
2025-09-02 17:18:18 +10:00
parent bde8fbab81
commit 8232e90aa4

View File

@ -0,0 +1,142 @@
<?php
namespace App\Http\Controllers;
use App\Models\Device;
use App\Models\Command;
use App\Models\CommandReceipt;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class DeviceApiController extends Controller
{
// POST /api/device/{imei}/telemetry
public function telemetry(Request $req, string $imei)
{
$data = $req->validate([
'recorded_at' => ['nullable','date'],
'lat' => ['required','numeric','between:-90,90'],
'lng' => ['required','numeric','between:-180,180'],
'altitude_m' => ['nullable','numeric'],
'speed_kmh' => ['nullable','numeric'],
'heading_deg' => ['nullable','numeric'],
'accuracy_m' => ['nullable','numeric'],
'battery_percent'=> ['nullable','integer','between:0,100'],
'is_car_on' => ['nullable','boolean'],
'raw' => ['nullable'],
]);
$device = Device::firstOrCreate(
['imei' => $imei],
['name' => $imei, 'is_active' => true]
);
$now = now();
$recordedAt = isset($data['recorded_at'])
? Carbon::parse($data['recorded_at'])
: $now;
DB::table('device_telemetry')->insert([
'device_id' => $device->id,
'recorded_at' => $recordedAt,
'lat' => $data['lat'],
'lng' => $data['lng'],
'altitude_m' => $data['altitude_m'] ?? null,
'speed_kmh' => $data['speed_kmh'] ?? null,
'heading_deg' => $data['heading_deg'] ?? null,
'accuracy_m' => $data['accuracy_m'] ?? null,
'raw' => array_key_exists('raw', $data) ? json_encode($data['raw']) : null,
'created_at' => $now,
]);
// Upsert status snapshot
DB::table('device_status')->updateOrInsert(
['device_id' => $device->id],
[
'is_car_on' => (bool)($data['is_car_on'] ?? false),
'battery_percent' => $data['battery_percent'] ?? null,
'external_power' => false,
'reported_at' => $now,
'last_gps_fix_at' => $recordedAt,
'updated_at' => $now,
'sleep_interval_sec'=> DB::raw('COALESCE(sleep_interval_sec, 60)'),
]
);
$device->forceFill(['last_seen_at' => $now, 'last_ip' => $req->ip()])->save();
return response()->json(['ok' => true], 201);
}
// GET /api/device/{imei}/commands?since=ISO8601
public function commands(Request $req, string $imei)
{
$since = $req->query('since');
$device = Device::where('imei', $imei)->first();
if (!$device) {
return response()->json(['commands' => []]);
}
$q = Command::where('device_id', $device->id)
->where('status', 'queued')
->where(function ($q) {
$q->whereNull('not_before_at')->orWhere('not_before_at', '<=', now());
})
->where(function ($q) {
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
})
->orderBy('priority', 'asc')
->orderBy('id', 'asc');
if ($since) {
$ts = Carbon::parse($since);
$q->where('created_at', '>=', $ts);
}
$cmds = $q->limit(50)->get(['id','command_type','payload','priority','created_at']);
// Mark as sent (optional)
if ($cmds->count()) {
Command::whereIn('id', $cmds->pluck('id'))->update(['status' => 'sent', 'updated_at' => now()]);
}
return response()->json(['commands' => $cmds]);
}
// POST /api/device/{imei}/command-receipts
public function commandReceipts(Request $req, string $imei)
{
$device = Device::where('imei', $imei)->first();
if (!$device) {
return response()->json(['ok' => true], 200);
}
$data = $req->validate([
'command_id' => ['required','integer'],
'acked_at' => ['nullable','date'],
'executed_at' => ['nullable','date'],
'result' => ['nullable','in:ok,error'],
'result_detail'=> ['nullable','string'],
]);
CommandReceipt::create([
'command_id' => $data['command_id'],
'device_id' => $device->id,
'acked_at' => isset($data['acked_at']) ? Carbon::parse($data['acked_at']) : null,
'executed_at' => isset($data['executed_at']) ? Carbon::parse($data['executed_at']) : null,
'result' => $data['result'] ?? null,
'result_detail'=> $data['result_detail'] ?? null,
]);
// If execution OK, mark command acked/acked+executed
if (!empty($data['result'])) {
$newStatus = $data['result'] === 'ok' ? 'acked' : 'failed';
Command::where('id', $data['command_id'])->where('device_id', $device->id)
->update(['status' => $newStatus, 'updated_at' => now()]);
}
return response()->json(['ok' => true], 200);
}
}