512 lines
20 KiB
PHP
512 lines
20 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Admin;
|
||
|
||
use App\Models\Device;
|
||
use App\Models\MonitorMode;
|
||
use App\Models\{MeteorologicalDailyReport, MeteorologicalReport, SoilDailyReport, SoilReport, AirLog, FarmWormPhoto, MonitorDevice, RegionMonitor, Region, FarmWormReport};
|
||
use App\Filters\Admin\DeviceFilter;
|
||
use Carbon\Carbon;
|
||
use App\Admin\Components;
|
||
use App\Iot\Linkos\HttpClient;
|
||
/**
|
||
* @method Device getModel()
|
||
* @method Device|\Illuminate\Database\Query\Builder query()
|
||
*/
|
||
class DeviceService extends BaseService
|
||
{
|
||
protected string $modelName = Device::class;
|
||
|
||
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) {
|
||
// 查看历史监控 &starttime=2023_02_02_14_00_00&endtime=2023_02_02_15_00_00
|
||
if ($history && $start && $end) {
|
||
$start_format = Carbon::createFromTimestamp($start)->format('Y_m_d_H_i_s');
|
||
$end_format = Carbon::createFromTimestamp($end)->format('Y_m_d_H_i_s');
|
||
$url .= (str_contains($url, '?') ? '&' : '?') .'starttime=' . $start_format . '&endtime=' . $end_format;
|
||
}
|
||
$src = $base . base64_encode($url);
|
||
}
|
||
$item->src = $src;
|
||
}
|
||
}
|
||
$total = (clone $query)->count();
|
||
|
||
return compact('items', 'total');
|
||
}
|
||
|
||
/**
|
||
* 根据时间,处理X轴横坐标
|
||
*/
|
||
public function makeChartXkeys($startTime = null, $endTime = null){
|
||
$diffDays = 0;
|
||
$day = date('Y-m-d');
|
||
$xKeys = [];
|
||
|
||
if($startTime && $endTime){
|
||
$startDay = Carbon::parse($startTime);
|
||
$endDay = Carbon::parse($endTime);
|
||
if($startDay->format('Y-m-d') == $endDay->format('Y-m-d')){//判断是否同一天
|
||
$day = $startDay->format('Y-m-d');
|
||
}else{
|
||
$diffDays = $startDay->diffInDays($endDay, false);
|
||
}
|
||
}
|
||
|
||
$xKeys = [];
|
||
if($diffDays){
|
||
for ($i = 0; $i<=$diffDays; $i++) {
|
||
$xKeys[] =(clone $startDay)->addDays($i)->startOfDay()->format('Y-m-d');
|
||
}
|
||
}else{
|
||
//调整截至到当前小时
|
||
|
||
$th = $startDay->format('H');
|
||
$eh = $endDay->format('H');;
|
||
if($day == date('Y-m-d')){
|
||
if($eh > date('H')){
|
||
$eh = date('H');
|
||
}
|
||
}
|
||
for ($i = $th; $i < ($eh+1); $i++) {
|
||
$xKeys[] = str_pad($i, 2, '0', STR_PAD_LEFT).':00';
|
||
}
|
||
}
|
||
|
||
return array($day, $diffDays, $xKeys);
|
||
}
|
||
|
||
public function getMonitorModeDeviceChartConfig($monitorMode, $startTime, $endTime, $columnNum = 3)
|
||
{
|
||
list($day, $diffDays, $xKeys) = $this->makeChartXkeys($startTime, $endTime);
|
||
switch($monitorMode->type){
|
||
case MonitorMode::TYPE_METEOROLOGICAL:
|
||
$dayliyReportQuery = MeteorologicalDailyReport::query();
|
||
$reportQuery = MeteorologicalReport::query();
|
||
$fieldNameMap = MonitorMode::fieldMap(MonitorMode::TYPE_METEOROLOGICAL);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(MonitorMode::TYPE_METEOROLOGICAL);
|
||
break;
|
||
case MonitorMode::TYPE_SOIL:
|
||
$dayliyReportQuery = SoilDailyReport::query();
|
||
$reportQuery = SoilReport::query();
|
||
$fieldNameMap = MonitorMode::fieldMap(MonitorMode::TYPE_SOIL);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(MonitorMode::TYPE_SOIL);
|
||
break;
|
||
case MonitorMode::TYPE_INSECT:
|
||
$dayliyReportQuery = FarmWormReport::query();
|
||
$reportQuery = FarmWormReport::query();
|
||
$fieldNameMap = MonitorMode::fieldMap(MonitorMode::TYPE_INSECT);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(MonitorMode::TYPE_INSECT);
|
||
$columnNum = 1;
|
||
break;
|
||
}
|
||
$monitorMode->load('devices');
|
||
$configData = $fieldMap = [];
|
||
|
||
$deviceIds = [];
|
||
foreach($monitorMode->devices as $device){
|
||
if($device->type == Device::TYPE_INSECT){
|
||
$_fields = [
|
||
'worm_num'
|
||
];
|
||
$deviceIds[] = $device->id;
|
||
}else{
|
||
$_fields = explode(',', $device->pivot->fields);
|
||
}
|
||
if($diffDays) {
|
||
$modelQuery = $dayliyReportQuery->whereBetween('reported_at', [$startTime, $endTime]);
|
||
}else{
|
||
$modelQuery = $reportQuery->whereDate('reported_at', $day);
|
||
}
|
||
if($modelQuery){
|
||
$datalist = $modelQuery->where('device_id', $device->id)->get()->keyBy('reported_at')->toArray();
|
||
}
|
||
|
||
//组装数据;
|
||
foreach($_fields as $field){
|
||
$_data = [];
|
||
foreach($xKeys as $key){
|
||
if(!$diffDays) {
|
||
$key = $day.' '. $key.':00';
|
||
}else{
|
||
$key .= ' 00:00:00';
|
||
}
|
||
$_data[] = $datalist[$key][$field] ?? 0;
|
||
}
|
||
$fieldMap[$field] = [
|
||
'name' => $fieldNameMap[$field],
|
||
'unit' => $fieldUnitMap[$field],
|
||
'data' => $_data
|
||
];
|
||
}
|
||
}
|
||
|
||
$k = 0;
|
||
$chartArr = [];
|
||
foreach($fieldMap as $key => $field){
|
||
$k++;
|
||
|
||
//特殊字段,特殊统计图;
|
||
switch($key){
|
||
//点状图;
|
||
case 'wind_direction':
|
||
$yData = ['北风','东北风','东风','东南风','南风','西南风','西风','西北风'];
|
||
$_chartCard = amisMake()->Card()->body(
|
||
amisMake()->Chart()->config(
|
||
Components::make()->chartScatterConfig($field['name'], $xKeys,
|
||
[
|
||
'name'=> $field['name'],
|
||
'type' => 'scatter',
|
||
'symbolSize' => 15,
|
||
'data' => array_map(function($item)use($yData){
|
||
return $yData[$item];
|
||
},$field['data']),
|
||
'color' => '#91CC75',
|
||
], $yData
|
||
)
|
||
)
|
||
);
|
||
break;
|
||
//柱状图;
|
||
case 'worm_num':
|
||
case 'box_noise':
|
||
case 'pm10':
|
||
case 'pm25':
|
||
case 'box_co2':
|
||
$_chartCard = amisMake()->Card()->body(
|
||
amisMake()->Chart()->config(
|
||
Components::make()->chartLineBarConfig($field['name'], $xKeys, [
|
||
[
|
||
'name'=> $field['name'],
|
||
'type' => 'bar',
|
||
'data' => $field['data'],
|
||
'color' => '#91CC75',
|
||
'unit' => $field['unit']
|
||
]
|
||
])
|
||
)
|
||
);
|
||
break;
|
||
default://折线图
|
||
$_chartCard = amisMake()->Card()->body(
|
||
amisMake()->Chart()->config(
|
||
Components::make()->chartLineBarConfig($field['name'], $xKeys, [
|
||
[
|
||
'name'=> $field['name'],
|
||
'type' => 'line',
|
||
'data' => $field['data'],
|
||
'color' => '#91CC75',
|
||
'unit' => $field['unit']
|
||
]
|
||
])
|
||
)
|
||
);
|
||
break;
|
||
}
|
||
|
||
if($k%$columnNum != 0){
|
||
$_chartCard->className('m-r');
|
||
}
|
||
$chartArr[] = $_chartCard;
|
||
}
|
||
$i = 0;
|
||
$len = round(count($chartArr)/$columnNum);
|
||
for($i; $i<=$len; $i++){
|
||
$configData[] = \amisMake()->grid()->columns([
|
||
amisMake()->Flex()->items(array_splice($chartArr, 0, $columnNum)),
|
||
]);
|
||
}
|
||
if($monitorMode->type == MonitorMode::TYPE_INSECT){
|
||
$configData[] = \amisMake()->grid()->columns([//添加照片
|
||
amisMake()->Images()->thumbRatio('16:9')->thumbMode(true)->enlargeAble(true)
|
||
->options(FarmWormPhoto::whereIn('device_id', $deviceIds)->whereBetween('uploaded_at', [$startTime, $endTime])->pluck('url')->toArray())
|
||
]);
|
||
}
|
||
|
||
return $configData;
|
||
}
|
||
|
||
public function getMonitorModeDeviceData($monitorMode, $startTime, $endTime)
|
||
{
|
||
list($day, $diffDays, $xKeys) = $this->makeChartXkeys($startTime, $endTime);
|
||
switch($monitorMode->type){
|
||
case MonitorMode::TYPE_METEOROLOGICAL:
|
||
$dayliyReportQuery = MeteorologicalDailyReport::query();
|
||
$reportQuery = MeteorologicalReport::query();
|
||
$fieldNameMap = MonitorMode::fieldMap(MonitorMode::TYPE_METEOROLOGICAL);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(MonitorMode::TYPE_METEOROLOGICAL);
|
||
break;
|
||
case MonitorMode::TYPE_SOIL:
|
||
$dayliyReportQuery = SoilDailyReport::query();
|
||
$reportQuery = SoilReport::query();
|
||
$fieldNameMap = MonitorMode::fieldMap(MonitorMode::TYPE_SOIL);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(MonitorMode::TYPE_SOIL);
|
||
break;
|
||
case MonitorMode::TYPE_INSECT:
|
||
$dayliyReportQuery = FarmWormReport::query();
|
||
$reportQuery = FarmWormReport::query();
|
||
$fieldNameMap = MonitorMode::fieldMap(MonitorMode::TYPE_INSECT);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(MonitorMode::TYPE_INSECT);
|
||
break;
|
||
}
|
||
$monitorMode->load('devices');
|
||
$fieldMap = [];
|
||
|
||
foreach($monitorMode->devices as $device){
|
||
if($device->type == Device::TYPE_INSECT){
|
||
$_fields = [
|
||
'worm_num'
|
||
];
|
||
$deviceIds[] = $device->id;
|
||
}else{
|
||
$_fields = explode(',', $device->pivot->fields);
|
||
}
|
||
|
||
if($diffDays) {
|
||
$modelQuery = $dayliyReportQuery->whereBetween('reported_at', [$startTime, $endTime]);
|
||
}else{
|
||
$modelQuery = $reportQuery->whereDate('reported_at', $day);
|
||
}
|
||
if($modelQuery){
|
||
$datalist = $modelQuery->where('device_id', $device->id)->get()->keyBy('reported_at')->toArray();
|
||
}
|
||
|
||
//组装数据;
|
||
foreach($_fields as $field){
|
||
$_data = [];
|
||
foreach($xKeys as $key){
|
||
if(!$diffDays) {
|
||
$key = date('Y-m-d').' '. $key.':00';
|
||
}else{
|
||
$key .= ' 00:00:00';
|
||
}
|
||
$_data[] = $datalist[$key][$field] ?? 0;
|
||
}
|
||
$fieldMap[$field] = [
|
||
'name' => $fieldNameMap[$field],
|
||
'unit' => $fieldUnitMap[$field],
|
||
'data' => $_data,
|
||
'xkeys'=> $xKeys
|
||
];
|
||
}
|
||
}
|
||
return $fieldMap;
|
||
}
|
||
|
||
public function meteorologicalControAir(Device $device, MeteorologicalReport $log, Carbon $reportedAt)
|
||
{
|
||
//如果温度或者湿度发生变化;
|
||
if($log->wasChanged('box_temperature') || $log->wasChanged('box_humidity')){
|
||
//获取当前设备关联监测点IDs;
|
||
$monitorIds = MonitorDevice::where('device_id', $device->id)->pluck('monitor_id')->toArray();
|
||
$regionIds = RegionMonitor::where('monitor_id', $monitorIds)->pluck('region_id')->toArray();
|
||
|
||
$regions = Region::whereIn('id', $regionIds)->whereHas('monitorModes', function($q){
|
||
return $q->where('type', MonitorMode::TYPE_AIR);
|
||
})->get();
|
||
|
||
//遍历地点,是否有通风设备;
|
||
foreach($regions as $region){
|
||
$monitorModes = $region->monitorModes()->where('type', MonitorMode::TYPE_AIR)->get();
|
||
foreach($monitorModes as $monitor){
|
||
$_device = $monitor->devices()->where('type', Device::TYPE_AIR)->first();
|
||
|
||
$config = $_device?->extends ?? [];
|
||
|
||
$fieldNameMap = MonitorMode::fieldMap(Device::TYPE_METEOROLOGICAL);
|
||
$fieldUnitMap = MonitorMode::fieldUnitMap(Device::TYPE_METEOROLOGICAL);
|
||
|
||
$airState = $this->getAirStatus($_device);
|
||
|
||
//开启了自动开启配置
|
||
if($config && $config['open_is_enable'] ){
|
||
$rule = $config['open_config'];
|
||
$res = $this->verifyRule($rule, $log);
|
||
|
||
if($res['status'] && !$airState){//如果判定成功,且设备当前是关闭状态
|
||
$msg = '';
|
||
$column = $res['keys'][0];
|
||
if(strpos($column, ',')){//看是否是并联条件
|
||
$_columns = explode(',',$column);
|
||
foreach($_columns as $cc){
|
||
$msg.= $fieldNameMap[$cc].'达到'.$log->$cc.$fieldUnitMap[$cc].'值,且';
|
||
}
|
||
|
||
$msg = mb_substr($msg, 0, -2);
|
||
}else{
|
||
$msg = $fieldNameMap[$column].'达到'.$log->$column.$fieldUnitMap[$column].'值';
|
||
}
|
||
$this->openAir($_device, $msg);
|
||
}
|
||
}
|
||
if($config && $config['close_is_enable'] ){
|
||
$rule = $config['close_config'];
|
||
$res = $this->verifyRule($rule, $log);
|
||
|
||
if($res['status'] && $airState){//如果判定成功,且设备当前是开启状态
|
||
$msg = '';
|
||
$column = $res['keys'][0];
|
||
if(strpos($column, ',')){//看是否是并联条件
|
||
$_columns = explode(',',$column);
|
||
foreach($_columns as $cc){
|
||
$msg.= $fieldNameMap[$cc].'达到'.$log->$cc.$fieldUnitMap[$cc].'值,且';
|
||
}
|
||
|
||
$msg = mb_substr($msg, 0, -2);
|
||
}else{
|
||
$msg = $fieldNameMap[$column].'达到'.$log->$column.$fieldUnitMap[$column].'值';
|
||
}
|
||
$this->closeAir($_device, $msg);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public function verifyRule($rule, $log)
|
||
{
|
||
$res = [
|
||
'status' => false,
|
||
'keys' => []
|
||
];
|
||
if(isset($rule['conjunction'])){//多条件
|
||
switch($rule['conjunction']){
|
||
case 'or':
|
||
if(isset($rule['children'])){
|
||
foreach($rule['children'] as $child){
|
||
$cRes = $this->verifyRule($child, $log);
|
||
if($cRes && $cRes['status']){
|
||
$res['status'] = true;
|
||
$res['keys'] = array_merge($res['keys'], $cRes['keys']);
|
||
return $res;
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
case 'and':
|
||
if(isset($rule['children'])){
|
||
$_keys = [];
|
||
foreach($rule['children'] as $child){
|
||
$cRes = $this->verifyRule($child, $log);
|
||
if($cRes && $cRes['status']){
|
||
$_keys = array_merge($_keys, $cRes['keys']);
|
||
continue;
|
||
}else{
|
||
$res['status'] = false;
|
||
return $res;
|
||
}
|
||
}
|
||
$res['status'] = true;
|
||
$res['keys'][] = implode(',', $_keys);
|
||
}
|
||
break;
|
||
}
|
||
}else{//单条件
|
||
$key = $rule['left']['field'] ?? '';
|
||
$value = $log->$key ?? null;
|
||
switch($rule['op']){
|
||
case 'less':
|
||
break;
|
||
if($value < $rule['right']){
|
||
$res['status'] = true;
|
||
$res['keys'][] = $key;
|
||
}
|
||
case 'less_or_equal':
|
||
if($value <= $rule['right']){
|
||
$res['status'] = true;
|
||
$res['keys'][] = $key;
|
||
}
|
||
break;
|
||
case 'greater':
|
||
if($value > $rule['right']){
|
||
$res['status'] = true;
|
||
$res['keys'][] = $key;
|
||
}
|
||
break;
|
||
case 'greater_or_equal':
|
||
if($value >= $rule['right']){
|
||
$res['status'] = true;
|
||
$res['keys'][] = $key;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
return $res;
|
||
}
|
||
|
||
/**
|
||
* 获取通风设备当前状态
|
||
*/
|
||
public function getAirStatus(Device $device)
|
||
{
|
||
if($device->type != Device::TYPE_AIR){
|
||
return false;
|
||
}
|
||
$res = app(HttpClient::class)->getDeviceStatus($device->sn, ["switch_state"]);
|
||
|
||
if($res && isset($res[0])){
|
||
if($res[0]['value'] == 1){
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 开启通风设备
|
||
*/
|
||
public function openAir(Device $device, string $msg)
|
||
{
|
||
if($device->type != Device::TYPE_AIR){
|
||
return ;
|
||
}
|
||
//记录触发日志
|
||
AirLog::create([
|
||
'device_id' => $device->id,
|
||
'type' => 1,
|
||
'content' => $msg,
|
||
]);
|
||
//编号,-todo
|
||
// $httpClient = app(HttpClient::class);
|
||
// dd($httpClient->deviceDataDownlink($device->sn, 'switch_control', ['switch_state' => 1]));
|
||
return ;
|
||
}
|
||
|
||
/**
|
||
* 关闭通风设备
|
||
*/
|
||
public function closeAir(Device $device, string $msg)
|
||
{
|
||
if($device->type != Device::TYPE_AIR){
|
||
return ;
|
||
}
|
||
AirLog::create([
|
||
'device_id' => $device->id,
|
||
'type' => 2,
|
||
'content' => $msg,
|
||
]);
|
||
//编号,
|
||
// $httpClient = app(HttpClient::class);
|
||
// dd($httpClient->deviceDataDownlink($device->sn, 'switch_control', ['switch_state' => 0]));
|
||
return ;
|
||
}
|
||
}
|