225 lines
7.8 KiB
PHP
225 lines
7.8 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
|
|
{
|
|
$now = now();
|
|
|
|
$this->info('------------------------------------------');
|
|
$this->info('同步时间: '. $now);
|
|
|
|
/** @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;
|
|
}
|
|
|
|
$this->info('==========================================');
|
|
$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) {
|
|
// 下载图片
|
|
$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,
|
|
]);
|
|
}
|
|
|
|
$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->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'] ?? '');
|
|
}
|
|
}
|