diff --git a/app/Console/Commands/DeviceLogDailyReportCommand.php b/app/Console/Commands/DeviceLogDailyReportCommand.php new file mode 100644 index 0000000..5c6c2f4 --- /dev/null +++ b/app/Console/Commands/DeviceLogDailyReportCommand.php @@ -0,0 +1,128 @@ +deviceLogService = $deviceLogService; + + $factory = $this->argument('factory'); + + $sleep = (int) value(fn ($sleep) => is_numeric($sleep) ? $sleep : 300, $this->option('sleep')); + + while (true) { + /** @var \Illuminate\Database\Eloquent\Collection */ + $devices = Device::with(['factory'])->poweredBy($factory)->get(); + + foreach ($devices as $device) { + switch ($device->factory?->key) { + case 'link-os': + switch ($device->type) { + case Device::TYPE_METEOROLOGICAL: + $this->createReportToLinkosMeteorologicalDevice($device); + break; + + case Device::TYPE_WATER_QUALITY: + $this->createReportToLinkosWaterQualityDevice($device); + break; + } + break; + } + } + + sleep($sleep); + }; + } + + /** + * 创建 linkos 气象设备每日报告 + */ + protected function createReportToLinkosMeteorologicalDevice(Device $device): void + { + $lastReportedAt = MeteorologicalDailyReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at') + ?: MeteorologicalReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at'); + + if ($lastReportedAt === null) { + return; + } + + if (is_null($latestReportedAt = MeteorologicalReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at'))) { + return; + } + + /** @var \Carbon\Carbon */ + $startAt = $lastReportedAt->copy()->startOfDay(); + /** @var \Carbon\Carbon */ + $endAt = $latestReportedAt->copy()->startOfDay(); + + do { + $this->deviceLogService->createDailyReportToLinkosMeteorologicalDevice($device, $startAt->copy()); + + $startAt->addDay(); + } while ($endAt->gte($startAt)); + } + + /** + * 创建 linkos 水质设备每日报告 + */ + protected function createReportToLinkosWaterQualityDevice(Device $device): void + { + $lastReportedAt = WaterQualityDailyReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at') + ?: WaterQualityReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at'); + + if ($lastReportedAt === null) { + return; + } + + if (is_null($latestReportedAt = WaterQualityReport::where('device_id', $device->id)->latest('reported_at')->value('reported_at'))) { + return; + } + + /** @var \Carbon\Carbon */ + $startAt = $lastReportedAt->copy()->startOfDay(); + /** @var \Carbon\Carbon */ + $endAt = $latestReportedAt->copy()->startOfDay(); + + do { + $this->deviceLogService->createDailyReportToLinkosWaterQualityDevice($device, $startAt->copy()); + + $startAt->addDay(); + } while ($endAt->gte($startAt)); + } +} diff --git a/app/Models/MeteorologicalDailyReport.php b/app/Models/MeteorologicalDailyReport.php new file mode 100644 index 0000000..07b8ad1 --- /dev/null +++ b/app/Models/MeteorologicalDailyReport.php @@ -0,0 +1,45 @@ + 'date' + ]; + + protected $fillable = [ + 'device_id', + 'today_rainfall', + 'yesterday_rainfall', + 'accumulate_rainfall', + 'moment_rainfall', + 'pm10', + 'pm25', + 'box_illumination', + 'box_pressure', + 'box_co2', + 'box_temperature', + 'box_humidity', + 'box_noise', + 'wind_degree', + 'wind_direction', + 'wind_power', + 'wind_speed', + 'reported_at', + ]; +} diff --git a/app/Models/WaterQualityDailyReport.php b/app/Models/WaterQualityDailyReport.php new file mode 100644 index 0000000..edaf742 --- /dev/null +++ b/app/Models/WaterQualityDailyReport.php @@ -0,0 +1,26 @@ + 'date' + ]; + + protected $fillable = [ + 'device_id', + 'chlorine', + 'conductivity', + 'oxygen', + 'ph', + 'temperature', + 'turbidity', + 'reported_at', + ]; +} diff --git a/app/Services/DeviceLogService.php b/app/Services/DeviceLogService.php index 713c822..72cd7e7 100644 --- a/app/Services/DeviceLogService.php +++ b/app/Services/DeviceLogService.php @@ -4,6 +4,10 @@ namespace App\Services; use App\Iot\Linkos\HttpClient as LinkosHttpClient; use App\Models\Device; +use App\Models\MeteorologicalDailyReport; +use App\Models\MeteorologicalReport; +use App\Models\WaterQualityDailyReport; +use App\Models\WaterQualityReport; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; @@ -73,4 +77,182 @@ class DeviceLogService $page++; } while ($countResults === $perPage); } + + /** + * 创建 linkos 气象设备每日报告 + */ + public 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; + } + + $result = $meteorologicalReports->reduce(function (array $carry, MeteorologicalReport $meteorologicalReport) { + foreach ($carry as $key => $item) { + if (is_null($v = $meteorologicalReport->{$key})) { + continue; + } + + switch ($key) { + case 'wind_samples': + if (! is_null($meteorologicalReport->wind_degree) && ! is_null($meteorologicalReport->wind_speed)) { + $item['wind_samples'][] = [ + 'wind_degree' => $meteorologicalReport->wind_degree, // 风向度数 + 'wind_speed' => $meteorologicalReport->wind_speed, // 风速 + ]; + } + break; + + default: + $item['sum'] = bcadd($item['sum'], $v, 2); + $item['count']++; + break; + } + + $carry[$key] = $item; + } + + return $carry; + }, [ + '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' => [], + ]); + + $meteorologicalDailyReport = MeteorologicalDailyReport::firstOrCreate([ + 'device_id' => $device->id, + 'reported_at' => $date, + ]); + + foreach ($result as $key => $item) { + switch ($key) { + case 'wind_samples': + $meteorologicalDailyReport->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); + + $meteorologicalDailyReport->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; + }, $meteorologicalDailyReport->wind_degree); + + break; + + default: + if ($item['count'] > 0) { + $meteorologicalDailyReport->{$key} = round(bcdiv($item['sum'], $item['count'], 2), 2); + } + break; + } + } + + $meteorologicalDailyReport->save(); + } + + /** + * 创建 linkos 水质设备每日报告 + */ + public 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; + } + + $result = $waterQualityReports->reduce(function (array $carry, WaterQualityReport $waterQualityReport) { + foreach ($carry as $key => $item) { + if (is_null($v = $waterQualityReport->{$key})) { + continue; + } + + $item['sum'] = bcadd($item['sum'], $v, 2); + $item['count']++; + + $carry[$key] = $item; + } + + return $carry; + }, [ + '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], + ]); + + $waterQualityDailyReport = WaterQualityDailyReport::firstOrCreate([ + 'device_id' => $device->id, + 'reported_at' => $date->format('Y-m-d'), + ]); + + foreach ($result as $key => $item) { + if ($item['count'] > 0) { + $waterQualityDailyReport->{$key} = round(bcdiv($item['sum'], $item['count'], 2), 2); + } + } + + $waterQualityDailyReport->save(); + } } diff --git a/database/migrations/2023_05_08_141249_create_water_quality_daily_reports_table.php b/database/migrations/2023_05_08_141249_create_water_quality_daily_reports_table.php new file mode 100644 index 0000000..e148e34 --- /dev/null +++ b/database/migrations/2023_05_08_141249_create_water_quality_daily_reports_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('device_id')->comment('设备ID'); + $table->decimal('chlorine', 8, 2)->nullable()->comment('余氯(单位: mg/L)'); + $table->decimal('conductivity', 8, 2)->nullable()->comment('电导率(单位: us/cm)'); + $table->decimal('oxygen', 8, 2)->nullable()->comment('溶解氧(单位: mg/L)'); + $table->decimal('ph', 8, 2)->nullable()->comment('PH'); + $table->decimal('temperature', 8, 2)->nullable()->comment('温度(单位: ℃)'); + $table->decimal('turbidity', 8, 2)->nullable()->comment('浊度(单位: NTU)'); + $table->date('reported_at')->comment('报告日期'); + $table->timestamps(); + + $table->unique(['device_id', 'reported_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('water_quality_daily_reports'); + } +}; diff --git a/database/migrations/2023_05_08_141302_create_meteorological_daily_reports_table.php b/database/migrations/2023_05_08_141302_create_meteorological_daily_reports_table.php new file mode 100644 index 0000000..2dfd58d --- /dev/null +++ b/database/migrations/2023_05_08_141302_create_meteorological_daily_reports_table.php @@ -0,0 +1,51 @@ +id(); + $table->unsignedBigInteger('device_id'); + $table->decimal('today_rainfall', 8, 2)->nullable()->comment('今日积雨量 (单位: mm)'); + $table->decimal('yesterday_rainfall', 8, 2)->nullable()->comment('昨日积雨量 (单位: mm)'); + $table->decimal('accumulate_rainfall', 8, 2)->nullable()->comment('累积雨量 (单位: mm)'); + $table->decimal('moment_rainfall', 8, 2)->nullable()->comment('瞬时雨量 (单位: mm)'); + $table->decimal('pm10', 8, 2)->nullable()->comment('PM10浓度 (单位: ug/m3)'); + $table->decimal('pm25', 8, 2)->nullable()->comment('PM2.5浓度 (单位: ug/m3)'); + $table->decimal('box_illumination', 10, 2)->nullable()->comment('百叶箱光照度 (单位: Lux)'); + $table->decimal('box_pressure', 8, 2)->nullable()->comment('百叶箱大气压力 (单位: Kpa)'); + $table->decimal('box_co2', 8, 2)->nullable()->comment('百叶箱CO2浓度 (单位: ppm)'); + $table->decimal('box_temperature', 8, 2)->nullable()->comment('百叶箱温度 (单位: ℃)'); + $table->decimal('box_humidity', 8, 2)->nullable()->comment('百叶箱湿度 (单位: %RH)'); + $table->decimal('box_noise', 8, 2)->nullable()->comment('百叶箱噪声 (单位: db)'); + $table->integer('wind_degree')->nullable()->comment('风向度数'); + $table->tinyInteger('wind_direction')->nullable()->comment('风向: 0 北风, 1 东北风, 2 东风, 3 东南风, 4 南风, 5 西南风, 6 西风, 7 西北风'); + $table->integer('wind_power')->nullable()->comment('风力 (单位: 级)'); + $table->decimal('wind_speed', 8, 2)->nullable()->comment('风速 (单位: m/s)'); + $table->date('reported_at')->comment('报告日期'); + $table->timestamps(); + + $table->unique(['device_id', 'reported_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('meteorological_daily_reports'); + } +};