lcly-data-admin/app/Http/Controllers/DeviceController.php

899 lines
33 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Http\Controllers;
use App\Enums\DeviceStatus;
use App\Enums\DeviceType;
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\Iot\MoJing\HttpClient as MoJingHttpClient;
use App\Iot\Qly\HttpClient as QlyHttpClient;
use App\Models\AgriculturalBase;
use App\Models\Device;
use App\Models\InsecticidalLampDailyReport;
use App\Models\InsecticidalLampReport;
use App\Models\MeteorologicalMonitoringDailyLog;
use App\Models\MeteorologicalMonitoringLog;
use App\Models\SoilMonitoringDailyLog;
use App\Models\SoilMonitoringLog;
use App\Models\WaterQualityMonitoringDailyLog;
use App\Models\WaterQualityMonitoringLog;
use App\Models\WormPhoto;
use App\Models\WormReport;
use App\Services\BiAngDeviceService;
use App\Services\OperationLogService;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Peidikeji\Setting\Models\Setting;
class DeviceController extends Controller
{
public function index(Request $request)
{
$query = Device::with(['base', 'supplier', 'project'])->filter($request->input())->orderBy('sort', 'desc');
$list = $query->paginate(Paginator::resolvePerPage('per_page', 20, 50));
return $this->json(DeviceResource::collection($list));
}
public function store(DeviceRequest $request)
{
$input = $request->input();
//如果不是监控设备移除extends
if ($input['type'] != DeviceType::Monitor->value) {
unset($input['extends']);
}
$device = Device::create(array_merge($input, [
'created_by' => auth('api')->user()?->id ?? 0,
'updated_by' => auth('api')->user()?->id ?? 0,
]));
(new OperationLogService())->inLog(OperationType::Create, '', $device, $request->input());
return $this->success('添加成功');
}
public function show(Device $device)
{
$device->loadMissing(['base', 'supplier', 'project']);
return $this->json(DeviceResource::make($device));
}
public function update(Device $device, DeviceRequest $request)
{
$input = $request->input();
//如果不是监控设备移除extends
if ($input['type'] != DeviceType::Monitor->value) {
$input['extends'] = null;
}
$device->update(array_merge($input, [
'updated_by' => auth('api')->user()?->id ?? 0,
]));
(new OperationLogService())->inLog(OperationType::Update, '', $device, $request->input());
return $this->success('修改成功');
}
public function destroy(Device $device)
{
$device->delete();
(new OperationLogService())->inLog(OperationType::Delete, '', $device);
return $this->success('删除成功');
}
public function types()
{
return $this->json(DeviceType::types());
}
public function updateRecommendStatus(Device $device){
$device->update(['is_recommend'=> $device->is_recommend ? 0:1]);
return $this->success('修改成功');
}
/**
* 统计某个基地下所有设备状态数量
*/
public function typeStatusNum(Request $request)
{
$baseId = $request->input('base_id', 0);
$parent = $request->input('parent', 0);
$query = Device::query();
if($parent){
$baseIds = AgriculturalBase::where('parent_id', $parent)->pluck('id')->toArray();
if(count($baseIds) > 0){
$query->whereIn('agricultural_base_id', $baseIds);
}else{
$query->where('agricultural_base_id', 0);
}
}
//如果查了镇街就不查指定基地了
if(!$parent && $baseId){
$query->where('agricultural_base_id', $baseId);
}
$query->groupBy('type')->groupBy('status');
$list = $query->select(DB::raw('type, status, count(1) as num '))->get();
$resData = [];
foreach ($list as $item) {
$resData[$item->type->value][$item->status->value] = $item->num;
}
//初始化数据;
$data = [];
foreach (DeviceType::types() as $typeKey => $typeName) {
foreach (DeviceStatus::status() as $statusKey => $statusName) {
$data[$typeKey][$statusKey] = $resData[$typeKey][$statusKey] ?? 0;
}
}
$data = [
1 => [0, 148, 21, 0],
2 => [0, 20, 3, 0],
3 => [0, 10, 2, 0],
4 => [0, 8, 3, 0],
5 => [0, 4, 1, 0],
6 => [0, 4, 0, 0],
7 => [0, 0, 0, 0],
];
return $this->json($data);
}
/**
* 设备数据
*/
public function dataStatics(Request $request)
{
$deviceId = $request->input('device_id');
$deviceColumn = $request->input('device_column'); //指定字段
$device = Device::find($deviceId);
$data = null;
switch ($device->type) {
case DeviceType::Monitor://监控设备
$data = DeviceResource::make($device);
break;
case DeviceType::Meteorological://气象设备
//当天最新一条
$log = MeteorologicalMonitoringLog::where('device_id', $deviceId)->orderBy('created_at', 'desc')->first();
$data = $log->toArray();
break;
case DeviceType::Soil://土壤设备
//当前时间往前推6个小时;
$startTime = now()->subHours(6);
$dataList = SoilMonitoringLog::where('device_id', $deviceId)->where('monitored_at', '>=', $startTime)->get()->keyBy('monitored_at')->toArray();
$data = [];
for ($i = 5; $i >= 0; $i--) {
$_key = now()->subHours($i)->format('Y-m-d H').':00:00';
$data[$_key] = null;
if (isset($dataList[$_key])) {
$data[$_key] = $dataList[$_key][$deviceColumn] ?? null;
}
}
break;
case DeviceType::WaterQuality://水质设备
//当天
$startTime = now()->subHours(6);
$dataList = WaterQualityMonitoringLog::where('device_id', $deviceId)->where('monitored_at', '>=', $startTime)->get()->keyBy('monitored_at')->toArray();
$data = [];
for ($i = 5; $i >= 0; $i--) {
$_key = now()->subHours($i)->format('Y-m-d H').':00:00';
$data[$_key] = null;
if (isset($dataList[$_key])) {
$data[$_key] = $dataList[$_key][$deviceColumn] ?? null;
}
}
break;
}
return $this->json([
'list' => $data,
]);
}
/**
* 获取指定基地指定设备类型的所有设备指定维度数据
*/
public function baseDataStatics(Request $request)
{
$baseId = $request->input('base_id');
$deviceType = $request->input('device_type');
$deviceColumn = $request->input('device_column'); //指定字段
//先获取基地下该类型所有设备
$deviceData = Device::where([
'agricultural_base_id' => $baseId,
'type' => $deviceType,
])->orderBy('sort', 'desc')->get();
$data = [];
switch ($deviceType) {
case DeviceType::Monitor->value://监控设备
$data = DeviceResource::collection($deviceData);
break;
case DeviceType::Soil->value:
$startTime = now()->subHours(6);
$dataList = SoilMonitoringLog::where('agricultural_base_id', $baseId)->where('monitored_at', '>=', $startTime)->get()->groupBy('device_id');
foreach ($deviceData as $device) {
$_dataList = $dataList->get($device->id);
$data[$device->monitoring_point] = [];
if ($_dataList) {
$_dataList = $_dataList->keyBy('monitored_at')->toArray();
}
for ($i = 5; $i >= 0; $i--) {
$_key = now()->subHours($i)->format('Y-m-d H').':00:00';
$data[$device->monitoring_point][$_key] = null;
if (isset($_dataList[$_key])) {
$data[$device->monitoring_point][$_key] = $_dataList[$_key][$deviceColumn] ?? null;
}else{
$data[$device->monitoring_point][$_key] = $data[$device->monitoring_point][now()->subHours($i+1)->format('Y-m-d H').':00:00'] ?? null;
}
}
}
break;
case DeviceType::WaterQuality->value:
$startTime = now()->subHours(6);
$dataList = WaterQualityMonitoringLog::where('agricultural_base_id', $baseId)->where('monitored_at', '>=', $startTime)->get()->groupBy('device_id');
foreach ($deviceData as $device) {
$_dataList = $dataList->get($device->id);
$data[$device->monitoring_point] = [];
if ($_dataList) {
$_dataList = $_dataList->keyBy('monitored_at')->toArray();
}
for ($i = 5; $i >= 0; $i--) {
$_key = now()->subHours($i)->format('Y-m-d H').':00:00';
$data[$device->monitoring_point][$_key] = $_dataList[$_key][$deviceColumn] ?? null;
if ($device->supplier_key === 'device-supplier-linkos') {
if (isset($_dataList[$_key])) {
// if($deviceColumn == 'ph'){
// $data[$device->monitoring_point][$_key] = 7.49;
// }elseif($deviceColumn == 'temperature'){
// $data[$device->monitoring_point][$_key] = 10.00;
// }elseif($deviceColumn == 'turbidity'){
// $data[$device->monitoring_point][$_key] = 1028.60;
// }else{
// $data[$device->monitoring_point][$_key] = $_dataList[$_key][$deviceColumn] ?? null;
// }
$data[$device->monitoring_point][$_key] = $_dataList[$_key][$deviceColumn] ?? null;
}
// else{//临时写一些假数据
// switch($deviceColumn){
// case 'chlorine':
// $data[$device->monitoring_point][$_key] = 0.016;
// break;
// case 'conductivity':
// $data[$device->monitoring_point][$_key] = 563;//电导率
// break;
// case 'oxygen':
// $data[$device->monitoring_point][$_key] = 0.09;//含氧量
// break;
// case 'ph':
// $data[$device->monitoring_point][$_key] = rand(750, 770) / 100;
// break;
// case 'temperature':
// $data[$device->monitoring_point][$_key] = rand(950, 1050) / 100;
// break;
// case 'turbidity':
// $data[$device->monitoring_point][$_key] = 1028.60;
// break;
// }
// }
}
}
}
break;
}
return $this->json($data);
}
public function baseDataStaticsV2(Request $request)
{
$baseId = $request->base_id;
$deviceType = DeviceType::tryFrom($request->device_type);
$deviceColumns = $request->whenFilled(
'device_columns',
fn ($deviceColumns) => explode(',', $deviceColumns),
fn () => []
);
$devices = Device::where([
'agricultural_base_id' => $baseId,
'type' => $deviceType,
])->orderBy('sort', 'desc')->get();
switch ($deviceType) {
case DeviceType::Soil:
$end = now()->startOfHour();
$start = $end->copy()->subHours(5);
$monitoringLogGroups = SoilMonitoringLog::where('agricultural_base_id', $baseId)
->where('monitored_at', '>=', $start)
->get()
->groupBy('device_id');
$data = [];
foreach ($deviceColumns as $deviceColumn) {
$x = [];
$series = [];
foreach ($devices as $device) {
$monitoringLogMap = $monitoringLogGroups->get($device->id, collect())->keyBy('monitored_at');
$startAt = $start->copy();
$y = [];
while ($startAt->lte($end)) {
$monitoringLog = $monitoringLogMap->get(
$monitoredAt = $startAt->format('Y-m-d H:i:s')
);
$x[] = $monitoredAt;
$y[] = $monitoringLog?->{$deviceColumn};
$startAt->addHours(1);
}
$series[] = [
'name' => $device->monitoring_point,
'data' => $y,
];
}
$data[$deviceColumn] = [
'x_axis' => $x,
'series' => $series,
];
}
return $data;
case DeviceType::WaterQuality:
$end = now()->startOfHour();
$start = $end->copy()->subHours(5);
$monitoringLogGroups = WaterQualityMonitoringLog::where('agricultural_base_id', $baseId)
->where('monitored_at', '>=', $start)
->get()
->groupBy('device_id');
$data = [];
foreach ($deviceColumns as $deviceColumn) {
$x = [];
$series = [];
foreach ($devices as $device) {
$monitoringLogMap = $monitoringLogGroups->get($device->id, collect())->keyBy('monitored_at');
$startAt = $start->copy();
$y = [];
while ($startAt->lte($end)) {
$monitoringLog = $monitoringLogMap->get(
$monitoredAt = $startAt->format('Y-m-d H:i:s')
);
$x[] = $monitoredAt;
if ($monitoringLog) {
$value = $monitoringLog->{$deviceColumn};
// if (is_null($value) && $device->supplier_key === 'device-supplier-linkos') {
// $value = match ($deviceColumn) {
// 'ph' => 7.49,
// 'temperature' => 10.00,
// 'turbidity' => 1028.60,
// default => $value,
// };
// }
$y[] = $value;
}
// elseif ($device->supplier_key === 'device-supplier-linkos') {
// $y[] = match ($deviceColumn) {
// 'chlorine' => 0.016,
// 'conductivity' => 563,
// 'oxygen' => 0.09,
// 'ph' => rand(750, 770) / 100,
// 'temperature' => rand(900, 1100) / 100,
// 'turbidity' => 1028.60,
// default => null,
// };
// }
else {
$y[] = null;
}
$startAt->addHours(1);
}
$series[] = [
'name' => $device->monitoring_point,
'data' => $y,
];
}
$data[$deviceColumn] = [
'x_axis' => $x,
'series' => $series,
];
}
return $data;
default:
return [];
}
}
public function timeZoneList(Request $request)
{
$request->validate([
'device_id' => ['bail', 'required'],
'start_time' => ['bail', 'nullable', 'date_format:Y-m-d'],
'end_time' => ['bail', 'nullable', 'date_format:Y-m-d'],
]);
$isSameDay = true;
if ($request->filled('start_time') && $request->filled('end_time')) {
$startTime = Carbon::parse($request->input('start_time'))->startOfDay();
$endTime = Carbon::parse($request->input('end_time'))->startOfDay();
if ($startTime->gt($endTime)) {
throw ValidationException::withMessages([
'start_time' => ['开始时间不能大于结束时间'],
]);
}
// 如果开始时间和结束时间是同一天
if ($startTime->eq($endTime)) {
$endTime = $startTime->isToday() ? now() : $startTime->copy()->endOfDay();
} else {
$isSameDay = false;
}
} else {
$endTime = now();
$startTime = $endTime->copy()->startOfDay();
}
$device = Device::findOrFail($request->input('device_id'));
$fields = [];
$monitoringLogs = collect();
switch ($device->type) {
// 气象设备
case DeviceType::Meteorological:
$fields = [
'wind_speed',
'wind_direction',
'wind_degree',
'air_humidity',
'air_temperature',
'air_pressure',
'co2',
'noise',
'illumination',
'pm25',
'pm10',
'rainfall',
];
/** @var \Illuminate\Support\Collection */
$monitoringLogs = (
$isSameDay
? MeteorologicalMonitoringLog::query()
: MeteorologicalMonitoringDailyLog::query()
)
->where('device_id', $device->id)
->whereBetween('monitored_at', [$startTime, $endTime])
->get()
->mapWithKeys(function ($item) use ($fields) {
$key = $item->monitored_at->toDateTimeString();
$data = collect($fields)->mapWithKeys(fn ($field) => [$field => $field !== 'rainfall' ? $item[$field] : null])->all();
if ($item instanceof MeteorologicalMonitoringDailyLog) {
$data['rainfall'] = $item->daily_rainfall;
} else {
$data['rainfall'] = $item->current_rainfall;
}
return [$key => $data];
});
break;
// 土壤设备
case DeviceType::Soil:
$fields = [
'conductivity',
'humidity',
'temperature',
'n',
'p',
'k',
];
switch ($device->supplier_key) {
case 'device-supplier-biang':
$fields = [
'conductivity',
'humidity',
'temperature',
'n',
'p',
'k',
'ph',
];
break;
}
/** @var \Illuminate\Support\Collection */
$monitoringLogs = (
$isSameDay
? SoilMonitoringLog::query()
: SoilMonitoringDailyLog::query()
)
->where('device_id', $device->id)
->whereBetween('monitored_at', [$startTime, $endTime])
->get()
->mapWithKeys(function ($item) use ($fields) {
$key = $item->monitored_at->toDateTimeString();
$data = collect($fields)->mapWithKeys(fn ($field) => [$field => $item[$field]])->all();
return [$key => $data];
});
break;
// 水质设备
case DeviceType::WaterQuality:
$fields = [
'chlorine',
'conductivity',
'oxygen',
'ph',
'temperature',
'turbidity',
];
switch ($device->supplier_key) {
case 'device-supplier-biang':
$fields = [
'oxygen',
'ph',
'temperature',
'turbidity',
'nh3n',
];
break;
}
/** @var \Illuminate\Support\Collection */
$monitoringLogs = (
$isSameDay
? WaterQualityMonitoringLog::query()
: WaterQualityMonitoringDailyLog::query()
)
->where('device_id', $device->id)
->whereBetween('monitored_at', [$startTime, $endTime])
->get()
->mapWithKeys(function ($item) use ($fields) {
$key = $item->monitored_at->toDateTimeString();
$data = collect($fields)->mapWithKeys(fn ($field) => [$field => $item[$field]])->all();
return [$key => $data];
});
break;
// 杀虫灯
case DeviceType::InsecticidalLamp:
$fields = [
'battery_vol',
'killed_num',
'air_temperature',
'air_humidity',
'charging_vol',
'high_vol',
];
/** @var \Illuminate\Support\Collection */
$monitoringLogs = (
$isSameDay
? InsecticidalLampReport::query()
: InsecticidalLampDailyReport::query()
)
->where('device_id', $device->id)
->whereBetween('reported_at', [$startTime, $endTime])
->get()
->mapWithKeys(function ($item) use ($fields) {
$key = $item->reported_at->toDateTimeString();
$data = collect($fields)->mapWithKeys(fn ($field) => [$field => $item[$field]])->all();
return [$key => $data];
});
break;
}
return $this->json(
collect($fields)->mapWithKeys(function ($field) use ($device, $monitoringLogs, $isSameDay, $startTime, $endTime) {
$data = [];
$beginTime = $startTime->copy();
do {
$key = $beginTime->toDateTimeString();
$monitoringLog = $monitoringLogs->get($key);
if (is_null($monitoringLog)) {
$data[$key] = null;
// 如果是水质设备,则写死假数据
// if($device->supplier_key === 'device-supplier-linkos' && $device->type == DeviceType::WaterQuality){
// switch($field){
// case 'chlorine':
// $data[$key] = 0.016;
// break;
// case 'conductivity':
// $data[$key] = rand(560, 565);//电导率
// break;
// case 'oxygen':
// $data[$key] = 0.09;//含氧量
// break;
// case 'ph':
// $data[$key] = rand(750, 770) / 100;
// break;
// case 'temperature':
// $data[$key] = rand(950, 1050) / 100;
// break;
// case 'turbidity':
// $data[$key] = 0.33;
// break;
// }
// } else {
// $data[$key] = null;
// }
} else {
$data[$key] = $monitoringLog[$field];
}
$isSameDay ? $beginTime->addHours(1) : $beginTime->addDays(1);
} while ($beginTime->lte($endTime));
return [$field => $data];
})
);
}
public function getFfmpegServiceIp(){
$data = [
'ip' => '127.0.0.1',
'port'=> '80',
];
$setting = Setting::where('slug', 'ffmpeg_websocket_ip')->first();
$dataValue = $setting?->value ?? '{"ip":"127.0.0.1", "port":"80"}';
$data = json_decode($dataValue);
return $this->json($data);
}
/**
* 虫情统计
*/
public function wormStatics($id, Request $request)
{
$request->validate([
'start_time' => ['bail', 'required_with:end_time', 'date_format:Y-m-d'],
'end_time' => ['bail', 'required_with:start_time', 'date_format:Y-m-d'],
], [], [
'start_time' => '开始时间',
'end_time' => '结束时间',
]);
// 结束时间
$endTime = $request->whenFilled(
'end_time',
fn ($time) => Carbon::parse($time)->startOfDay(),
fn () => now()->startOfDay(),
);
// 开始时间
$startTime = $request->whenFilled(
'start_time',
fn ($time) => Carbon::parse($time)->startOfDay(),
fn () => $endTime->copy()->subDays(6),
);
if ($startTime->gt($endTime)) {
throw ValidationException::withMessages([
'start_time' => ['开始时间不能大于结束时间'],
]);
}
$wormReports = WormReport::where('device_id', $id)
->whereBetween('reported_at', [$startTime->toDateString(), $endTime->toDateString()])
->pluck('worm_num', 'reported_at');
$data = [];
if ($startTime->lte($endTime)) {
do {
// 日期
$date = $startTime->toDateString();
$data[$date] = $wormReports->get($date, 0);
$startTime->addDay();
} while ($startTime->lte($endTime));
}
return $this->json($data);
}
/**
* 虫情图片
*/
public function wormPhotos($id, Request $request, BiAngDeviceService $biAngDeviceService)
{
$request->validate([
'start_time' => ['bail', 'required_with:end_time', 'date_format:Y-m-d'],
'end_time' => ['bail', 'required_with:start_time', 'date_format:Y-m-d'],
], [], [
'start_time' => '开始时间',
'end_time' => '结束时间',
]);
// 结束时间
$endTime = $request->whenFilled(
'end_time',
fn ($time) => Carbon::parse($time)->endOfDay(),
fn () => now()->endOfDay(),
);
// 开始时间
$startTime = $request->whenFilled(
'start_time',
fn ($time) => Carbon::parse($time)->startOfDay(),
fn () => $endTime->copy()->startOfDay(),
);
$wormPhotos = WormPhoto::where('device_id', $id)
->whereBetween('uploaded_at', [$startTime, $endTime])
->latest('uploaded_at')
->paginate($request->input('per_page', 20));
return WormPhotoResource::collection($wormPhotos);
}
/**
* 监控设备直播地址
*/
public function live(int $id, BiAngDeviceService $biangDeviceService)
{
$device = Device::where('type', DeviceType::Monitor)->findOrFail($id);
switch ($device->supplier_key) {
case 'device-supplier-biang':
$client = $biangDeviceService->buildHttpClient($device);
// 直播地址
$address = $client->getMonitorPalyAddress($device->sn);
return [
'type' => 'm3u8',
'address' => (string) $address,
// 有效期 60 分钟
'expires' => 3540,
];
// 中国移动千里眼
case 'device-supplier-yidong':
$client = new QlyHttpClient(
config('services.ydqly.appid'),
config('services.ydqly.secret'),
config('services.ydqly.rsa'),
);
$result = $client->post(
'/v3/open/api/websdk/player',
[
'deviceId' => $device->sn,
],
);
$address = (string) data_get($result, 'data.url');
if (config('services.ydqly.play_proxy')) {
$address = str_replace('open.andmu.cn', 'lcyd.peidikeji.cn', $address);
}
return [
'type' => 'iframe',
'address' => $address,
'expires' => data_get($result, 'data.expiresIn'),
];
// 中国电信魔镜
case 'device-supplier-dianxin':
$address = '';
if ($channelId = data_get($device->extends, 'passage')) {
$client = new MoJingHttpClient(
config('services.dxmj.app_id'),
config('services.dxmj.app_secret'),
);
$result = $client->get(
"/api/v3/channel/RealTimeVideo/{$channelId}",
[
'transType' => 4,
'natType' => 1,
'nAudioType' => 1,
],
);
$address = data_get($result, 'data.playUrl');
}
return [
'type' => 'flv',
'address' => (string) $address,
// 有效期 29 分钟
'expires' => 1740,
];
}
// 直播地址
$address = '';
if ($rtspUrl = data_get($device->extends, 'rtsp_url')) {
$value = Setting::where('slug', 'ffmpeg_websocket_ip')->value('value');
$wsConfig = $value ? json_decode($value, true) : [];
if (! is_array($wsConfig)) {
$wsConfig = [];
}
$wsConfig = array_merge([
'host' => '',
'ip' => '127.0.0.1',
'port'=> '80',
'ssl' => false,
], $wsConfig);
if ($wsConfig['ssl'] && $wsConfig['host']) {
$address = sprintf(
'wss://%s/rtsp?url=%s',
$wsConfig['host'],
base64_encode($rtspUrl),
);
} else {
$address = sprintf(
'ws://%s:%s/rtsp?url=%s',
$wsConfig['ip'],
$wsConfig['port'],
base64_encode($rtspUrl),
);
}
}
return [
'type' => 'flv',
'address' => $address,
'expires' => 0,
];
}
}