factory?->key) { case 'link-os': $this->syncLinkosDeviceLogs($device, $start, $end); break; } } /** * 同步 Linkos 设备历史流水 */ protected function syncLinkosDeviceLogs(Device $device, Carbon $start, Carbon $end): void { /** @var \App\Iot\Linkos\HttpClient */ $httpClient = app(LinkosHttpClient::class); $page = 1; $perPage = 50; do { $data = $httpClient->getDeviceFlowList( $device->sn, $start, $end, $page, $perPage ); $countResults = count($data['content']); if ($countResults === 0) { break; } foreach ($data['content'] as $item) { if (! isset($item['data'])) { continue; } if ($device->isTypeSoil()) { // 如果包含气象设备监测字段,则跳过 if (Arr::hasAny($item['data'], [ 'day_rainfall', 'accumulate_rainfall', 'potassium_content', 'moment_rainfall', 'pm10_concentration', 'pm25_concentration', 'box_noise', 'box_carbon', 'box_humidity', 'box_pressure', 'box_temperature', 'box_illumination', 'wind_power', 'wind_speed', 'wind_degree', 'wind_direction', ])) { continue; } } elseif ($device->isTypeMeteorological()) { // 如果包含土壤设备监测字段,则跳过 if (Arr::hasAny($item['data'], [ 'nitrogen_content', 'phosphorus_content', 'potassium_content', 'conductivity', 'soil_humidity', 'soil_temperature', ])) { continue; } } if ( $device->isTypeSoil() && Arr::hasAny($item['data'], [ ]) ) { } elseif ( $device->isTypeMeteorological() && Arr::hasAny( $item['data'], [ 'nitrogen_content', 'phosphorus_content', 'potassium_content', 'conductivity', 'soil_humidity', 'soil_temperature', ] ) ) { continue; } $isSoilMonitoring = Arr::hasAny($item['data'], [ 'soil_humidity', 'soil_temperature', 'nitrogen_content', 'potassium_content', 'phosphorus_content', ]); if (($device->isTypeSoil() && ! $isSoilMonitoring) || (! $device->isTypeSoil() && $isSoilMonitoring)) { continue; } $device->logs()->firstOrCreate([ 'reported_at' => $item['createTime'], ], [ 'data' => empty($item['data']) ? (new \stdClass) : $item['data'], ]); } unset($data); $page++; } while ($countResults === $perPage); } /** * 创建 linkos 设备报告 */ public function createReportToLinkosDevice(Device $device, Carbon $time): void { switch ($device->type) { case Device::TYPE_SOIL: $this->createReportToLinkosSoilDevice($device, $time); break; case Device::TYPE_METEOROLOGICAL: $this->createReportToLinkosMeteorologicalDevice($device, $time); break; case Device::TYPE_WATER_QUALITY: $this->createReportToLinkosWaterQualityDevice($device, $time); break; } } /** * 创建 linkos 土壤设备报告 */ protected function createReportToLinkosSoilDevice(Device $device, Carbon $time): void { $reportedAt = $time->copy()->startOfHour(); /** @var \Illuminate\Database\Eloquent\Collection */ $logs = $device->logs() ->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) { 'nitrogen_content' => 'n', 'potassium_content' => 'k', 'phosphorus_content' => 'p', 'electroconductibility' => 'conductivity', 'temperature' => 'temperature', 'moisture_content' => 'moisture', 'conductivity' => 'conductivity', 'soil_humidity' => 'humidity', 'soil_temperature' => 'temperature', default => null, }; if ($attribute) { $attributes[$attribute] = $v; } } } return $attributes; }, []); $soilReport = SoilReport::where([ 'device_id' => $device->id, 'reported_at' => $reportedAt, ])->first(); if ($soilReport === null) { $lastSoilReport = SoilReport::where([ 'device_id' => $device->id, 'reported_at' => $reportedAt->copy()->subHour(), ])->first(); $soilReport = $lastSoilReport?->replicate() ?: new SoilReport(); $soilReport->fill([ 'device_id' => $device->id, 'reported_at' => $reportedAt, ]); } $soilReport->fill($attributes)->save(); (new DeviceWarningService())->judgeLog($device, $soilReport, $time); } /** * 创建 linkos 气象设备报告 */ protected function createReportToLinkosMeteorologicalDevice(Device $device, Carbon $time): void { $reportedAt = $time->copy()->startOfHour(); /** @var \Illuminate\Database\Eloquent\Collection */ $logs = $device->logs() ->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) { 'current_rainfall' => 'today_rainfall', 'day_rainfall' => 'yesterday_rainfall', 'accumulate_rainfall' => 'accumulate_rainfall', 'moment_rainfall' => 'moment_rainfall', 'pm10_concentration' => 'pm10', 'pm25_concentration' => 'pm25', 'box_illumination' => 'box_illumination', 'box_pressure' => 'box_pressure', 'box_carbon' => 'box_co2', 'box_temperature' => 'box_temperature', 'box_humidity' => 'box_humidity', 'box_noise' => 'box_noise', 'wind_degree' => 'wind_degree', 'wind_direction' => 'wind_direction', 'wind_power' => 'wind_power', 'wind_speed' => 'wind_speed', default => null, }; if ($attribute) { $attributes[$attribute] = $v; } } } return $attributes; }, []); $meteorologicalReport = MeteorologicalReport::where([ 'device_id' => $device->id, 'reported_at' => $reportedAt, ])->first(); if ($meteorologicalReport === null) { $lastMeteorologicalReport = MeteorologicalReport::where([ 'device_id' => $device->id, 'reported_at' => $reportedAt->copy()->subHour(), ])->first(); $meteorologicalReport = $lastMeteorologicalReport?->replicate() ?: new MeteorologicalReport(); $meteorologicalReport->fill([ 'device_id' => $device->id, 'reported_at' => $reportedAt, ]); } $meteorologicalReport->fill($attributes)->save(); (new DeviceWarningService())->judgeLog($device, $meteorologicalReport, $time); } /** * 创建 linkos 水质设备报告 */ protected function createReportToLinkosWaterQualityDevice(Device $device, Carbon $time): void { $reportedAt = $time->copy()->startOfHour(); /** @var \Illuminate\Database\Eloquent\Collection */ $logs = $device->logs() ->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) { 'chlorine' => 'chlorine', 'conductivity' => 'conductivity', 'oxygen' => 'oxygen', 'ph' => 'ph', 'temp' => 'temperature', 'turbidity' => 'turbidity', default => null, }; if ($attribute) { $attributes[$attribute] = $v; } } } return $attributes; }, []); $waterQualityReport = WaterQualityReport::where([ 'device_id' => $device->id, 'reported_at' => $reportedAt, ])->first(); if ($waterQualityReport === null) { $lastWaterQualityReport = WaterQualityReport::where([ 'device_id' => $device->id, 'reported_at' => $reportedAt->copy()->subHour(), ])->first(); $waterQualityReport = $lastWaterQualityReport?->replicate() ?: new WaterQualityReport(); $waterQualityReport->fill([ 'device_id' => $device->id, 'reported_at' => $reportedAt, ]); } $waterQualityReport->fill($attributes)->save(); (new DeviceWarningService())->judgeLog($device, $waterQualityReport, $time); } /** * 创建 linkos 设备每日报告 */ public function createDailyReportToLinkosDevice(Device $device, Carbon $time): void { switch ($device->type) { case Device::TYPE_METEOROLOGICAL: $this->createDailyReportToLinkosMeteorologicalDevice($device, $time); break; case Device::TYPE_WATER_QUALITY: $this->createDailyReportToLinkosWaterQualityDevice($device, $time); break; case Device::TYPE_SOIL: $this->createDailyReportToLinkosSoilDevice($device, $time); break; } } /** * 创建 linkos 土壤设备每日报告 */ protected function createDailyReportToLinkosSoilDevice(Device $device, Carbon $date): void { /** @var \Illuminate\Database\Eloquent\Collection */ $soilReports = SoilReport::where('device_id', $device->id) ->whereDate('reported_at', $date) ->oldest('reported_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], ]; 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 = SoilDailyReport::firstOrNew([ 'device_id' => $device->id, 'reported_at' => $date->format('Y-m-d'), ]); $soilDailyReport->fill($attributes)->save(); } /** * 创建 linkos 气象设备每日报告 */ protected function createDailyReportToLinkosMeteorologicalDevice(Device $device, Carbon $date): void { /** @var \Illuminate\Database\Eloquent\Collection */ $meteorologicalReports = MeteorologicalReport::where('device_id', $device->id) ->whereDate('reported_at', $date) ->oldest('reported_at') ->get(); if ($meteorologicalReports->isEmpty()) { return; } $attributes = value(function ($meteorologicalReports) { $data = [ 'today_rainfall' => ['sum' => 0, 'count' => 0], 'yesterday_rainfall' => ['sum' => 0, 'count' => 0], 'accumulate_rainfall' => ['sum' => 0, 'count' => 0], 'moment_rainfall' => ['sum' => 0, 'count' => 0], 'pm10' => ['sum' => 0, 'count' => 0], 'pm25' => ['sum' => 0, 'count' => 0], 'box_illumination' => ['sum' => 0, 'count' => 0], 'box_pressure' => ['sum' => 0, 'count' => 0], 'box_co2' => ['sum' => 0, 'count' => 0], 'box_temperature' => ['sum' => 0, 'count' => 0], 'box_humidity' => ['sum' => 0, 'count' => 0], 'box_noise' => ['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})) { $item['sum'] = bcadd($item['sum'], $v, 2); $item['count']++; } $data[$k] = $item; } } $attributes = []; foreach ($data as $key => $item) { switch ($key) { case 'wind_samples': $attributes['wind_degree'] = value(function (array $windSamples) { $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 ($windDegree >= 22.5 && $windDegree < 67.5) { return MeteorologicalDailyReport::WIND_DIRECTION_NORTHEAST; } elseif ($windDegree >= 67.5 && $windDegree < 112.5) { return MeteorologicalDailyReport::WIND_DIRECTION_EAST; } elseif ($windDegree >= 112.5 && $windDegree < 157.5) { return MeteorologicalDailyReport::WIND_DIRECTION_SOUTHEAST; } elseif ($windDegree >= 157.5 && $windDegree < 202.5) { return MeteorologicalDailyReport::WIND_DIRECTION_SOUTH; } elseif ($windDegree >= 202.5 && $windDegree < 247.5) { return MeteorologicalDailyReport::WIND_DIRECTION_SOUTHWEST; } elseif ($windDegree >= 247.5 && $windDegree < 292.5) { return MeteorologicalDailyReport::WIND_DIRECTION_WEST; } elseif ($windDegree >= 292.5 && $windDegree < 337.5) { return MeteorologicalDailyReport::WIND_DIRECTION_NORTHWEST; } return MeteorologicalDailyReport::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\MeteorologicalDailyReport */ $meteorologicalDailyReport = MeteorologicalDailyReport::firstOrNew([ 'device_id' => $device->id, 'reported_at' => $date, ]); $meteorologicalDailyReport->fill($attributes)->save(); } /** * 创建 linkos 水质设备每日报告 */ protected function createDailyReportToLinkosWaterQualityDevice(Device $device, Carbon $date): void { /** @var \Illuminate\Database\Eloquent\Collection */ $waterQualityReports = WaterQualityReport::where('device_id', $device->id) ->whereDate('reported_at', $date) ->oldest('reported_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], ]; 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\WaterQualityDailyReport */ $waterQualityDailyReport = WaterQualityDailyReport::firstOrNew([ 'device_id' => $device->id, 'reported_at' => $date->format('Y-m-d'), ]); $waterQualityDailyReport->fill($attributes)->save(); } }