buildHttpClient($device); } catch (BizException $e) { return []; } return $httpClient->getWormPhotos($device->sn, $start, $end); } /** * 虫情设备 - (识别款)图片虫数识别统计 */ public function getWormStatistics(Device $device, Carbon $start, Carbon $end): array { try { $httpClient = $this->buildHttpClient($device); } catch (BizException $e) { return []; } return $httpClient->getWormStatistics($device->sn, $start, $end); } /** * 创建设备报告 */ public function createReport(Device $device, Carbon $time): void { switch ($device->type) { case DeviceType::Soil: $this->createSoilReport($device, $time); break; case DeviceType::WaterQuality: $this->createWaterQualityReport($device, $time); break; case DeviceType::Meteorological: $this->createMeteorologicalReport($device, $time); break; case DeviceType::InsecticidalLamp: $this->createInsecticidalLampReport($device, $time); break; } } /** * 创建土壤设备报告 */ protected function createSoilReport(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 = $logs->reduce(function (array $attributes, DeviceLog $log) { if (is_array($data = $log->data)) { foreach ($data as $k => $v) { $attribute = match ($k) { 'soilAlkalineHydrolyzedNitrogen' => 'n', 'soilAvailablePotassium' => 'k', 'soilAvailablePhosphorus' => 'p', 'soilConductivity' => 'conductivity', 'soilTemperature1' => 'temperature', 'soilHumidity1' => 'humidity', 'soilPH' => 'ph', default => null, }; if ($attribute) { $attributes[$attribute] = $v; } } } return $attributes; }, []); $soilReport = SoilMonitoringLog::where([ 'device_id' => $device->id, 'monitored_at' => $reportedAt, ])->first(); if ($soilReport === null) { $lastSoilReport = SoilMonitoringLog::where([ 'device_id' => $device->id, 'monitored_at' => $reportedAt->copy()->subHour(), ])->first(); $soilReport = $lastSoilReport?->replicate() ?: new SoilMonitoringLog(); $soilReport->fill([ 'device_id' => $device->id, 'monitored_at' => $reportedAt, 'agricultural_base_id' => $device->agricultural_base_id, ]); } $soilReport->fill($attributes)->save(); } protected function createWaterQualityReport(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 = $logs->reduce(function (array $attributes, DeviceLog $log) { if (is_array($data = $log->data)) { foreach ($data as $k => $v) { $attribute = match ($k) { 'waterMlss' => 'turbidity', 'waterPh' => 'ph', 'waterDo' => 'oxygen', 'waterTemp' => 'temperature', 'waterNh3n' => 'nh3n', default => null, }; if ($attribute) { $attributes[$attribute] = $v; } } } return $attributes; }, []); $waterQualityReport = WaterQualityMonitoringLog::where([ 'device_id' => $device->id, 'monitored_at' => $reportedAt, ])->first(); if ($waterQualityReport === null) { $lastWaterQualityReport = WaterQualityMonitoringLog::where([ 'device_id' => $device->id, 'monitored_at' => $reportedAt->copy()->subHour(), ])->first(); $waterQualityReport = $lastWaterQualityReport?->replicate() ?: new WaterQualityMonitoringLog(); $waterQualityReport->fill([ 'device_id' => $device->id, 'monitored_at' => $reportedAt, 'agricultural_base_id' => $device->agricultural_base_id, ]); } $waterQualityReport->fill($attributes)->save(); } /** * 创建气象设备报告 */ protected function createMeteorologicalReport(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 = $logs->reduce(function (array $attributes, DeviceLog $log) { if (is_array($data = $log->data)) { foreach ($data as $k => $v) { $attribute = match ($k) { 'rainfall' => 'moment_rainfall', //瞬时降雨量 'lightIntensity' => 'illumination', 'airTemperature' => 'air_temperature', 'airHumidity' => 'air_humidity', 'windDirection' => 'wind_degree', 'windSpeed' => 'wind_speed', default => null, }; if ($attribute) { switch ($attribute) { case 'moment_rainfall': $attributes[$attribute] = bcadd($attributes[$attribute] ?? '0.00', $v, 2); break; case 'wind_degree': $attributes['wind_direction'] = value(function ($v) { if ($v >= 22.5 && $v < 67.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_NORTHEAST; } elseif ($v >= 67.5 && $v < 112.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_EAST; } elseif ($v >= 112.5 && $v < 157.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_SOUTHEAST; } elseif ($v >= 157.5 && $v < 202.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_SOUTH; } elseif ($v >= 202.5 && $v < 247.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_SOUTHWEST; } elseif ($v >= 247.5 && $v < 292.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_WEST; } elseif ($v >= 292.5 && $v < 337.5) { return MeteorologicalMonitoringLog::WIND_DIRECTION_NORTHWEST; } return MeteorologicalMonitoringLog::WIND_DIRECTION_NORTH; }, $v); default: $attributes[$attribute] = $v; break; } } } } return $attributes; }, []); # 累计雨量求和 $attributes['current_rainfall'] = DeviceLog::select(DB::raw("SUM((data->>'rainfall')::NUMERIC) as aggregate"))->where('device_id', $device->id) ->whereBetween('reported_at', [$reportedAt->copy()->startOfDay(), $reportedAt->copy()->endOfHour()]) ->value('aggregate') ?: 0; $meteorologicalReport = MeteorologicalMonitoringLog::where([ 'device_id' => $device->id, 'monitored_at' => $reportedAt, ])->first(); if ($meteorologicalReport === null) { $lastMeteorologicalReport = MeteorologicalMonitoringLog::where([ 'device_id' => $device->id, 'monitored_at' => $reportedAt->copy()->subHour(), ])->first(); $meteorologicalReport = $lastMeteorologicalReport?->replicate() ?: new MeteorologicalMonitoringLog(); $meteorologicalReport->fill([ 'device_id' => $device->id, 'monitored_at' => $reportedAt, 'agricultural_base_id' => $device->agricultural_base_id, ]); } $meteorologicalReport->fill($attributes)->save(); } /** * 创建杀虫灯设备报告 */ 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 = [ 'vol' => ['sum' => 0, 'count' => 0], 'sunVol' => ['sum' => 0, 'count' => 0], 'dct' => ['sum' => 0, 'count' => 0], 'temp' => ['sum' => 0, 'count' => 0], 'humidity' => ['sum' => 0, 'count' => 0], 'highVol' => ['sum' => 0, 'count' => 0], ]; /** @var \App\Models\DeviceLog */ foreach ($logs as $log) { if (! is_array($log->data)) { continue; } foreach ($data as $k => $item) { $v = $log->data[$k] ?? null; if (is_null($v)) { continue; } $item['sum'] = bcadd($item['sum'], $v, 2); $item['count']++; $data[$k] = $item; } } $attributes = []; foreach ($data as $key => $item) { $attribute = match ($key) { 'vol' => 'battery_vol', 'sunVol' => 'charging_vol', 'dct' => 'killed_num', 'temp' => 'air_temperature', 'humidity' => 'air_humidity', 'highVol' => 'high_vol', }; if ($item['count'] > 0) { if ($attribute === 'killed_num') { $attributes[$attribute] = (int) $item['sum']; } else { $attributes[$attribute] = round(bcdiv($item['sum'], $item['count'], 2), 2); } } else { $attributes[$attribute] = 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::Meteorological: $this->createMeteorologicalDailyReport($device, $time); break; case DeviceType::Soil: $this->createSoilDailyReport($device, $time); break; case DeviceType::WaterQuality: $this->createWaterQualityDailyReport($device, $time); break; case DeviceType::InsecticidalLamp: $this->createInsecticidalLampDailyReport($device, $time); break; } } /** * 创建土壤设备每日报告 */ protected function createSoilDailyReport(Device $device, Carbon $date): void { /** @var \Illuminate\Database\Eloquent\Collection */ $soilReports = SoilMonitoringLog::where('device_id', $device->id) ->whereDate('monitored_at', $date) ->oldest('monitored_at') ->get(); if ($soilReports->isEmpty()) { return; } $attributes = value(function ($soilReports) { $data = [ 'n' => ['sum' => 0, 'count' => 0], 'p' => ['sum' => 0, 'count' => 0], 'k' => ['sum' => 0, 'count' => 0], 'conductivity' => ['sum' => 0, 'count' => 0], 'temperature' => ['sum' => 0, 'count' => 0], 'humidity' => ['sum' => 0, 'count' => 0], 'moisture' => ['sum' => 0, 'count' => 0], 'ph' => ['sum' => 0, 'count' => 0], ]; foreach ($soilReports as $soilReport) { foreach ($data as $k => $item) { if (is_null($v = $soilReport->{$k})) { continue; } $item['sum'] = bcadd($item['sum'], $v, 2); $item['count']++; $data[$k] = $item; } } $attributes = []; foreach ($data as $key => $item) { $attributes[$key] = $item['count'] > 0 ? round(bcdiv($item['sum'], $item['count'], 2), 2) : null; } return $attributes; }, $soilReports); /** @var \App\Models\SoilDailyReport */ $soilDailyReport = SoilMonitoringDailyLog::firstOrNew([ 'device_id' => $device->id, 'monitored_at' => $date->format('Y-m-d'), ], [ 'agricultural_base_id' => $device->agricultural_base_id, ]); $soilDailyReport->fill($attributes)->save(); } /** * 创建水质设备每日报告 */ protected function createWaterQualityDailyReport(Device $device, Carbon $date): void { /** @var \Illuminate\Database\Eloquent\Collection */ $waterQualityReports = WaterQualityMonitoringLog::where('device_id', $device->id) ->whereDate('monitored_at', $date) ->oldest('monitored_at') ->get(); if ($waterQualityReports->isEmpty()) { return; } $attributes = value(function ($waterQualityReports) { $data = [ 'chlorine' => ['sum' => 0, 'count' => 0], 'conductivity' => ['sum' => 0, 'count' => 0], 'oxygen' => ['sum' => 0, 'count' => 0], 'ph' => ['sum' => 0, 'count' => 0], 'temperature' => ['sum' => 0, 'count' => 0], 'turbidity' => ['sum' => 0, 'count' => 0], 'nh3n' => ['sum' => 0, 'count' => 0], ]; foreach ($waterQualityReports as $waterQualityReport) { foreach ($data as $k => $item) { if (is_null($v = $waterQualityReport->{$k})) { continue; } $item['sum'] = bcadd($item['sum'], $v, 2); $item['count']++; $data[$k] = $item; } } $attributes = []; foreach ($data as $key => $item) { $attributes[$key] = $item['count'] > 0 ? round(bcdiv($item['sum'], $item['count'], 2), 2) : null; } return $attributes; }, $waterQualityReports); /** @var \App\Models\WaterQualityMonitoringDailyLog */ $waterQualityDailyReport = WaterQualityMonitoringDailyLog::firstOrNew([ 'device_id' => $device->id, 'monitored_at' => $date->format('Y-m-d'), ], [ 'agricultural_base_id' => $device->agricultural_base_id, ]); $waterQualityDailyReport->fill($attributes)->save(); } /** * 创建气象设备每日报告 */ protected function createMeteorologicalDailyReport(Device $device, Carbon $date): void { /** @var \Illuminate\Database\Eloquent\Collection */ $meteorologicalReports = MeteorologicalMonitoringLog::where('device_id', $device->id) ->whereDate('monitored_at', $date) ->oldest('monitored_at') ->get(); if ($meteorologicalReports->isEmpty()) { return; } $attributes = value(function ($meteorologicalReports) { $data = [ 'current_rainfall' => 0, 'illumination' => ['sum' => 0, 'count' => 0], 'air_temperature' => ['sum' => 0, 'count' => 0], 'air_humidity' => ['sum' => 0, 'count' => 0], 'wind_speed' => ['sum' => 0, 'count' => 0], 'wind_samples' => [], ]; foreach ($meteorologicalReports as $meteorologicalReport) { foreach ($data as $k => $item) { if ($k === 'wind_samples') { if (is_null($meteorologicalReport->wind_degree) || is_null($meteorologicalReport->wind_speed)) { continue; } $item[] = [ 'wind_degree' => $meteorologicalReport->wind_degree, // 风向度数 'wind_speed' => $meteorologicalReport->wind_speed, // 风速 ]; } elseif (! is_null($v = $meteorologicalReport->{$k})) { if ($k === 'current_rainfall') { $item = $v; } else { $item['sum'] = bcadd($item['sum'], $v, 2); $item['count']++; } } $data[$k] = $item; } } $attributes = []; foreach ($data as $key => $item) { switch ($key) { case 'current_rainfall': $attributes['daily_rainfall'] = $item; break; case 'wind_samples': if (! empty($item)) { $attributes['wind_degree'] = value(function (array $windSamples) { if (empty($windSamples)) { return null; } $x = 0; $y = 0; foreach ($windSamples as $sample) { if ($sample['wind_degree'] == 0 && $sample['wind_speed'] == 0) { continue; } // 角度转弧度 $radian = deg2rad($sample['wind_degree']); // $x += $sample['wind_speed'] * sin($radian); // $y += $sample['wind_speed'] * cos($radian); $x += sin($radian); $y += cos($radian); } $degree = round(rad2deg(atan2($y, $x))); if (($x > 0 || $x < 0) && $y < 0) { $degree += 180; } elseif ($x < 0 && $y > 0) { $degree += 360; } return $degree; }, $item); $attributes['wind_direction'] = value(function ($windDegree) { if (is_null($windDegree)) { return null; } if ($windDegree >= 22.5 && $windDegree < 67.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_NORTHEAST; } elseif ($windDegree >= 67.5 && $windDegree < 112.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_EAST; } elseif ($windDegree >= 112.5 && $windDegree < 157.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_SOUTHEAST; } elseif ($windDegree >= 157.5 && $windDegree < 202.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_SOUTH; } elseif ($windDegree >= 202.5 && $windDegree < 247.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_SOUTHWEST; } elseif ($windDegree >= 247.5 && $windDegree < 292.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_WEST; } elseif ($windDegree >= 292.5 && $windDegree < 337.5) { return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_NORTHWEST; } return MeteorologicalMonitoringDailyLog::WIND_DIRECTION_NORTH; }, $attributes['wind_degree']); } break; default: $attributes[$key] = $item['count'] > 0 ? round(bcdiv($item['sum'], $item['count'], 2), 2) : null; break; } } return $attributes; }, $meteorologicalReports); /** @var \App\Models\MeteorologicalMonitoringDailyLog */ $meteorologicalDailyReport = MeteorologicalMonitoringDailyLog::firstOrNew([ 'device_id' => $device->id, 'monitored_at' => $date, ], [ 'agricultural_base_id' => $device->agricultural_base_id, ]); $meteorologicalDailyReport->fill($attributes)->save(); } /** * 杀虫灯每日报告 */ 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 = [ 'killed_num' => ['sum' => 0, 'count' => 0], 'battery_vol' => ['sum' => 0, 'count' => 0], 'air_temperature' => ['sum' => 0, 'count' => 0], 'air_humidity' => ['sum' => 0, 'count' => 0], 'charging_vol' => ['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(); } /** * 创建 HTTP 客户端 */ public function buildHttpClient(Device $device): HttpClient { $config = json_decode($device->project?->value, true); if (! is_array($config)) { throw new BizException('账户信息未找到'); } return new HttpClient($config['username'] ?? '', $config['password'] ?? ''); } }