Merge branch 'main' of https://gitea.hmily.club/pdkj/lcly-data-admin
commit
7137b11950
|
|
@ -7,10 +7,11 @@ use App\Enums\DeviceType;
|
|||
use App\Iot\BiAng\HttpClient;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceLog;
|
||||
use App\Models\InsecticidalLampReport;
|
||||
use App\Models\WormPhoto;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
|
|
@ -135,16 +136,33 @@ class DeviceLogSyncCommand extends Command
|
|||
|
||||
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);
|
||||
|
||||
// foreach ($data['imgUrl'] as $item) {
|
||||
// WormPhoto::firstOrCreate([
|
||||
// 'device_id' => $device->id,
|
||||
// 'uploaded_at' => $item['time'],
|
||||
// ], [
|
||||
// 'url' => $item['url'],
|
||||
// ]);
|
||||
// }
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ class WormStatisticsSyncCommand extends Command
|
|||
foreach ($devices as $device) {
|
||||
$this->info('==================================');
|
||||
|
||||
$latestReportedAt = WormReport::Where('device_id', $device->id)->latest('reported_at')->value('reported_at');
|
||||
$latestReportedAt = WormReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at');
|
||||
|
||||
$start = $latestReportedAt ? $latestReportedAt->copy() : $today->copy()->subDays(179);
|
||||
$start = $latestReportedAt ? $latestReportedAt->copy() : $today->copy();
|
||||
|
||||
$days = $start->diffInDays($today, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands\Linkos;
|
||||
|
||||
use App\Enums\DeviceStatus;
|
||||
use App\Enums\DeviceType;
|
||||
use App\Iot\Linkos\FarmClient;
|
||||
use App\Models\Device;
|
||||
use App\Models\WormPhoto;
|
||||
use App\Models\WormReport;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Throwable;
|
||||
|
||||
class WormReportCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'linkos:worm-report';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'linkos 虫情设备数据同步';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('------------------------------------------');
|
||||
$this->info(now());
|
||||
try {
|
||||
$this->sync();
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
$this->info('------------------------------------------');
|
||||
}
|
||||
|
||||
protected function sync(): void
|
||||
{
|
||||
// 接口接口限制,每分钟最多访问6次,因此每次访问后需休眠10秒
|
||||
$client = new FarmClient('xunwang', 'qwer1234');
|
||||
|
||||
$now = now();
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$devices = Device::supplierBy('device-supplier-linkos')
|
||||
->where('type', DeviceType::Worm)
|
||||
->whereIn('status', [DeviceStatus::Online, DeviceStatus::Offline])
|
||||
->get();
|
||||
|
||||
if ($devices->isEmpty()) {
|
||||
$this->warn('没有找到虫情设备');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('==================================');
|
||||
$this->info('尝试更新设备状态...');
|
||||
$realTimeData = $client->realTimeData($devices->pluck('sn')->all());
|
||||
foreach ($realTimeData as $item) {
|
||||
foreach ($devices as $device) {
|
||||
if ($item['deviceAddr'] != $device->sn) {
|
||||
continue;
|
||||
}
|
||||
// 更新设备状态
|
||||
$device->update([
|
||||
'state' => $item['status'] === 'online' ? DeviceStatus::Online : DeviceStatus::Offline,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->info("设备状态更新完成");
|
||||
|
||||
$this->info('==================================');
|
||||
|
||||
$this->info('尝试同步虫情区域统计...');
|
||||
for ($i=2; $i > 0; $i--) {
|
||||
$reportedAt = $now->copy()->subDays($i);
|
||||
|
||||
$statistics = collect(
|
||||
$client->wormStatistics(
|
||||
'E05F10DAIB6F4I4977IB95FI82554A48DE7C',
|
||||
$reportedAt->copy()->startOfDay(),
|
||||
$reportedAt->copy()->endOfDay(),
|
||||
)
|
||||
)->mapWithKeys(fn ($item) => [$item['deviceAddr'] => $item['wornData']]);
|
||||
|
||||
foreach ($devices as $device) {
|
||||
$data = $statistics[$device->sn] ?? [];
|
||||
|
||||
WormReport::updateOrCreate([
|
||||
'device_id' => $device->id,
|
||||
'reported_at' => $reportedAt->toDateString(),
|
||||
], [
|
||||
'agricultural_base_id' => $device->agricultural_base_id,
|
||||
'worm_num' => collect($data)->sum('num'),
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->info("同步虫情区域统计完成");
|
||||
|
||||
$this->info('==================================');
|
||||
|
||||
// 接口请求次数
|
||||
$requests = 4;
|
||||
|
||||
// 同步最近7天的分析报表记录
|
||||
$this->info('尝试同步分析报表记录...');
|
||||
foreach ($devices->pluck('sn') as $sn) {
|
||||
$data = $client->wormAnalyseData($sn, $now->copy()->subDays(7), $now, 1, 100);
|
||||
|
||||
foreach ($data['rows'] as $item) {
|
||||
foreach ($devices as $device) {
|
||||
if ($item['deviceAddr'] != $device->sn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $item['analyseCoordUrl'] ?: $item['imagesUrl'];
|
||||
|
||||
// 下载图片
|
||||
$name = md5($url);
|
||||
if ($ext = pathinfo($url, PATHINFO_EXTENSION)) {
|
||||
$name .= ".{$ext}";
|
||||
}
|
||||
|
||||
$path = "worm-photos/{$device->id}/{$name}";
|
||||
|
||||
$disk = Storage::disk('public');
|
||||
if (! $disk->exists($path)) {
|
||||
$disk->put($path, file_get_contents($url));
|
||||
}
|
||||
|
||||
WormPhoto::updateOrCreate([
|
||||
'device_id' => $device->id,
|
||||
'uploaded_at' => $item['createTime'],
|
||||
], [
|
||||
'url' => $path,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$requests++;
|
||||
// 接口请求频率: 每分钟6次
|
||||
if ($requests == 6) {
|
||||
$requests = 0;
|
||||
sleep(61);
|
||||
}
|
||||
}
|
||||
$this->info("同步分析报表记录完成");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\DeviceType;
|
||||
use App\Models\Device;
|
||||
use App\Models\LinkosDeviceLog;
|
||||
use App\Services\LinkosDeviceLogService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class LinkosDeviceLogArchiveCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'linkos:device-log-archive';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'LinkOS 设备流水归档';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @param \App\Services\LinkosDeviceLogService $linkosDeviceLogService
|
||||
* @return int
|
||||
*/
|
||||
public function handle(LinkosDeviceLogService $linkosDeviceLogService)
|
||||
{
|
||||
$devices = Device::whereIn('type', [DeviceType::Meteorological, DeviceType::Soil, DeviceType::WaterQuality])->get();
|
||||
|
||||
// 物联平台目前只有水质监测设备和气象监测设备
|
||||
LinkosDeviceLog::orderBy('reported_at', 'asc')->lazy()->each(function ($log) use ($devices, $linkosDeviceLogService) {
|
||||
if (empty($log->data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($devices as $device) {
|
||||
if ($device->sn !== $log->device_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match ($device->type) {
|
||||
DeviceType::Soil => $linkosDeviceLogService->handleSoilMonitoringLog($device, $log->data, $log->reported_at),
|
||||
DeviceType::Meteorological => $linkosDeviceLogService->handleMeteorologicalMonitoringLog($device, $log->data, $log->reported_at),
|
||||
DeviceType::WaterQuality => $linkosDeviceLogService->handleWaterQualityMonitoringLog($device, $log->data, $log->reported_at),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$now = now();
|
||||
$date = Carbon::parse('2022-06-01');
|
||||
|
||||
while ($date->lt($now)) {
|
||||
foreach ($devices as $device) {
|
||||
match ($device->type) {
|
||||
DeviceType::Soil => $linkosDeviceLogService->handleSoilMonitoringDailyLog($device, $date),
|
||||
DeviceType::Meteorological => $linkosDeviceLogService->handleMeteorologicalMonitoringDailyLog($device, $date),
|
||||
DeviceType::WaterQuality => $linkosDeviceLogService->handleWaterQualityMonitoringDailyLog($device, $date),
|
||||
};
|
||||
}
|
||||
|
||||
$date->addDay();
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Exceptions\BizException;
|
||||
use App\Models\LinkosDeviceLog;
|
||||
use App\Services\LinkosService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class LinkosDeviceLogSyncCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'linkos:device-log-sync {device}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'LinkOS 设备流水同步';
|
||||
|
||||
/**
|
||||
* @var \App\Services\LinkosService
|
||||
*/
|
||||
protected $linkosService;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$now = now();
|
||||
|
||||
$device = $this->argument('device');
|
||||
|
||||
// 最近同步时间
|
||||
$lastDate = $this->getLastDate($device);
|
||||
|
||||
do {
|
||||
if ($lastDate === null) {
|
||||
$lastDate = Carbon::parse('2022-06-01');
|
||||
} else {
|
||||
$lastDate->addDay();
|
||||
}
|
||||
|
||||
$start = $lastDate->copy()->startOfDay();
|
||||
|
||||
if ($start->gt($now)) {
|
||||
throw new BizException('开始时间大约当前时间');
|
||||
}
|
||||
|
||||
$end = $lastDate->copy()->endOfDay();
|
||||
|
||||
if ($end->gt($now)) {
|
||||
$end = $now;
|
||||
}
|
||||
|
||||
$this->info('----------------------------------');
|
||||
$this->info('设备编号: '.$device);
|
||||
$this->info('开始时间: '.$start->toDateTimeString());
|
||||
$this->info('结束时间: '.$end->toDateTimeString());
|
||||
$this->info('开始同步');
|
||||
$this->info('...');
|
||||
$this->synchronize($device, $start, $end);
|
||||
$this->info('Done!');
|
||||
$this->info('----------------------------------');
|
||||
|
||||
if ($now->isSameDay($lastDate)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->setLastDate($device, $lastDate);
|
||||
} while (true);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步设备历史数据
|
||||
*
|
||||
* @param string $device
|
||||
* @param \Carbon\Carbon $start
|
||||
* @param \Carbon\Carbon $end
|
||||
* @return void
|
||||
*/
|
||||
protected function synchronize(string $device, Carbon $start, Carbon $end)
|
||||
{
|
||||
// 分页页码
|
||||
$page = 0;
|
||||
// 每页条数
|
||||
$size = 50;
|
||||
// 开始时间戳
|
||||
$startTime = $start->unix() * 1000;
|
||||
// 结束时间戳
|
||||
$endTime = $end->unix() * 1000;
|
||||
|
||||
LinkosDeviceLog::where('device_id', $device)->whereBetween('reported_at', [$start, $end])->delete();
|
||||
|
||||
do {
|
||||
$result = retry(5, function () use ($device, $page, $size, $startTime, $endTime) {
|
||||
return $this->linkosService()->post('/deviceFlow/v1/list', [
|
||||
'device_id' => $device,
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'pageable' => [
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
],
|
||||
]);
|
||||
}, 100);
|
||||
|
||||
$data = collect($result['data']['content']);
|
||||
|
||||
$count = $data->count();
|
||||
|
||||
if ($count === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$time = now();
|
||||
|
||||
LinkosDeviceLog::insert(
|
||||
$data->map(function ($item) use ($time) {
|
||||
return [
|
||||
'device_id' => $item['device_id'],
|
||||
'device_unit' => $item['device_unit'],
|
||||
'device_category' => $item['device_category'],
|
||||
'data' => ! empty($item['data']) ? json_encode($item['data']) : '{}',
|
||||
'reported_at' => $item['createTime'],
|
||||
'created_at' => $time->toDateTimeString(),
|
||||
'updated_at' => $time->toDateTimeString(),
|
||||
];
|
||||
})->toArray()
|
||||
);
|
||||
|
||||
unset($result, $data);
|
||||
|
||||
$page++;
|
||||
} while ($count === $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \App\Services\LinkosService
|
||||
*/
|
||||
protected function linkosService(): LinkosService
|
||||
{
|
||||
if ($this->linkosService === null) {
|
||||
$this->linkosService = app(LinkosService::class);
|
||||
}
|
||||
|
||||
return $this->linkosService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备最后同步日期
|
||||
*
|
||||
* @param string $device
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
protected function getLastDate(string $device): ?Carbon
|
||||
{
|
||||
if (is_null($date = Cache::get($this->generateKey($device)))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Carbon::parse($date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置设备最后同步日期
|
||||
*
|
||||
* @param string $device
|
||||
* @param \Carbon\Carbon $date
|
||||
* @return void
|
||||
*/
|
||||
protected function setLastDate(string $device, Carbon $date): void
|
||||
{
|
||||
Cache::put($this->generateKey($device), $date->toDateString(), 86400);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $device
|
||||
* @return string
|
||||
*/
|
||||
protected function generateKey(string $device): string
|
||||
{
|
||||
return 'linkos_device_log:'.$device.'_last_sync_date';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands\YunFei;
|
||||
|
||||
use App\Enums\DeviceType;
|
||||
use App\Models\Device;
|
||||
use App\Models\InsecticidalLampDailyReport;
|
||||
use App\Models\InsecticidalLampReport;
|
||||
use App\Services\YunFeiDeviceService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeviceLogDailyReportCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'yunfei:device-log-daily-report
|
||||
{--sleep=300 : 监控报告生产后的休眠时间(秒)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '按设备厂商生成监控报告';
|
||||
|
||||
/**
|
||||
* @var \App\Services\DeviceLogService
|
||||
*/
|
||||
protected $deviceLogService;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$seconds = (int) value(fn ($seconds) => is_numeric($seconds) ? $seconds : 300, $this->option('sleep'));
|
||||
|
||||
while (true) {
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$devices = Device::supplierBy('device-supplier-yunfei')->get();
|
||||
|
||||
foreach ($devices as $device) {
|
||||
$this->createReport($device);
|
||||
}
|
||||
|
||||
sleep($seconds);
|
||||
};
|
||||
}
|
||||
|
||||
protected function createReport(Device $device): void
|
||||
{
|
||||
[$lastReportedAt, $latestReportedAt] = value(function (Device $device) {
|
||||
$lastReportedAt = null;
|
||||
|
||||
$latestReportedAt = null;
|
||||
|
||||
switch ($device->type) {
|
||||
case DeviceType::InsecticidalLamp:
|
||||
$lastReportedAt = InsecticidalLampDailyReport::where('device_id', $device->id)
|
||||
->latest('reported_at')
|
||||
->value('reported_at');
|
||||
|
||||
$lastReportedAt ??= InsecticidalLampReport::where('device_id', $device->id)
|
||||
->oldest('reported_at')
|
||||
->value('reported_at');
|
||||
|
||||
if ($lastReportedAt) {
|
||||
$latestReportedAt = InsecticidalLampReport::where('device_id', $device->id)
|
||||
->latest('reported_at')
|
||||
->value('reported_at');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return [$lastReportedAt, $latestReportedAt];
|
||||
}, $device);
|
||||
|
||||
if ($lastReportedAt === null || $latestReportedAt === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$service = new YunFeiDeviceService();
|
||||
|
||||
/** @var \Carbon\Carbon */
|
||||
$startAt = $lastReportedAt->copy()->startOfDay();
|
||||
|
||||
do {
|
||||
$service->createDailyReport($device, $startAt->copy());
|
||||
|
||||
$startAt->addDay();
|
||||
} while ($latestReportedAt->gte($startAt));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands\YunFei;
|
||||
|
||||
use App\Enums\DeviceType;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceLog;
|
||||
use App\Models\InsecticidalLampReport;
|
||||
use App\Services\YunFeiDeviceService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeviceLogReportCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'yunfei:device-log-report
|
||||
{--sleep=300 : 监控报告生产后的休眠时间(秒)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '按设备厂商生成监控报告';
|
||||
|
||||
/**
|
||||
* @var \App\Services\DeviceLogService
|
||||
*/
|
||||
protected $deviceLogService;
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$seconds = (int) value(fn ($seconds) => is_numeric($seconds) ? $seconds : 300, $this->option('sleep'));
|
||||
|
||||
while (true) {
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$devices = Device::supplierBy('device-supplier-yunfei')->get();
|
||||
|
||||
foreach ($devices as $device) {
|
||||
$this->createReport($device);
|
||||
}
|
||||
|
||||
sleep($seconds);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建比昂设备报告
|
||||
*/
|
||||
protected function createReport(Device $device): void
|
||||
{
|
||||
$lastReportedAt = match ($device->type) {
|
||||
DeviceType::InsecticidalLamp => InsecticidalLampReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at'),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (is_null($lastReportedAt ??= DeviceLog::where('device_id', $device->id)->oldest('reported_at')->value('reported_at'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($latestReportedAt = DeviceLog::where('device_id', $device->id)->latest('reported_at')->value('reported_at'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$service = new YunFeiDeviceService();
|
||||
|
||||
/** @var \Carbon\Carbon */
|
||||
$startAt = $lastReportedAt->copy()->startOfHour();
|
||||
|
||||
do {
|
||||
$service->createReport($device, $startAt->copy());
|
||||
|
||||
$startAt->addHour();
|
||||
} while ($latestReportedAt->gte($startAt));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,6 +18,10 @@ class Kernel extends ConsoleKernel
|
|||
$schedule->command(Commands\BiAng\WormStatisticsSyncCommand::class)
|
||||
->hourly()
|
||||
->runInBackground();
|
||||
|
||||
$schedule->command(Commands\Linkos\WormReportCommand::class)
|
||||
->hourlyAt(15)
|
||||
->runInBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class LinkosFarmException extends RuntimeException
|
||||
{
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ use App\Enums\OperationType;
|
|||
use App\Helpers\Paginator;
|
||||
use App\Http\Requestes\AgriculturalBaseRequest;
|
||||
use App\Http\Resources\AgriculturalBaseResource;
|
||||
use App\Http\Resources\DeviceResource;
|
||||
use App\Models\AgriculturalBase;
|
||||
use App\Models\Device;
|
||||
use App\Services\OperationLogService;
|
||||
|
|
@ -142,9 +143,9 @@ class AgriculturalBaseController extends Controller
|
|||
|
||||
/**
|
||||
* 获取指定基地指定设备类型下所有监控点名称选项
|
||||
* device_type
|
||||
*/
|
||||
public function basePointList(AgriculturalBase $agriculturalBasic, Request $request){
|
||||
public function basePointList(AgriculturalBase $agriculturalBasic, Request $request)
|
||||
{
|
||||
$deviceType = $request->input('device_type', DeviceType::Meteorological);
|
||||
$list = Device::where([
|
||||
'agricultural_base_id' => $agriculturalBasic->id,
|
||||
|
|
@ -152,4 +153,11 @@ class AgriculturalBaseController extends Controller
|
|||
])->orderBy('sort', 'desc')->get()->pluck('monitoring_point', 'id')->toArray();
|
||||
return $this->json($list);
|
||||
}
|
||||
|
||||
public function basePoints(Request $request)
|
||||
{
|
||||
$devices = Device::filter($request->input())->orderBy('sort', 'desc')->get();
|
||||
|
||||
return DeviceResource::collection($devices);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -8,6 +8,7 @@ use App\Enums\OperationType;
|
|||
use App\Helpers\Paginator;
|
||||
use App\Http\Requestes\DeviceRequest;
|
||||
use App\Http\Resources\DeviceResource;
|
||||
use App\Http\Resources\WormPhotoResource;
|
||||
use App\Models\AgriculturalBase;
|
||||
use App\Models\Device;
|
||||
use App\Models\InsecticidalLampDailyReport;
|
||||
|
|
@ -671,19 +672,22 @@ class DeviceController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
$device = Device::findOrFail($id);
|
||||
|
||||
$wormReports = WormReport::where('device_id', $device->id)
|
||||
$wormReports = WormReport::where('device_id', $id)
|
||||
->whereBetween('reported_at', [$startTime->toDateString(), $endTime->toDateString()])
|
||||
->pluck('worm_num', 'reported_at');
|
||||
|
||||
$data = [];
|
||||
|
||||
do {
|
||||
$key = $startTime->toDateString();
|
||||
$data[$key] = $wormReports->get($key);
|
||||
$startTime->addDay();
|
||||
} while ($startTime->lte($endTime));
|
||||
if ($startTime->lte($endTime)) {
|
||||
do {
|
||||
// 日期
|
||||
$date = $startTime->toDateString();
|
||||
|
||||
$data[$date] = $wormReports->get($date);
|
||||
|
||||
$startTime->addDay();
|
||||
} while ($startTime->lte($endTime));
|
||||
}
|
||||
|
||||
return $this->json($data);
|
||||
}
|
||||
|
|
@ -714,40 +718,11 @@ class DeviceController extends Controller
|
|||
fn () => $endTime->copy()->startOfDay(),
|
||||
);
|
||||
|
||||
if ($startTime->gt($endTime)) {
|
||||
throw ValidationException::withMessages([
|
||||
'start_time' => ['开始时间不能大于结束时间'],
|
||||
]);
|
||||
}
|
||||
|
||||
$device = Device::findOrFail($id);
|
||||
|
||||
$data = [];
|
||||
|
||||
switch ($device->supplier_key) {
|
||||
case 'device-supplier-biang':
|
||||
$result = $biAngDeviceService->getWormPhotos(
|
||||
$device,
|
||||
Carbon::parse($request->input('start_time')),
|
||||
Carbon::parse($request->input('end_time')),
|
||||
);
|
||||
$data = $result['imgUrl'];
|
||||
break;
|
||||
|
||||
default:
|
||||
$wormPhotos = WormPhoto::where('device_id', $device->id)
|
||||
$wormPhotos = WormPhoto::where('device_id', $id)
|
||||
->whereBetween('uploaded_at', [$startTime, $endTime])
|
||||
->latest('uploaded_at')
|
||||
->get();
|
||||
->paginate($request->input('per_page', 20));
|
||||
|
||||
$data = $wormPhotos->map(fn ($item) => [
|
||||
'id' => $item->id,
|
||||
'url' => $item->url,
|
||||
'time' => $item->created_at->toDateTimeString(),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->json($data);
|
||||
return WormPhotoResource::collection($wormPhotos);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class Kernel extends HttpKernel
|
|||
],
|
||||
|
||||
'api' => [
|
||||
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
'throttle:600,1',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\FormatJsonResponse::class,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class WormPhotoResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'url' => $this->image_url,
|
||||
'time' => $this->uploaded_at->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace App\Iot\Linkos;
|
||||
|
||||
use App\Exceptions\LinkosFarmException;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class FarmClient
|
||||
{
|
||||
public const ENDPOINT_URL = 'http://api.farm.0531yun.cn';
|
||||
|
||||
/**
|
||||
* 授权令牌
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* 授权令牌有效期
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $expires;
|
||||
|
||||
public function __construct(
|
||||
protected readonly string $loginName,
|
||||
protected readonly string $loginPwd,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
*/
|
||||
public function devices(?string $groupId = null, ?string $deviceType = null): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/entrance/device/getsysUserDevice', [
|
||||
'groupId' => $groupId,
|
||||
'deviceType' => $deviceType,
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户区域
|
||||
*/
|
||||
public function groups(?string $groupName = null): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/entrance/group/getsysUserGroup', [
|
||||
'groupName' => $groupName,
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备实时数据
|
||||
*/
|
||||
public function realTimeData(array $devices = []): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/entrance/device/getRealTimeData', [
|
||||
'deviceAddrs' => implode(',', $devices),
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 虫情区域统计
|
||||
*/
|
||||
public function wormStatistics(string $groupId, Carbon $beginTime, Carbon $endTime): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/worm/deviceData/getWormStatisticsByGroup', [
|
||||
'groupId' => $groupId,
|
||||
'beginTime' => $beginTime->toDateTimeString(),
|
||||
'endTime' => $endTime->toDateTimeString(),
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 虫情设备分析报表
|
||||
*/
|
||||
public function wormAnalyseData(string $device, Carbon $beginTime, Carbon $endTime, int $pages, int $limit = 100): array
|
||||
{
|
||||
$result = $this->get('/api/v2.0/worm/deviceData/getWormDataList', [
|
||||
'deviceAddr' => $device,
|
||||
'beginTime' => $beginTime->toDateTimeString(),
|
||||
'endTime' => $endTime->toDateTimeString(),
|
||||
'pages' => $pages,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
public function get(string $url, array $query = []): array
|
||||
{
|
||||
return $this->request('GET', $url, [
|
||||
'query' => $query,
|
||||
]);
|
||||
}
|
||||
|
||||
public function post(string $url, array $data = []): array
|
||||
{
|
||||
return $this->request('POST', $url, [
|
||||
'json' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function request(string $method, string $url, array $options = []): array
|
||||
{
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
if ($url !== '/api/v2.0/entrance/user/userLogin') {
|
||||
$headers['token'] = $this->token();
|
||||
}
|
||||
|
||||
/** @var \Illuminate\Http\Client\Response */
|
||||
$response = Http::withHeaders($headers)->baseUrl(self::ENDPOINT_URL)->send($method, $url, $options);
|
||||
|
||||
$json = $response->throw()->json();
|
||||
|
||||
if (data_get($json, 'code') === 1000) {
|
||||
return $json;
|
||||
}
|
||||
|
||||
throw new LinkosFarmException(
|
||||
data_get($json, 'message', '出错啦'),
|
||||
data_get($json, 'code', 0),
|
||||
);
|
||||
}
|
||||
|
||||
protected function token(): string
|
||||
{
|
||||
if ($this->token && $this->expires > now()->unix() + 30) {
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
$result = $this->post('/api/v2.0/entrance/user/userLogin', [
|
||||
'loginName' => $this->loginName,
|
||||
'loginPwd' => $this->loginPwd,
|
||||
]);
|
||||
|
||||
$this->expires = $result['data']['expDate'];
|
||||
|
||||
return $this->token = $result['data']['token'];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class WormPhoto extends Model
|
||||
{
|
||||
|
|
@ -16,4 +18,16 @@ class WormPhoto extends Model
|
|||
protected $fillable = [
|
||||
'device_id', 'url', 'uploaded_at',
|
||||
];
|
||||
|
||||
public function imageUrl(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function(mixed $value, array $attributes) {
|
||||
if (preg_match('/^https?:\/\//', $attributes['url']) > 0) {
|
||||
return $attributes['url'];
|
||||
}
|
||||
return Storage::disk('public')->url($attributes['url']);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\DeviceType;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceLog;
|
||||
use App\Models\InsecticidalLampDailyReport;
|
||||
use App\Models\InsecticidalLampReport;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class YunFeiDeviceService
|
||||
{
|
||||
/**
|
||||
* 创建设备报告
|
||||
*/
|
||||
public function createReport(Device $device, Carbon $time): void
|
||||
{
|
||||
switch ($device->type) {
|
||||
case DeviceType::InsecticidalLamp:
|
||||
$this->createInsecticidalLampReport($device, $time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建杀虫灯设备报告
|
||||
*/
|
||||
protected function createInsecticidalLampReport(Device $device, Carbon $time): void
|
||||
{
|
||||
$reportedAt = $time->copy()->startOfHour();
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$logs = DeviceLog::where('device_id', $device->id)
|
||||
->whereBetween('reported_at', [$reportedAt, $reportedAt->copy()->endOfHour()])
|
||||
->oldest('reported_at')
|
||||
->get();
|
||||
|
||||
if ($logs->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attributes = value(function ($logs) {
|
||||
$data = [
|
||||
'battery_vol' => ['sum' => 0, 'count' => 0],
|
||||
'charging_vol' => ['sum' => 0, 'count' => 0],
|
||||
'killed_num' => ['sum' => 0, 'count' => 0],
|
||||
'air_temperature' => ['sum' => 0, 'count' => 0],
|
||||
'air_humidity' => ['sum' => 0, 'count' => 0],
|
||||
];
|
||||
|
||||
/** @var \App\Models\DeviceLog */
|
||||
foreach ($logs as $log) {
|
||||
if (! is_array($log->data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($data as $key => $item) {
|
||||
switch ($key) {
|
||||
// 电池电压
|
||||
case 'battery_vol':
|
||||
if (! is_null($bv = Arr::get($log->data, 'bv'))) {
|
||||
$item['sum'] = bcadd($item['sum'], $bv, 2);
|
||||
$item['count'] += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
// 充电电压
|
||||
case 'charging_vol':
|
||||
if (! is_null($cv = Arr::get($log->data, 'cv'))) {
|
||||
$item['sum'] = bcadd($item['sum'], $cv, 2);
|
||||
$item['count'] += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
// 杀虫数
|
||||
case 'killed_num':
|
||||
if (! is_null($ct = Arr::get($log->data, 'ct'))) {
|
||||
$item['sum'] += $ct;
|
||||
$item['count'] += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
// 温度
|
||||
case 'air_temperature':
|
||||
if (! is_null($at = Arr::get($log->data, 'at'))) {
|
||||
$item['sum'] = bcadd($item['sum'], $at, 2);
|
||||
$item['count'] += 1;
|
||||
}
|
||||
break;
|
||||
|
||||
// 湿度
|
||||
case 'air_humidity':
|
||||
if (! is_null($ah = Arr::get($log->data, 'ah'))) {
|
||||
$item['sum'] = bcadd($item['sum'], $ah, 2);
|
||||
$item['count'] += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$data[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
|
||||
foreach ($data as $key => $item) {
|
||||
if ($item['count'] > 0) {
|
||||
if ($key === 'killed_num') {
|
||||
$attributes[$key] = (int) $item['sum'];
|
||||
} else {
|
||||
$attributes[$key] = round(bcdiv($item['sum'], $item['count'], 2), 2);
|
||||
}
|
||||
} else {
|
||||
$attributes[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}, $logs);
|
||||
|
||||
/** @var \App\Models\InsecticidalLampReport */
|
||||
$insecticidalLampReport = InsecticidalLampReport::firstOrNew([
|
||||
'device_id' => $device->id,
|
||||
'reported_at' => $reportedAt,
|
||||
], [
|
||||
'agricultural_base_id' => $device->agricultural_base_id,
|
||||
]);
|
||||
|
||||
$insecticidalLampReport->fill($attributes)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备每日报告
|
||||
*/
|
||||
public function createDailyReport(Device $device, Carbon $time): void
|
||||
{
|
||||
switch ($device->type) {
|
||||
case DeviceType::InsecticidalLamp:
|
||||
$this->createInsecticidalLampDailyReport($device, $time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 杀虫灯每日报告
|
||||
*/
|
||||
protected function createInsecticidalLampDailyReport(Device $device, Carbon $date): void
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Collection */
|
||||
$insecticidalLampReports = InsecticidalLampReport::where('device_id', $device->id)
|
||||
->whereDate('reported_at', $date)
|
||||
->oldest('reported_at')
|
||||
->get();
|
||||
|
||||
if ($insecticidalLampReports->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$attributes = value(function ($insecticidalLampReports) {
|
||||
$data = [
|
||||
'battery_vol' => ['sum' => 0, 'count' => 0],
|
||||
'charging_vol' => ['sum' => 0, 'count' => 0],
|
||||
'killed_num' => ['sum' => 0, 'count' => 0],
|
||||
'air_temperature' => ['sum' => 0, 'count' => 0],
|
||||
'air_humidity' => ['sum' => 0, 'count' => 0],
|
||||
'high_vol' => ['sum' => 0, 'count' => 0],
|
||||
];
|
||||
|
||||
foreach ($insecticidalLampReports as $insecticidalLampReport) {
|
||||
foreach ($data as $k => $item) {
|
||||
if (is_null($v = $insecticidalLampReport->{$k})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['sum'] = bcadd($item['sum'], $v, 2);
|
||||
$item['count']++;
|
||||
|
||||
$data[$k] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
|
||||
foreach ($data as $key => $item) {
|
||||
if ($item['count'] > 0) {
|
||||
if ($key === 'killed_num') {
|
||||
$attributes[$key] = (int) $item['sum'];
|
||||
} else {
|
||||
$attributes[$key] = round(bcdiv($item['sum'], $item['count'], 2), 2);
|
||||
}
|
||||
} else {
|
||||
$attributes[$key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}, $insecticidalLampReports);
|
||||
|
||||
/** @var \App\Models\InsecticidalLampDailyReport */
|
||||
$insecticidalLampDailyReport = InsecticidalLampDailyReport::firstOrNew([
|
||||
'device_id' => $device->id,
|
||||
'reported_at' => $date->format('Y-m-d'),
|
||||
], [
|
||||
'agricultural_base_id' => $device->agricultural_base_id,
|
||||
]);
|
||||
|
||||
$insecticidalLampDailyReport->fill($attributes)->save();
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ Route::group([
|
|||
Route::apiResource('town-street', AgriculturalBaseController::class)->names('town_street');//镇街
|
||||
Route::get('agricultural-device-basic', [AgriculturalBaseController::class, 'deviceBase'])->name('agricultural_basic.device_bases'); //通过设备类型查询基地
|
||||
Route::get('agricultural-device-point/{agricultural_basic}', [AgriculturalBaseController::class, 'basePointList']);//获取基地对应监控点
|
||||
Route::get('agricultural-device-points', [AgriculturalBaseController::class, 'basePoints']);
|
||||
|
||||
//农作物
|
||||
Route::apiResource('crops', CropController::class)->names('crops');//基地农作物
|
||||
|
|
|
|||
Loading…
Reference in New Issue