star-cloud/app/Jobs/Machine/ProcessHeartbeat.php
sky121113 40aec884b0 [REFACTOR] 溫度回報邏輯優化與 MQTT 規格標準化
1. 優化 ProcessHeartbeat 溫度處理邏輯,改為「變動即記錄」並標準化為整數型態。
2. 在 Machine Model 中新增 temperature 欄位的 integer cast。
3. 更新 MQTT 與 API 技術規格技能文件 (SKILL.md),同步最新的溫度記錄規則。
4. 更新 config/api-docs.php,明確標註心跳上報之溫度規格與日誌規則。
5. 移除 routes/api.php 中已廢棄之 B600 與機台日誌 REST API 路由。
6. 清理 docs 目錄,將初期計畫文件遷移至 docs/plan/。
2026-04-22 17:02:24 +08:00

120 lines
4.2 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);
$newState = $oldState ?? [];
// 1. 韌體版本比對 (有改才傳,有改才記)
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);
}
}