Laravel-GPS-API/app/Http/Controllers/DeviceApiController.php

169 lines
5.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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()]);
}
$payload = $cmds->map(function ($c) {
return [
'id' => $c->id,
'type' => $c->command_type, // rename field to "type"
'payload' => $c->payload ?? new \stdClass(),
'priority' => $c->priority,
'created_at' => $c->created_at,
];
});
// Mark as sent (optional your current behavior)
if ($cmds->count()) {
\App\Models\Command::whereIn('id', $cmds->pluck('id'))
->update(['status' => 'sent', 'updated_at' => now()]);
}
$bodyArray = $payload->values()->all(); // a plain array
$body = json_encode($bodyArray, JSON_UNESCAPED_SLASHES);
$len = strlen($body);
return response($body, 200)
->header('Content-Type', 'application/json')
->header('Content-Length', (string)$len)
->header('Connection', 'keep-alive');
// 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);
}
}