lcly-data-admin/app/Console/Commands/BiAng/DeviceLogSyncCommand.php

225 lines
7.9 KiB
PHP

<?php
namespace App\Console\Commands\BiAng;
use App\Enums\DeviceStatus;
use App\Enums\DeviceType;
use App\Iot\BiAng\HttpClient;
use App\Models\Device;
use App\Models\DeviceLog;
use App\Models\WormPhoto;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use Throwable;
class DeviceLogSyncCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'biang:device-log-sync
{--sleep=60 : 设备数据同步完成后的休眠时间(秒)}';
/**
* The console command description.
*
* @var string
*/
protected $description = '按设备厂商同步数据';
/**
* Execute the console command.
*/
public function handle()
{
$sleep = (int) value(fn ($sleep) => is_numeric($sleep) ? $sleep : 60, $this->option('sleep'));
while (true) {
try {
$this->sync();
} catch (Throwable $e) {
report($e);
}
sleep($sleep);
};
}
/**
* 执行同步
*/
protected function sync(): void
{
/** @var \Illuminate\Database\Eloquent\Collection */
$devices = Device::with(['project'])
->supplierBy("device-supplier-biang")
->whereIn('status', [DeviceStatus::Online, DeviceStatus::Offline])
->get();
/** @var \App\Models\Device */
foreach ($devices as $device) {
if (! in_array($device->type, [
DeviceType::Monitor,
DeviceType::Soil,
DeviceType::WaterQuality,
DeviceType::Meteorological,
DeviceType::Worm,
DeviceType::InsectSexLure,
DeviceType::InsecticidalLamp,
])) {
continue;
}
$now = now();
$this->info('==========================================');
$this->info('同步时间: '. $now);
$this->info('设备编号: ' . $device->sn);
$this->info('设备名称: ' . $device->name);
$this->info('设备类型: ' . match ($device->type) {
DeviceType::Monitor => '苗情设备',
DeviceType::Soil => '土壤设备',
DeviceType::WaterQuality => '水质设备',
DeviceType::Meteorological => '气象设备',
DeviceType::Worm => '虫情设备',
DeviceType::InsectSexLure => '昆虫性诱设备',
DeviceType::InsecticidalLamp => '杀虫灯设备',
});
try {
$httpClient = $this->buildHttpClient($device);
switch ($device->type) {
case DeviceType::Monitor:
$data = $httpClient->getMonitorPalyAddress($device->sn);
$device->update([
'status' => $data ? DeviceStatus::Online : DeviceStatus::Offline,
]);
break;
case DeviceType::Soil:
case DeviceType::WaterQuality:
case DeviceType::Meteorological:
case DeviceType::InsecticidalLamp:
$data = match ($device->type) {
DeviceType::Soil => $httpClient->getLatestSoilReport($device->sn),
DeviceType::WaterQuality => $httpClient->getLatestWaterDeviceReport($device->sn),
DeviceType::Meteorological => $httpClient->getLatestMeteorologicalReport($device->sn),
DeviceType::InsecticidalLamp => $httpClient->getLatestLampReport($device->sn),
};
if (empty($data)) {
$device->update([
'status' => DeviceStatus::Offline,
]);
$this->warn('设备数据: 无');
break;
} else {
$this->info('设备数据: '.json_encode($data));
}
$log = DeviceLog::firstOrCreate([
'device_id' => $device->id,
'reported_at' => $data['time'],
], [
'data' => Arr::except($data, ['deviceId', 'time']),
]);
$device->update([
'status' => $now->copy()->subMinutes(60)->lt($log->reported_at) ? DeviceStatus::Online : DeviceStatus::Offline,
]);
break;
case DeviceType::Worm:
case DeviceType::InsectSexLure:
$dir = "worm-photos/{$device->id}";
$disk = Storage::disk('public');
$data = $httpClient->getWormPhotos($device->sn, $now->copy()->subHours(24), $now);
if (empty($data)) {
$device->update([
'status' => DeviceStatus::Offline,
]);
$this->warn('设备数据: 无');
break;
} else {
$this->info('设备数据: '.json_encode($data));
}
foreach ($data['imgUrl'] as $item) {
try {
// 下载图片
$name = md5($item['url']);
if ($ext = pathinfo($item['url'], PATHINFO_EXTENSION)) {
$name .= ".{$ext}";
}
$path = "{$dir}/{$name}";
$disk = Storage::disk('public');
if (! $disk->exists($path)) {
$disk->put($path, file_get_contents($item['url']));
}
WormPhoto::updateOrCreate([
'device_id' => $device->id,
'uploaded_at' => $item['time'],
], [
'url' => $path,
]);
} catch (Throwable $e) {
report($e);
}
}
$device->update([
'status' => count($data['imgUrl'] ?? []) > 0 ? DeviceStatus::Online : DeviceStatus::Offline,
]);
break;
}
switch ($device->status) {
case DeviceStatus::Online:
$this->info('设备状态: 在线');
break;
case DeviceStatus::Offline:
$this->warn('设备状态: 离线');
break;
}
$this->info('同步成功!');
} catch (Throwable $e) {
report($e);
$this->error('同步失败: '. $e->getMessage());
}
$this->info('==========================================');
$this->newLine();
}
}
/**
* 创建 HTTP 客户端
*/
public function buildHttpClient(Device $device): HttpClient
{
$config = json_decode($device->project?->value, true);
if (! is_array($config)) {
throw new RuntimeException('账户信息未找到');
}
return new HttpClient($config['username'] ?? '', $config['password'] ?? '');
}
}