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; } } 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; } }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] = 0.33; 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' => 0.33, 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', ]; /** @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', 'turbidity', ]; 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)) { // 如果是水质设备,则写死假数据 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); $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, ]; } }