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

193 lines
6.9 KiB
PHP
Raw Permalink 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);
}
public function storeCommand(Request $request, $imei)
{
// 1. Validate the incoming data from the T3 dashboard
$validated = $request->validate([
'type' => 'required|string|in:wifi,sleep,telemetry_sec,poll_sec,ota,ring_fence,lights,camera,reboot',
'payload' => 'present|nullable|array',
]);
// 2. Find the device by its IMEI, or create it if it's the first time we've seen it
$device = Device::firstOrCreate(['imei' => $imei]);
// 3. Create the command and associate it with the device
$command = $device->commands()->create([
'type' => $validated['type'],
'payload' => $validated['payload'],
'status' => 'queued', // Set a default status
]);
// 4. Return a success response with the new command's ID
return response()->json([
'ok' => true,
'command_id' => $command->id,
], 201); // HTTP 201 Created
}
}