diff --git a/app/Console/Commands/DeviceLogDailyReportCommand.php b/app/Console/Commands/DeviceLogDailyReportCommand.php index 9454dcb..4e2feef 100644 --- a/app/Console/Commands/DeviceLogDailyReportCommand.php +++ b/app/Console/Commands/DeviceLogDailyReportCommand.php @@ -10,6 +10,7 @@ use App\Models\SoilMonitoringDailyLog; use App\Models\SoilMonitoringLog; use App\Models\WaterQualityMonitoringDailyLog; use App\Models\WaterQualityMonitoringLog; +use App\Services\BiAngDeviceLogService; use App\Services\DeviceLogService; use Illuminate\Console\Command; @@ -39,22 +40,20 @@ class DeviceLogDailyReportCommand extends Command /** * Execute the console command. */ - public function handle(DeviceLogService $deviceLogService) + public function handle() { - $this->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(['supplier'])->supplierBy($factory)->get(); + $devices = Device::with(['supplier'])->supplierBy("device-supplier-{$factory}")->get(); foreach ($devices as $device) { switch ($device->supplier?->key) { - case 'linkos': - $this->createReportToLinkosDevice($device); + case 'device-supplier-biang': + $this->createReportToBiAngDevice($device); break; } } @@ -66,7 +65,7 @@ class DeviceLogDailyReportCommand extends Command /** * 创建 linkos 设备报告 */ - protected function createReportToLinkosDevice(Device $device): void + protected function createReportToBiAngDevice(Device $device): void { [$lastReportedAt, $latestReportedAt] = value(function (Device $device) { $lastReportedAt = null; @@ -74,22 +73,6 @@ class DeviceLogDailyReportCommand extends Command $latestReportedAt = null; switch ($device->type) { - case DeviceType::WaterQuality: - $lastReportedAt = WaterQualityMonitoringDailyLog::where('device_id', $device->id) - ->latest('monitored_at') - ->value('monitored_at'); - - $lastReportedAt ??= WaterQualityMonitoringLog::where('device_id', $device->id) - ->oldest('monitored_at') - ->value('monitored_at'); - - if ($lastReportedAt) { - $latestReportedAt = WaterQualityMonitoringLog::where('device_id', $device->id) - ->latest('monitored_at') - ->value('monitored_at'); - } - break; - case DeviceType::Meteorological: $lastReportedAt = MeteorologicalMonitoringDailyLog::where('device_id', $device->id) ->latest('monitored_at') @@ -130,11 +113,13 @@ class DeviceLogDailyReportCommand extends Command return; } + $service = new BiAngDeviceLogService(); + /** @var \Carbon\Carbon */ $startAt = $lastReportedAt->copy()->startOfDay(); do { - $this->deviceLogService->createDailyReportToLinkosDevice($device, $startAt->copy()); + $service->createDailyReport($device, $startAt->copy()); $startAt->addDay(); } while ($latestReportedAt->gte($startAt)); diff --git a/app/Console/Commands/DeviceLogReportCommand.php b/app/Console/Commands/DeviceLogReportCommand.php index 0a54032..bc8fbe5 100644 --- a/app/Console/Commands/DeviceLogReportCommand.php +++ b/app/Console/Commands/DeviceLogReportCommand.php @@ -4,11 +4,10 @@ namespace App\Console\Commands; use App\Enums\DeviceType; use App\Models\Device; -use App\Models\LinkosDeviceLog; +use App\Models\DeviceLog; use App\Models\MeteorologicalMonitoringLog; use App\Models\SoilMonitoringLog; -use App\Models\WaterQualityMonitoringLog; -use App\Services\DeviceLogService; +use App\Services\BiAngDeviceLogService; use Illuminate\Console\Command; class DeviceLogReportCommand extends Command @@ -37,22 +36,20 @@ class DeviceLogReportCommand extends Command /** * Execute the console command. */ - public function handle(DeviceLogService $deviceLogService) + public function handle() { - $this->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(['supplier'])->supplierBy($factory)->get(); + $devices = Device::with(['supplier'])->supplierBy("device-supplier-{$factory}")->get(); foreach ($devices as $device) { switch ($device->supplier?->key) { - case 'linkos': - $this->createReportToLinkosDevice($device); + case 'device-supplier-biang': + $this->createReportToBiAngDevice($device); break; } } @@ -62,30 +59,31 @@ class DeviceLogReportCommand extends Command } /** - * 创建 linkos 设备报告 + * 创建比昂设备报告 */ - protected function createReportToLinkosDevice(Device $device): void + protected function createReportToBiAngDevice(Device $device): void { $lastReportedAt = match ($device->type) { DeviceType::Soil => SoilMonitoringLog::where('device_id', $device->id)->latest('monitored_at')->value('monitored_at'), - DeviceType::WaterQuality => WaterQualityMonitoringLog::where('device_id', $device->id)->latest('monitored_at')->value('monitored_at'), DeviceType::Meteorological => MeteorologicalMonitoringLog::where('device_id', $device->id)->latest('monitored_at')->value('monitored_at'), default => null, }; - if (is_null($lastReportedAt ??= LinkosDeviceLog::where('device_id', $device->sn)->oldest('reported_at')->value('reported_at'))) { + if (is_null($lastReportedAt ??= DeviceLog::where('device_id', $device->id)->oldest('reported_at')->value('reported_at'))) { return; } - if (is_null($latestReportedAt = LinkosDeviceLog::where('device_id', $device->sn)->latest('reported_at')->value('reported_at'))) { + if (is_null($latestReportedAt = DeviceLog::where('device_id', $device->id)->latest('reported_at')->value('reported_at'))) { return; } + $service = new BiAngDeviceLogService(); + /** @var \Carbon\Carbon */ $startAt = $lastReportedAt->copy()->startOfHour(); do { - $this->deviceLogService->createReportToLinkosDevice($device, $startAt->copy()); + $service->createReport($device, $startAt->copy()); $startAt->addHour(); } while ($latestReportedAt->gte($startAt)); diff --git a/app/Console/Commands/DeviceLogSyncCommand.php b/app/Console/Commands/DeviceLogSyncCommand.php index 6ccaaae..b55233e 100644 --- a/app/Console/Commands/DeviceLogSyncCommand.php +++ b/app/Console/Commands/DeviceLogSyncCommand.php @@ -3,10 +3,8 @@ namespace App\Console\Commands; use App\Enums\DeviceType; -use App\Iot\BiAng\HttpClient; use App\Models\Device; use App\Services\BiAngDeviceLogService; -use App\Services\DeviceLogService; use Illuminate\Console\Command; use Throwable; @@ -57,7 +55,7 @@ class DeviceLogSyncCommand extends Command $this->info('结束时间: '. $end); /** @var \Illuminate\Database\Eloquent\Collection */ - $devices = Device::with(['supplier'])->supplierBy($factory)->get(); + $devices = Device::with(['supplier', 'project'])->supplierBy("device-supplier-{$factory}")->get(); /** @var \App\Models\Device */ foreach ($devices as $device) { @@ -72,8 +70,8 @@ class DeviceLogSyncCommand extends Command }); try { - switch ($factory) { - case 'biang': + switch ($device->supplier?->key) { + case 'device-supplier-biang': (new BiAngDeviceLogService())->sync($device); break; } diff --git a/app/Http/Controllers/Callback/LinkosController.php b/app/Http/Controllers/Callback/LinkosController.php index b753048..24dab6a 100644 --- a/app/Http/Controllers/Callback/LinkosController.php +++ b/app/Http/Controllers/Callback/LinkosController.php @@ -45,8 +45,6 @@ class LinkosController extends Controller */ protected function handleDeviceDataNotify(array $data) { - return; - if (! is_array($deviceData = $data['data'] ?? [])) { $deviceData = []; } diff --git a/app/Models/Device.php b/app/Models/Device.php index f0ed058..f9e699a 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -39,7 +39,8 @@ class Device extends Model 'created_by', 'updated_by', 'sort', - 'supplier', + 'supplier_key', + 'project_key', ]; public function scopeSupplierBy(Builder $query, string $supplier): void @@ -67,6 +68,11 @@ class Device extends Model return $this->belongsTo(Keywords::class, 'supplier_key', 'key'); } + public function project(): BelongsTo + { + return $this->belongsTo(Keywords::class, 'project_key', 'key'); + } + public function isTypeSoil(): bool { return $this->type === DeviceType::Soil; diff --git a/app/Services/BiAngDeviceLogService.php b/app/Services/BiAngDeviceLogService.php index 70d8ebb..471a2f4 100644 --- a/app/Services/BiAngDeviceLogService.php +++ b/app/Services/BiAngDeviceLogService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Enums\DeviceStatus; use App\Enums\DeviceType; use App\Iot\BiAng\HttpClient as BiAngHttpClient; use App\Models\Device; @@ -12,42 +13,64 @@ use App\Models\SoilMonitoringDailyLog; use App\Models\SoilMonitoringLog; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; class BiAngDeviceLogService { public function sync(Device $device): void { - $httpClient = new BiAngHttpClient("", ""); + $config = json_decode($device->project?->value, true); + if (! isset($config['username'], $config['password'])) { + return; + } + + $httpClient = new BiAngHttpClient($config['username'], $config['password']); + + $data = null; switch ($device->type) { case DeviceType::Soil: $data = $httpClient->getLatestSoilReport($device->sn); - - DeviceLog::firstOrCreate([ - 'device_id' => $device->sn, - 'reported_at' => $data['time'], - ], [ - 'data' => Arr::except($data, ['deviceId', 'time']), - ]); break; case DeviceType::Meteorological: $data = $httpClient->getLatestMeteorologicalReport($device->sn); - - DeviceLog::firstOrCreate([ - 'device_id' => $device->sn, - 'reported_at' => $data['time'], - ], [ - 'data' => Arr::except($data, ['deviceId', 'time']), - ]); break; } + + if (is_array($data) && isset($data['time'])) { + $log = DeviceLog::firstOrCreate([ + 'device_id' => $device->id, + 'reported_at' => $data['time'], + ], [ + 'data' => Arr::except($data, ['deviceId', 'time']), + ]); + + switch ($device->status) { + case DeviceStatus::Online: + // 如果设备数据在线时,最近60分钟内没有数据,则视为离线 + if (now()->subMinutes(60)->gt($log->reported_at)) { + $device->update([ + 'status' => DeviceStatus::Offline, + ]); + } + break; + case DeviceStatus::Offline: + // 如果设备数据离线时,最近60分钟内有数据,则视为在线 + if (now()->subMinutes(60)->lt($log->reported_at)) { + $device->update([ + 'status' => DeviceStatus::Online, + ]); + } + break; + } + } } /** * 创建设备报告 */ - public function createReportToDevice(Device $device, Carbon $time): void + public function createReport(Device $device, Carbon $time): void { switch ($device->type) { case DeviceType::Soil: @@ -68,7 +91,7 @@ class BiAngDeviceLogService $reportedAt = $time->copy()->startOfHour(); /** @var \Illuminate\Database\Eloquent\Collection */ - $logs = DeviceLog::where('device_id', $device->sn) + $logs = DeviceLog::where('device_id', $device->id) ->whereBetween('reported_at', [$reportedAt, $reportedAt->copy()->endOfHour()]) ->oldest('reported_at') ->get(); @@ -130,7 +153,7 @@ class BiAngDeviceLogService $reportedAt = $time->copy()->startOfHour(); /** @var \Illuminate\Database\Eloquent\Collection */ - $logs = DeviceLog::where('device_id', $device->sn) + $logs = DeviceLog::where('device_id', $device->id) ->whereBetween('reported_at', [$reportedAt, $reportedAt->copy()->endOfHour()]) ->oldest('reported_at') ->get(); @@ -143,11 +166,11 @@ class BiAngDeviceLogService if (is_array($data = $log->data)) { foreach ($data as $k => $v) { $attribute = match ($k) { - 'rainfall' => 'moment_rainfall', + 'rainfall' => 'moment_rainfall', //瞬时降雨量 'lightIntensity' => 'illumination', 'airTemperature' => 'air_temperature', 'airHumidity' => 'air_humidity', - 'windDirection' => 'wind_direction', + 'windDirection' => 'wind_degree', 'windSpeed' => 'wind_speed', default => null, }; @@ -161,6 +184,11 @@ class BiAngDeviceLogService 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, @@ -276,17 +304,10 @@ class BiAngDeviceLogService $attributes = value(function ($meteorologicalReports) { $data = [ - 'current_rainfall' => 0, - 'accumulated_rainfall' => ['sum' => 0, 'count' => 0], - 'moment_rainfall' => ['sum' => 0, 'count' => 0], - 'pm10' => ['sum' => 0, 'count' => 0], - 'pm25' => ['sum' => 0, 'count' => 0], + 'current_rainfall' => ['sum' => 0, 'count' => 0], 'illumination' => ['sum' => 0, 'count' => 0], - 'air_pressure' => ['sum' => 0, 'count' => 0], - 'co2' => ['sum' => 0, 'count' => 0], 'air_temperature' => ['sum' => 0, 'count' => 0], 'air_humidity' => ['sum' => 0, 'count' => 0], - 'noise' => ['sum' => 0, 'count' => 0], 'wind_speed' => ['sum' => 0, 'count' => 0], 'wind_samples' => [], ]; @@ -319,9 +340,6 @@ class BiAngDeviceLogService 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) { diff --git a/database/migrations/2023_08_03_185845_add_project_key_to_devices_table.php b/database/migrations/2023_08_03_185845_add_project_key_to_devices_table.php new file mode 100644 index 0000000..cbffd8e --- /dev/null +++ b/database/migrations/2023_08_03_185845_add_project_key_to_devices_table.php @@ -0,0 +1,32 @@ +string('project_key')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('devices', function (Blueprint $table) { + $table->dropColumn(['project_key']); + }); + } +}; diff --git a/database/migrations/2023_08_03_193757_change_columns_to_meteorological_monitoring_daily_logs_table.php b/database/migrations/2023_08_03_193757_change_columns_to_meteorological_monitoring_daily_logs_table.php new file mode 100644 index 0000000..fdfdf30 --- /dev/null +++ b/database/migrations/2023_08_03_193757_change_columns_to_meteorological_monitoring_daily_logs_table.php @@ -0,0 +1,32 @@ +decimal('wind_degree', 5, 2)->nullable()->comment('风向度数')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('meteorological_monitoring_daily_logs', function (Blueprint $table) { + $table->integer('wind_degree')->nullable()->comment('风向度数')->change(); + }); + } +}; diff --git a/database/migrations/2023_08_03_194242_change_columns_to_meteorological_monitoring_logs_table.php b/database/migrations/2023_08_03_194242_change_columns_to_meteorological_monitoring_logs_table.php new file mode 100644 index 0000000..8f3e50e --- /dev/null +++ b/database/migrations/2023_08_03_194242_change_columns_to_meteorological_monitoring_logs_table.php @@ -0,0 +1,34 @@ +decimal('wind_degree', 5, 2)->nullable()->comment('风向度数')->change(); + $table->decimal('illumination', 10, 2)->nullable()->comment('光照度 (单位: Lux)')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('meteorological_monitoring_logs', function (Blueprint $table) { + $table->integer('wind_degree')->nullable()->comment('风向度数')->change(); + $table->integer('illumination')->nullable()->comment('光照度 (单位: Lux)')->change(); + }); + } +}; diff --git a/database/migrations/2023_08_03_194825_change_columns_to_soil_monitoring_logs_table.php b/database/migrations/2023_08_03_194825_change_columns_to_soil_monitoring_logs_table.php new file mode 100644 index 0000000..0de36d4 --- /dev/null +++ b/database/migrations/2023_08_03_194825_change_columns_to_soil_monitoring_logs_table.php @@ -0,0 +1,36 @@ +decimal('n', 8, 2)->nullable()->comment('氮 (单位: mg/kg)')->change(); + $table->decimal('p', 8, 2)->nullable()->comment('磷 (单位: mg/kg)')->change(); + $table->decimal('k', 8, 2)->nullable()->comment('钾 (单位: mg/kg)')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('soil_monitoring_logs', function (Blueprint $table) { + $table->integer('n')->nullable()->comment('氮 (单位: mg/kg)')->change(); + $table->integer('p')->nullable()->comment('磷 (单位: mg/kg)')->change(); + $table->integer('k')->nullable()->comment('钾 (单位: mg/kg)')->change(); + }); + } +}; diff --git a/database/seeders/KeywordsTableSeeder.php b/database/seeders/KeywordsTableSeeder.php index d79e179..8b8e9d7 100644 --- a/database/seeders/KeywordsTableSeeder.php +++ b/database/seeders/KeywordsTableSeeder.php @@ -33,13 +33,45 @@ class KeywordsTableSeeder extends Seeder ['key' => 'crops-cate-activity', 'name' => '其他', 'type_key' => 'crops-category', 'value' => ''], ]], [ - 'key' => 'equipment_supplier', + 'key' => 'device-supplier', 'name' => '设备供应商', 'value' => '', 'list' => [ - ['key' => 'linkos', 'name' => '慧联无限', 'value' => ''], - ['key' => 'biang', 'name' => '比昂', 'value' => ''], - ['key' => 'other', 'name' => '其它', 'value' => ''], + ['key' => 'device-supplier-linkos', 'name' => '慧联无限', 'value' => ''], + ['key' => 'device-supplier-biang', 'name' => '比昂', 'value' => ''], + ['key' => 'device-supplier-other', 'name' => '其它', 'value' => ''], + ], + ], + [ + 'key' => 'device-project', + 'name' => '设备项目', + 'value' => '', + 'list' => [ + [ + 'key' => 'device-project-cyxdnyyq', + 'name' => '成渝现代高效特色农业带合作园区', + 'value' => json_encode([ + 'username' => '成渝现代高效特色农业带合作园区', + 'password' => '888888', + ]), + ], + [ + 'key' => 'device-project-nyncj', + 'name' => '隆昌市农业农村局', + 'value' => json_encode([ + 'username' => '隆昌市农业农村局', + 'password' => '888888', + ]), + ], + [ + 'key' => 'device-project-syqshc', + 'name' => '隆昌市石燕桥镇三合村股份经济联合社', + 'value' => json_encode([ + 'username' => '隆昌市石燕桥镇三合村股份经济联合社', + 'password' => '888888', + ]), + ], + ['key' => 'device-project-other', 'name' => '其它', 'value' => ''], ], ] ]; @@ -71,6 +103,7 @@ class KeywordsTableSeeder extends Seeder 'type_key' => $parentType->key, 'level' => ($parentType->level ?? 1) + 1, 'parent_id' => $parentType->id, + 'value' => $item['value'], ]); } else { $type = Keywords::create(Arr::except($item, 'list'));