143 lines
5.2 KiB
PHP
143 lines
5.2 KiB
PHP
<?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);
|
|
}
|
|
}
|