star-cloud/app/Jobs/Machine/ProcessHeartbeat.php
sky121113 4bf53db3be [FIX] 優化機台連線規範與上線日誌邏輯
1. 更新 SKILL.md 與 Java 範例,將 MQTT Client ID 格式由隨機後綴改為固定格式 SC_{serial_no},以確保單機連線唯一性。
2. 同步更新 config/api-docs.php,在 API 文件中加入 MQTT 連線參數規範說明。
3. 修正 ProcessHeartbeat.php 邏輯,當機台狀態從 offline 轉為 online 時,自動寫入「Machine is online」日誌,完善連線紀錄回溯。
2026-05-07 10:16:29 +08:00

131 lines
4.5 KiB
PHP

<?php
namespace App\Jobs\Machine;
use App\Models\Machine\Machine;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class ProcessHeartbeat implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $serialNo;
protected $payload;
/**
* Create a new job instance.
*/
public function __construct(string $serialNo, $payload)
{
$this->serialNo = $serialNo;
$this->payload = (array) $payload;
}
/**
* Execute the job.
*/
public function handle(): void
{
$machine = Machine::withoutGlobalScopes()->where('serial_no', $this->serialNo)->first();
if (!$machine) {
Log::warning("MQTT Heartbeat: Machine not found", ['serial_no' => $this->serialNo]);
return;
}
// 更新機台狀態
Log::debug("ProcessHeartbeat: Attempting to set status to online for {$this->serialNo}");
$updateData = [
'last_heartbeat_at' => now(),
'status' => 'online',
];
// === 狀態異動觸發 (Redis 快取,讓 MQTT 也支援日誌) ===
$cacheKey = "machine:{$this->serialNo}:state";
$oldState = \Illuminate\Support\Facades\Cache::get($cacheKey);
// 1. 偵測狀態由離線轉在線 (寫入日誌)
if ($machine->status === 'offline') {
\App\Jobs\Machine\ProcessStateLog::dispatch(
$machine->id,
$machine->company_id,
"Machine is online",
'info',
[]
);
}
$newState = $oldState ?? [];
// 2. 韌體版本比對 (有改才傳,有改才記)
if (isset($this->payload['firmware_version'])) {
$fv = (string) $this->payload['firmware_version'];
if (!isset($oldState['firmware_version']) || (string)$oldState['firmware_version'] !== $fv) {
\App\Jobs\Machine\ProcessStateLog::dispatch(
$machine->id,
$machine->company_id,
"Firmware updated to :version",
'info',
['version' => $fv, 'old' => $oldState['firmware_version'] ?? 'Unknown']
);
}
$updateData['firmware_version'] = $fv;
$newState['firmware_version'] = $fv;
}
// 2. 溫度更新 (只要有變動就紀錄日誌)
if (isset($this->payload['temperature'])) {
$temp = (int) $this->payload['temperature'];
Log::debug("ProcessHeartbeat: Temperature reported for {$this->serialNo}: {$temp}");
$updateData['temperature'] = $temp;
// 讀取最後一次記錄日誌的溫度與時間
$tempLogCacheKey = "machine:{$this->serialNo}:last_temp_log";
$lastLogEntry = \Illuminate\Support\Facades\Cache::get($tempLogCacheKey);
$shouldLog = false;
if (!$lastLogEntry) {
$shouldLog = true;
} else {
$lastValue = (int) $lastLogEntry['value'];
$lastAt = \Carbon\Carbon::parse($lastLogEntry['at']);
// 條件 A: 只要溫度與上次記錄不同就記錄 (整數比對)
if ($temp !== $lastValue) {
Log::debug("ProcessHeartbeat: Temperature changed from {$lastValue} to {$temp}. Triggering log.");
$shouldLog = true;
}
// 條件 B: 距離上次日誌超過 4 小時 (保底機制)
if ($lastAt->diffInHours(now()) >= 4) {
$shouldLog = true;
}
}
if ($shouldLog) {
\App\Jobs\Machine\ProcessStateLog::dispatch(
$machine->id,
$machine->company_id,
"Temperature reported: :temp°C",
'info',
['temp' => $temp]
);
// 更新快取,保留 7 天
\Illuminate\Support\Facades\Cache::put($tempLogCacheKey, [
'value' => $temp,
'at' => now()->toDateTimeString()
], 604800);
}
}
// 更新快取
\Illuminate\Support\Facades\Cache::put($cacheKey, $newState, 86400);
$machine->update($updateData);
}
}