From 2ab9c496555a3bef458943f345e101820007e71c Mon Sep 17 00:00:00 2001 From: panliang <1163816051@qq.com> Date: Fri, 5 May 2023 11:32:53 +0800 Subject: [PATCH 1/9] banner seeder --- database/seeders/BannerSeeder.php | 1 + 1 file changed, 1 insertion(+) diff --git a/database/seeders/BannerSeeder.php b/database/seeders/BannerSeeder.php index 672453e..d3f8d65 100644 --- a/database/seeders/BannerSeeder.php +++ b/database/seeders/BannerSeeder.php @@ -18,6 +18,7 @@ class BannerSeeder extends Seeder { $places = [ ['key' => 'h5-home-banner', 'name' => '首页广告位'], + ['key' => 'h5-home-special', 'name' => '首页园区要点'], ]; Banner::truncate(); From d1a14556f2fe071d1f23d11098a9705480ca6edb Mon Sep 17 00:00:00 2001 From: panliang <1163816051@qq.com> Date: Fri, 5 May 2023 11:58:10 +0800 Subject: [PATCH 2/9] setting --- app/Admin/Controllers/SettingController.php | 19 ++++------------ database/seeders/DatabaseSeeder.php | 6 +++++- database/seeders/SettingSeeder.php | 24 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 database/seeders/SettingSeeder.php diff --git a/app/Admin/Controllers/SettingController.php b/app/Admin/Controllers/SettingController.php index 1bb58df..8b34b7d 100644 --- a/app/Admin/Controllers/SettingController.php +++ b/app/Admin/Controllers/SettingController.php @@ -18,10 +18,7 @@ class SettingController extends AdminController public function index() { - $page = $this->basePage()->body([ - Alert::make()->showIcon(true)->body("此处内容仅供演示, 设置项无实际意义,实际开发中请根据实际情况进行修改。"), - $this->form(), - ]); + $page = $this->basePage()->body($this->form()); return $this->response()->success($page); } @@ -37,13 +34,8 @@ class SettingController extends AdminController ->body( Tabs::make()->tabs([ Tab::make()->title('基本设置')->body([ - TextControl::make()->label('网站名称')->name('site_name'), - InputKV::make()->label('附加配置')->name('addition_config'), - ]), - Tab::make()->title('上传设置')->body([ - TextControl::make()->label('上传域名')->name('upload_domain'), - TextControl::make()->label('上传路径')->name('upload_path'), - ]), + TextControl::make()->label('RTSP 转流服务')->name('rtsp_url'), + ]) ]) ); } @@ -51,10 +43,7 @@ class SettingController extends AdminController public function store(Request $request) { $data = $request->only([ - 'site_name', - 'addition_config', - 'upload_domain', - 'upload_path', + 'rtsp_url' ]); return settings()->adminSetMany($data); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index e796ac6..d198219 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,11 +2,12 @@ namespace Database\Seeders; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { + use WithoutModelEvents; /** * Seed the application's database. * @@ -16,5 +17,8 @@ class DatabaseSeeder extends Seeder { $this->call(AdminMenuSeeder::class); $this->call(AdminSeeder::class); + $this->call(KeywordSeeder::class); + $this->call(BannerSeeder::class); + $this->call(SettingSeeder::class); } } diff --git a/database/seeders/SettingSeeder.php b/database/seeders/SettingSeeder.php new file mode 100644 index 0000000..815ab21 --- /dev/null +++ b/database/seeders/SettingSeeder.php @@ -0,0 +1,24 @@ + 'rtsp_url', 'values' => json_encode('ws://117.176.117.148:8100/rtsp?url=')] + ]; + AdminSetting::truncate(); + AdminSetting::insert($settings); + } +} From 180f2e070d4bcfaae3ab7b49f0f78e99c7ef883d Mon Sep 17 00:00:00 2001 From: panliang <1163816051@qq.com> Date: Fri, 5 May 2023 15:12:15 +0800 Subject: [PATCH 3/9] =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=9B=91=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Admin/Controllers/DeviceController.php | 119 +++++++++------------ app/Models/Device.php | 6 +- app/Services/Admin/DeviceService.php | 29 +++++ database/factories/DeviceFactory.php | 26 +++++ database/seeders/DeviceSeeder.php | 28 +++++ lang/zh_CN/device.php | 6 ++ 6 files changed, 145 insertions(+), 69 deletions(-) create mode 100644 database/factories/DeviceFactory.php create mode 100644 database/seeders/DeviceSeeder.php create mode 100644 lang/zh_CN/device.php diff --git a/app/Admin/Controllers/DeviceController.php b/app/Admin/Controllers/DeviceController.php index e105e99..74b8b2b 100644 --- a/app/Admin/Controllers/DeviceController.php +++ b/app/Admin/Controllers/DeviceController.php @@ -2,17 +2,12 @@ namespace App\Admin\Controllers; -use Slowlyo\OwlAdmin\Renderers\Page; -use Slowlyo\OwlAdmin\Renderers\Form; -use Slowlyo\OwlAdmin\Renderers\TableColumn; -use Slowlyo\OwlAdmin\Renderers\TextControl; -use Slowlyo\OwlAdmin\Renderers\CRUDTable; +use Slowlyo\OwlAdmin\Renderers\{Button, Form, Page, TableColumn, TextControl, Component, CRUDTable, Card, Video, DateRangeControl}; use Slowlyo\OwlAdmin\Controllers\AdminController; use App\Services\Admin\DeviceService; use App\Models\Device; use App\Models\Keyword; use App\Admin\Components; -use Slowlyo\OwlAdmin\Renderers\Button; class DeviceController extends AdminController { @@ -68,18 +63,12 @@ class DeviceController extends AdminController \amisMake()->SelectControl()->name('powered_by')->label('厂家')->options(Keyword::getByParentKey('device-factory')->pluck('name', 'id')->toArray())->required(true), TextControl::make()->name('model_sn')->label('型号'), \amisMake()->RadiosControl()->name('type')->label('类型')->options(Device::typeMap())->required(true), - //监控设备-额外参数 - \amisMake()->TextControl()->name('extends.ip')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('设备IP'), - \amisMake()->TextControl()->name('extends.username')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('设备用户名'), - \amisMake()->TextControl()->name('extends.password')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('设备密码'), - \amisMake()->GroupControl()->body([ - \amisMake()->TextControl()->name('extends.live_port')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('实时端口'), - \amisMake()->TextControl()->name('extends.live_channel')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('实时通道'), - ]), - \amisMake()->GroupControl()->body([ - \amisMake()->TextControl()->name('extends.back_port')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('回放端口'), - \amisMake()->TextControl()->name('extends.back_channel')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label('回放通道'), - ]), + // 监控设备-额外参数 + // rtsp://admin:lcdx12345@172.16.40.2:554/Streaming/Channels/5201 + TextControl::make()->name('extends.rtsp_url')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label(__('device.rtsp_url')), + // rtsp://admin:lcdx12345@172.16.40.2:554/Streaming/tracks/5201 + TextControl::make()->name('extends.rtsp_history')->hiddenOn('data.type != '.Device::TYPE_MONITOR)->label(__('device.rtsp_history')), + Components::make()->keywordsTagControl('group_tags', '分组', 'device-group'), Components::make()->sortControl('sort', __('admin.order')), TextControl::make()->name('is_enable')->type('switch')->default(1)->label('显示'), @@ -101,64 +90,60 @@ class DeviceController extends AdminController /** * 监控设备列表 */ - public function monitorList(){ - return CRUDTable::make()->mode('cards')->columnsCount(3) - ->data([ - 'items' => [ - [ - 'id'=>1, - 'src'=>'' - // ws://183.221.204.29:8100/rtsp?url=cnRzcDovL2FkbWluOjEyMzQ1Njc4OXhAMTE3LjE3NC4xODQuMTE4OjkwMDkvY2FtL3JlYWxtb25pdG9yP2NoYW5uZWw9MSZzdWJ0eXBlPTA= - ], - ] - ]) - ->filter([ - 'title' => '搜索条件', - 'body' => [ - \amisMake()->TextControl()->name('name')->label('点位名称')->size('sm'), - amis('submit')->label(__('admin.search'))->level('primary'), - ] - ]) + public function monitorList() + { + if ($this->actionOfGetData()) { + return $this->response()->success($this->service->list()); + } + return CRUDTable::make() + ->mode('cards') + ->hideCheckToggler() + ->columnsCount(3) + ->perPage(6) + ->affixHeader(false) + ->filterTogglable(true) + ->set('primaryField', $this->service->primaryKey()) + ->api(admin_url($this->queryPath . '?_action=getData&_type=' . Device::TYPE_MONITOR)) + ->footerToolbar(['statistics', 'pagination']) + ->headerToolbar([]) + ->filter($this->baseFilter()->actions([])->body([ + TextControl::make()->name('name')->label('点位名称')->size('md'), + Button::make()->label(__('admin.reset'))->actionType('clear-and-submit'), + Component::make()->setType('submit')->label(__('admin.search'))->level('primary'), + ])) ->actions([]) ->itemClassName('col-sm-4') - ->card([ - 'header' => [], - 'body' => amisMake()->Video() - // ->isLive(true)->videoType('video/x-flv')->muted(true)->autoPlay(true) - ->src('${src}') - ]); + ->card(Card::make()->header(['title' => '$name'])->body(Video::make()->videoType('video/x-flv')->muted(true)->autoPlay(true)->src('${src}'))); } /** * 监控历史视频 */ - public function monitorVideoList(){ - return CRUDTable::make()->mode('cards')->columnsCount(3) - ->data([ - 'items' => [ - [ - 'id'=>1, - 'src'=>'' - // ws://183.221.204.29:8100/rtsp?url=cnRzcDovL2FkbWluOjEyMzQ1Njc4OXhAMTE3LjE3NC4xODQuMTE4OjkwMDkvY2FtL3JlYWxtb25pdG9yP2NoYW5uZWw9MSZzdWJ0eXBlPTA= - ], - ] - ]) - ->filter([ - 'title' => '搜索条件', - 'body' => [ - \amisMake()->TextControl()->name('name')->label('点位名称')->size('sm'), - \amisMake()->DateRangeControl()->label('时间范围'), - amis('submit')->label(__('admin.search'))->level('primary'), - ] - ]) + public function monitorVideoList() + { + if ($this->actionOfGetData()) { + return $this->response()->success($this->service->list()); + } + return CRUDTable::make() + ->mode('cards') + ->hideCheckToggler() + ->columnsCount(3) + ->perPage(6) + ->affixHeader(false) + ->filterTogglable(true) + ->set('primaryField', $this->service->primaryKey()) + ->api(admin_url($this->queryPath . '?_action=getData&_type=' . Device::TYPE_MONITOR . '&_mode=history')) + ->footerToolbar(['statistics', 'pagination']) + ->headerToolbar([]) + ->filter($this->baseFilter()->actions([])->body([ + TextControl::make()->name('name')->label('点位名称')->size('md'), + DateRangeControl::make()->name('date')->label('日期')->maxDate('now')->size('md'), + Button::make()->label(__('admin.reset'))->actionType('clear-and-submit'), + Component::make()->setType('submit')->label(__('admin.search'))->level('primary'), + ])) ->actions([]) ->itemClassName('col-sm-4') - ->card([ - 'header' => [], - 'body' => amisMake()->Video() - // ->isLive(true)->videoType('video/x-flv')->muted(true)->autoPlay(true) - ->src('${src}') - ]); + ->card(Card::make()->header(['title' => '$name'])->body(Video::make()->videoType('video/x-flv')->muted(true)->autoPlay(true)->src('${src}'))); } /** diff --git a/app/Models/Device.php b/app/Models/Device.php index 9cb9804..9d33899 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -2,14 +2,12 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use EloquentFilter\Filterable; use Illuminate\Database\Eloquent\Relations\HasMany; class Device extends Model { - use HasFactory; use Filterable; public const TYPE_MONITOR = 1; //监控设备 @@ -30,6 +28,10 @@ class Device extends Model 'group_tags' ]; + protected $casts = [ + 'extends' => 'json' + ]; + protected function serializeDate(\DateTimeInterface $date){ return $date->format('Y-m-d H:i:s'); } diff --git a/app/Services/Admin/DeviceService.php b/app/Services/Admin/DeviceService.php index 7b7045e..22fe056 100644 --- a/app/Services/Admin/DeviceService.php +++ b/app/Services/Admin/DeviceService.php @@ -16,4 +16,33 @@ class DeviceService extends BaseService protected array $withRelationships = ['factory']; protected string $modelFilterName = DeviceFilter::class; + + public function list() + { + $query = $this->listQuery(); + + $items = (clone $query)->paginate(request()->input('perPage', 20))->items(); + $base = settings()->get('rtsp_url'); + if (request('_type') == Device::TYPE_MONITOR) { + $history = request('_mode') === 'history'; + $date = explode(',', request('date')); + $start = data_get($date, 0); + $end = data_get($date, 1); + foreach ($items as &$item) { + $url = data_get($item->extends, $history ? 'rtsp_history' : 'rtsp_url'); + $src = null; + if ($base && $url) { + // 查看历史监控 + if ($history && $start && $end) { + $url .= '?start=' . date('Y-m-d', $start) . '&end=' . data('Y-m-d', $end); + } + $src = $base . base64_encode($url); + } + $item->src = $src; + } + } + $total = (clone $query)->count(); + + return compact('items', 'total'); + } } diff --git a/database/factories/DeviceFactory.php b/database/factories/DeviceFactory.php new file mode 100644 index 0000000..0d4f607 --- /dev/null +++ b/database/factories/DeviceFactory.php @@ -0,0 +1,26 @@ + + */ +class DeviceFactory extends Factory +{ + protected $model = Device::class; + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + 'name' => $this->faker->name, + 'sn' => $this->faker->isbn10(), + ]; + } +} diff --git a/database/seeders/DeviceSeeder.php b/database/seeders/DeviceSeeder.php new file mode 100644 index 0000000..9d5fc4a --- /dev/null +++ b/database/seeders/DeviceSeeder.php @@ -0,0 +1,28 @@ +count(100)->create([ + 'type' => Device::TYPE_MONITOR, + // 'extends' => [ + // 'rtsp_url' => 'rtsp://admin:lcdx12345@172.16.40.2:554/Streaming/Channels/5201', + // 'rtsp_history' => 'rtsp://admin:lcdx12345@172.16.40.2:554/Streaming/tracks/5201', + // ] + ]); + } +} diff --git a/lang/zh_CN/device.php b/lang/zh_CN/device.php new file mode 100644 index 0000000..fa1ed41 --- /dev/null +++ b/lang/zh_CN/device.php @@ -0,0 +1,6 @@ + 'RTSP 直播流', + 'rtsp_history' => 'RTSP 回放流', +]; From 3f43e9953f102a77576b7f8c3590aad085f83a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Fri, 5 May 2023 16:27:17 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/DeviceLogSyncCommand.php | 83 +++++++++++++++++++ app/Models/Device.php | 16 +++- app/Services/DeviceLogService.php | 59 +++++++++++++ 3 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 app/Console/Commands/DeviceLogSyncCommand.php create mode 100644 app/Services/DeviceLogService.php diff --git a/app/Console/Commands/DeviceLogSyncCommand.php b/app/Console/Commands/DeviceLogSyncCommand.php new file mode 100644 index 0000000..4dda41d --- /dev/null +++ b/app/Console/Commands/DeviceLogSyncCommand.php @@ -0,0 +1,83 @@ +deviceLogService = $deviceLogService; + + $sleep = value(fn ($sleep) => is_numeric($sleep) ? (int) $sleep : 60, $this->option('sleep')); + + while (true) { + $end = now(); + $start = $end->copy()->subHours(1); + + $this->info('------------------------------------------'); + $this->info('开始时间: '. $start); + $this->info('结束时间: '. $end); + + $this->performSync($start, $end); + + $this->info('------------------------------------------'); + $this->newLine(); + + sleep($sleep); + }; + } + + protected function performSync(Carbon $start, Carbon $end): void + { + /** @var \Illuminate\Database\Eloquent\Collection */ + $devices = Device::poweredBy($this->argument('factory'))->get(); + + foreach ($devices as $device) { + $this->info('=========================================='); + $this->info('设备编号: ' . $device->sn); + $this->info('设备名称: ' . $device->name); + + try { + $this->deviceLogService->sync($device, $start, $end); + + $this->info('同步成功!'); + } catch (Throwable $e) { + report($e); + + $this->error('同步失败: '. $e->getMessage()); + } + + $this->info('=========================================='); + } + } +} diff --git a/app/Models/Device.php b/app/Models/Device.php index 9d33899..cd09d9d 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use EloquentFilter\Filterable; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; class Device extends Model @@ -22,15 +23,21 @@ class Device extends Model public const STATE_OFFLINE = 2; public const STATE_FAULT = 3; + protected $casts = [ + 'extends' => 'json' + ]; + protected $fillable = [ 'name', 'sn', 'powered_by', 'type', 'model_sn', 'state', 'extends', 'is_enable', 'sort', 'is_recommend', 'group_tags' ]; - protected $casts = [ - 'extends' => 'json' - ]; + public function scopePoweredBy(Builder $query, string $factory): void + { + $query->whereHas('factory', fn ($query) => $query->where('key', $factory)); + } + protected function serializeDate(\DateTimeInterface $date){ return $date->format('Y-m-d H:i:s'); @@ -53,7 +60,8 @@ class Device extends Model return $this->hasMany(DeviceLog::class); } - public function factory(){ + public function factory() + { return $this->belongsTo(Keyword::class, 'powered_by'); } } diff --git a/app/Services/DeviceLogService.php b/app/Services/DeviceLogService.php new file mode 100644 index 0000000..eb5b9b7 --- /dev/null +++ b/app/Services/DeviceLogService.php @@ -0,0 +1,59 @@ +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) { + $device->logs()->firstOrCreate([ + 'reported_at' => $item['createTime'], + ], [ + 'data' => empty($item['data']) ? (new \stdClass) : $item['data'], + ]); + } + + unset($data); + + $page++; + } while ($countResults === $perPage); + } +} From 4646f1fd1685e6ca8109658186016f4f3524790f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Fri, 5 May 2023 17:21:05 +0800 Subject: [PATCH 5/9] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/DeviceLogSyncCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/DeviceLogSyncCommand.php b/app/Console/Commands/DeviceLogSyncCommand.php index 4dda41d..51df568 100644 --- a/app/Console/Commands/DeviceLogSyncCommand.php +++ b/app/Console/Commands/DeviceLogSyncCommand.php @@ -17,7 +17,7 @@ class DeviceLogSyncCommand extends Command */ protected $signature = 'device-log:sync {factory} - {--sleep=60 : 设备数据同步完成后的休眠时间(秒)}'; + {--sleep=60 : 设备数据同步完成后的休眠时间(秒)}'; /** * The console command description. @@ -38,7 +38,7 @@ class DeviceLogSyncCommand extends Command { $this->deviceLogService = $deviceLogService; - $sleep = value(fn ($sleep) => is_numeric($sleep) ? (int) $sleep : 60, $this->option('sleep')); + $sleep = (int) value(fn ($sleep) => is_numeric($sleep) ? $sleep : 60, $this->option('sleep')); while (true) { $end = now(); From c551bcafeb08e95bfd152d479b389e7ea686484d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Sat, 6 May 2023 10:16:38 +0800 Subject: [PATCH 6/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20SQL=20=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=20N+1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/DeviceLogSyncCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/DeviceLogSyncCommand.php b/app/Console/Commands/DeviceLogSyncCommand.php index 51df568..e727132 100644 --- a/app/Console/Commands/DeviceLogSyncCommand.php +++ b/app/Console/Commands/DeviceLogSyncCommand.php @@ -60,7 +60,7 @@ class DeviceLogSyncCommand extends Command protected function performSync(Carbon $start, Carbon $end): void { /** @var \Illuminate\Database\Eloquent\Collection */ - $devices = Device::poweredBy($this->argument('factory'))->get(); + $devices = Device::with(['factory'])->poweredBy($this->argument('factory'))->get(); foreach ($devices as $device) { $this->info('=========================================='); From 3fcbe33ddc0a5e60c454f77cd98d070dffe50cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Sat, 6 May 2023 14:18:52 +0800 Subject: [PATCH 7/9] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=90=8C=E6=AD=A5=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/DeviceLogSyncCommand.php | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/Console/Commands/DeviceLogSyncCommand.php b/app/Console/Commands/DeviceLogSyncCommand.php index e727132..d5ea029 100644 --- a/app/Console/Commands/DeviceLogSyncCommand.php +++ b/app/Console/Commands/DeviceLogSyncCommand.php @@ -38,29 +38,31 @@ class DeviceLogSyncCommand extends Command { $this->deviceLogService = $deviceLogService; + $factory = $this->argument('factory'); + $sleep = (int) value(fn ($sleep) => is_numeric($sleep) ? $sleep : 60, $this->option('sleep')); while (true) { - $end = now(); - $start = $end->copy()->subHours(1); - - $this->info('------------------------------------------'); - $this->info('开始时间: '. $start); - $this->info('结束时间: '. $end); - - $this->performSync($start, $end); - - $this->info('------------------------------------------'); - $this->newLine(); + $this->runSync($factory); sleep($sleep); }; } - protected function performSync(Carbon $start, Carbon $end): void + /** + * 执行同步 + */ + protected function runSync(string $factory): void { + $end = now(); + $start = $end->copy()->subHours(1); + + $this->info('------------------------------------------'); + $this->info('开始时间: '. $start); + $this->info('结束时间: '. $end); + /** @var \Illuminate\Database\Eloquent\Collection */ - $devices = Device::with(['factory'])->poweredBy($this->argument('factory'))->get(); + $devices = Device::with(['factory'])->poweredBy($factory)->get(); foreach ($devices as $device) { $this->info('=========================================='); @@ -79,5 +81,8 @@ class DeviceLogSyncCommand extends Command $this->info('=========================================='); } + + $this->info('------------------------------------------'); + $this->newLine(); } } From 3d07fde0087924371247c16539493bb1f09e89c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Sat, 6 May 2023 16:16:21 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=E5=90=8C=E6=AD=A5=20linkos=20=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E5=8E=86=E5=8F=B2=E6=95=B0=E6=8D=AE=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E5=8C=BA=E5=88=86=E5=9C=9F=E5=A3=A4=E7=9B=91=E6=8E=A7=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=92=8C=E6=B0=94=E8=B1=A1=E7=9B=91=E6=8E=A7=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/DeviceLogSyncCommand.php | 7 +++++++ app/Models/Device.php | 5 +++++ app/Services/DeviceLogService.php | 13 +++++++++++++ 3 files changed, 25 insertions(+) diff --git a/app/Console/Commands/DeviceLogSyncCommand.php b/app/Console/Commands/DeviceLogSyncCommand.php index d5ea029..8a900ea 100644 --- a/app/Console/Commands/DeviceLogSyncCommand.php +++ b/app/Console/Commands/DeviceLogSyncCommand.php @@ -68,6 +68,13 @@ class DeviceLogSyncCommand extends Command $this->info('=========================================='); $this->info('设备编号: ' . $device->sn); $this->info('设备名称: ' . $device->name); + $this->info('设备类型: ' . match ($device->type) { + Device::TYPE_SOIL => '土壤设备', + Device::TYPE_WATER_QUALITY => '水质设备', + Device::TYPE_METEOROLOGICAL => '气象设备', + Device::TYPE_AIR => '通风设备', + default => '其它', + }); try { $this->deviceLogService->sync($device, $start, $end); diff --git a/app/Models/Device.php b/app/Models/Device.php index cd09d9d..bc21c8b 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -64,4 +64,9 @@ class Device extends Model { return $this->belongsTo(Keyword::class, 'powered_by'); } + + public function isTypeSoil(): bool + { + return $this->type === static::TYPE_SOIL; + } } diff --git a/app/Services/DeviceLogService.php b/app/Services/DeviceLogService.php index eb5b9b7..6c185ca 100644 --- a/app/Services/DeviceLogService.php +++ b/app/Services/DeviceLogService.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Iot\Linkos\HttpClient as LinkosHttpClient; use App\Models\Device; +use Illuminate\Support\Arr; use Illuminate\Support\Carbon; class DeviceLogService @@ -44,6 +45,18 @@ class DeviceLogService } foreach ($data['content'] as $item) { + $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'], ], [ From 015e10682fcdf32dab70fd7453a5d555d6fba25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=9D=99?= Date: Sat, 6 May 2023 18:02:02 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Commands/DeviceLogReportCommand.php | 197 ++++++++++++++++++ app/Models/MeteorologicalMonitoringReport.php | 36 ++++ app/Models/SoilMonitoringReport.php | 26 +++ app/Models/WaterQualityMonitoringReport.php | 26 +++ ...7_create_soil_monitoring_reports_table.php | 41 ++++ ...water_quality_monitoring_reports_table.php | 41 ++++ ...eteorological_monitoring_reports_table.php | 51 +++++ 7 files changed, 418 insertions(+) create mode 100644 app/Console/Commands/DeviceLogReportCommand.php create mode 100644 app/Models/MeteorologicalMonitoringReport.php create mode 100644 app/Models/SoilMonitoringReport.php create mode 100644 app/Models/WaterQualityMonitoringReport.php create mode 100644 database/migrations/2023_05_06_103057_create_soil_monitoring_reports_table.php create mode 100644 database/migrations/2023_05_06_103152_create_water_quality_monitoring_reports_table.php create mode 100644 database/migrations/2023_05_06_103216_create_meteorological_monitoring_reports_table.php diff --git a/app/Console/Commands/DeviceLogReportCommand.php b/app/Console/Commands/DeviceLogReportCommand.php new file mode 100644 index 0000000..369f217 --- /dev/null +++ b/app/Console/Commands/DeviceLogReportCommand.php @@ -0,0 +1,197 @@ +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) { + $this->createMonitoringReport($device); + } + + sleep($sleep); + }; + } + + protected function createMonitoringReport(Device $device) + { + switch ($device->factory?->key) { + case 'link-os': + $this->createLinkosDeviceMonitoringReport($device); + break; + } + } + + protected function createLinkosDeviceMonitoringReport(Device $device) + { + switch ($device->type) { + case Device::TYPE_SOIL: + $lastMonitoringReport = SoilMonitoringReport::where('device_id', $device->id) + ->latest('reported_at') + ->first(); + + $lastReportedAt = $lastMonitoringReport?->reported_at; + + $device->logs() + ->when($lastReportedAt, fn ($query, $lastReportedAt) => $query->where('reported_at', '>=', $lastReportedAt->reported_at)) + ->oldest('reported_at') + ->chunkById(500, function ($deviceLogs) use ($device, &$lastMonitoringReport) { + /** @var \App\Models\DeviceLog */ + foreach ($deviceLogs as $deviceLog) { + $monitoringReport = SoilMonitoringReport::firstOrCreate( + [ + 'device_id' => $device->id, + 'reported_at' => $deviceLog->reported_at->startOfHour(), + ], + Arr::except($lastMonitoringReport?->setHidden([])?->attributesToArray() ?: [], ['reported_at']) + ); + + foreach ([ + 'conductivity' => 'conductivity', + 'soil_humidity' => 'humidity', + 'soil_temperature' => 'temperature', + 'nitrogen_content' => 'n', + 'potassium_content' => 'k', + 'phosphorus_content' => 'p', + ] as $key => $attribute) { + if (! is_array($deviceLog->data) || ! array_key_exists($key, $deviceLog->data)) { + continue; + } + + $monitoringReport->{$attribute} = $deviceLog->data[$key]; + } + + $lastMonitoringReport = tap($monitoringReport)->save(); + } + }); + break; + + case Device::TYPE_WATER_QUALITY: + $lastMonitoringReport = WaterQualityMonitoringReport::where('device_id', $device->id) + ->latest('reported_at') + ->first(); + + $lastReportedAt = $lastMonitoringReport?->reported_at; + + $device->logs() + ->when($lastReportedAt, fn ($query, $lastReportedAt) => $query->where('reported_at', '>=', $lastReportedAt->reported_at)) + ->oldest('reported_at') + ->chunkById(500, function ($deviceLogs) use ($device, &$lastMonitoringReport) { + /** @var \App\Models\DeviceLog */ + foreach ($deviceLogs as $deviceLog) { + $monitoringReport = WaterQualityMonitoringReport::firstOrCreate( + [ + 'device_id' => $device->id, + 'reported_at' => $deviceLog->reported_at->startOfHour(), + ], + Arr::except($lastMonitoringReport?->setHidden([])?->attributesToArray() ?: [], ['reported_at']) + ); + + foreach ([ + 'chlorine' => 'chlorine', + 'conductivity' => 'conductivity', + 'oxygen' => 'oxygen', + 'ph' => 'ph', + 'temp' => 'temperature', + 'turbidity' => 'turbidity', + ] as $key => $attribute) { + if (! is_array($deviceLog->data) || ! array_key_exists($key, $deviceLog->data)) { + continue; + } + + $monitoringReport->{$attribute} = $deviceLog->data[$key]; + } + + $lastMonitoringReport = tap($monitoringReport)->save(); + } + }); + break; + + case Device::TYPE_METEOROLOGICAL: + $lastMonitoringReport = MeteorologicalMonitoringReport::where('device_id', $device->id) + ->latest('reported_at') + ->first(); + + $lastReportedAt = $lastMonitoringReport?->reported_at; + + $device->logs() + ->when($lastReportedAt, fn ($query, $lastReportedAt) => $query->where('reported_at', '>=', $lastReportedAt->reported_at)) + ->oldest('reported_at') + ->chunkById(500, function ($deviceLogs) use ($device, &$lastMonitoringReport) { + /** @var \App\Models\DeviceLog */ + foreach ($deviceLogs as $deviceLog) { + $monitoringReport = MeteorologicalMonitoringReport::firstOrCreate( + [ + 'device_id' => $device->id, + 'reported_at' => $deviceLog->reported_at->startOfHour(), + ], + Arr::except($lastMonitoringReport?->setHidden([])?->attributesToArray() ?: [], ['reported_at']) + ); + + foreach ([ + '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', + ] as $key => $attribute) { + if (! is_array($deviceLog->data) || ! array_key_exists($key, $deviceLog->data)) { + continue; + } + + $monitoringReport->{$attribute} = $deviceLog->data[$key]; + } + + $lastMonitoringReport = tap($monitoringReport)->save(); + } + }); + break; + } + } +} diff --git a/app/Models/MeteorologicalMonitoringReport.php b/app/Models/MeteorologicalMonitoringReport.php new file mode 100644 index 0000000..904425f --- /dev/null +++ b/app/Models/MeteorologicalMonitoringReport.php @@ -0,0 +1,36 @@ + 'datetime' + ]; + + 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/SoilMonitoringReport.php b/app/Models/SoilMonitoringReport.php new file mode 100644 index 0000000..bdba671 --- /dev/null +++ b/app/Models/SoilMonitoringReport.php @@ -0,0 +1,26 @@ + 'datetime', + ]; + + protected $fillable = [ + 'device_id', + 'temperature', + 'humidity', + 'n', + 'p', + 'k', + 'conductivity', + 'reported_at', + ]; +} diff --git a/app/Models/WaterQualityMonitoringReport.php b/app/Models/WaterQualityMonitoringReport.php new file mode 100644 index 0000000..b943b45 --- /dev/null +++ b/app/Models/WaterQualityMonitoringReport.php @@ -0,0 +1,26 @@ + 'datetime' + ]; + + protected $fillable = [ + 'device_id', + 'chlorine', + 'conductivity', + 'oxygen', + 'ph', + 'temperature', + 'turbidity', + 'reported_at', + ]; +} diff --git a/database/migrations/2023_05_06_103057_create_soil_monitoring_reports_table.php b/database/migrations/2023_05_06_103057_create_soil_monitoring_reports_table.php new file mode 100644 index 0000000..daee1b2 --- /dev/null +++ b/database/migrations/2023_05_06_103057_create_soil_monitoring_reports_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('device_id')->comment('设备ID'); + $table->decimal('temperature', 8, 2)->nullable()->comment('温度(单位: ℃)'); + $table->decimal('humidity', 8, 2)->nullable()->comment('湿度(单位: %RH)'); + $table->integer('n')->nullable()->comment('氮 (单位: mg/kg)'); + $table->integer('p')->nullable()->comment('磷 (单位: mg/kg)'); + $table->integer('k')->nullable()->comment('钾 (单位: mg/kg)'); + $table->decimal('conductivity', 8, 2)->nullable()->comment('电导率(单位: us/cm)'); + $table->timestamp('reported_at')->comment('报告时间(小时),示例: 2023-05-01 01:00:00'); + $table->timestamps(); + + $table->unique(['device_id', 'reported_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('soil_monitoring_reports'); + } +}; diff --git a/database/migrations/2023_05_06_103152_create_water_quality_monitoring_reports_table.php b/database/migrations/2023_05_06_103152_create_water_quality_monitoring_reports_table.php new file mode 100644 index 0000000..ac51ed3 --- /dev/null +++ b/database/migrations/2023_05_06_103152_create_water_quality_monitoring_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->timestamp('reported_at')->comment('报告时间(小时),示例: 2023-05-01 01:00:00'); + $table->timestamps(); + + $table->unique(['device_id', 'reported_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('water_quality_monitoring_reports'); + } +}; diff --git a/database/migrations/2023_05_06_103216_create_meteorological_monitoring_reports_table.php b/database/migrations/2023_05_06_103216_create_meteorological_monitoring_reports_table.php new file mode 100644 index 0000000..1c46fc3 --- /dev/null +++ b/database/migrations/2023_05_06_103216_create_meteorological_monitoring_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->timestamp('reported_at')->comment('报告时间(小时),示例: 2023-05-01 01:00:00'); + $table->timestamps(); + + $table->unique(['device_id', 'reported_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('meteorological_monitoring_reports'); + } +};