Browse Source

add ahu control

chenhaiyang 4 years ago
parent
commit
54de27cf67

+ 0 - 0
app/controllers/equipment/ahu/__init__.py


+ 116 - 0
app/controllers/equipment/ahu/basic.py

@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+
+from typing import Dict, List, Optional
+import numpy as np
+from httpx import AsyncClient
+from loguru import logger
+
+from app.controllers.events import ahu_supply_air_temp_set_dict
+from app.schemas.equipment import AHU
+from app.schemas.system import ACAT
+from app.services.platform import DataPlatformService, InfoCode
+from app.services.transfer import ReviewService, EquipmentInfoService
+from app.services.weather import WeatherService
+
+
+class AHUController:
+
+    def __init__(self, equipment: Optional[AHU] = None, system: Optional[ACAT] = None):
+        super(AHUController, self).__init__()
+        self._equipment = equipment
+        self._system = system
+        self._supply_air_temperature_command_dict = ahu_supply_air_temp_set_dict.get('dataframe')
+
+    async def build_freq_set(self, supply_air_temperature_set_duration: List, hot_rate: float) -> float:
+        extent = 5
+        if np.isnan(self._equipment.fan_freq_set)\
+                or np.isnan(self._system.supply_static_press)\
+                or np.isnan(self._system.supply_static_press_set):
+            temp_freq_set = 80.0
+        else:
+            pre_fan_freq_set = self._equipment.fan_freq_set
+
+            if self._system.supply_static_press < self._system.supply_static_press_set - extent:
+                temp_freq_set = pre_fan_freq_set + 2
+            elif self._system.supply_static_press > self._system.supply_static_press_set + extent:
+                temp_freq_set = pre_fan_freq_set - 2
+            else:
+                temp_freq_set = pre_fan_freq_set
+
+        temperature_value_list = np.array([item['value'] for item in supply_air_temperature_set_duration])
+        if temperature_value_list.size > 0\
+                and np.all(temperature_value_list == temperature_value_list[0])\
+                and temperature_value_list[0] <= 18.0\
+                and hot_rate >= 0.5:
+            freq_set = 100.0
+        else:
+            freq_set = min(temp_freq_set, 90.0)
+
+        return freq_set
+
+    async def build_supply_air_temperature_set(self, outdoor_temperature: float) -> float:
+        command_df = self._supply_air_temperature_command_dict
+        temp_command = command_df.loc[command_df['outdoor_temperature'] == outdoor_temperature]['temperature_set']
+        if len(temp_command) > 0:
+            command = float(temp_command)
+        else:
+            command = 20.0
+
+        return command
+
+
+@logger.catch()
+async def get_freq_controlled(project_id: str, equipment_id: str) -> None:
+    async with AsyncClient() as client:
+        platform = DataPlatformService(client, project_id)
+        review_service = ReviewService(client, project_id)
+        # equip_service = EquipmentInfoService(client, project_id)
+
+        fan_freq_set = await platform.get_realtime_fan_freq_set(equipment_id)
+        # system = await equip_service.get_system_by_equipment(equipment_id)
+        system = ['Sy1101050030eab54ad66b084a1cb1b919950b263280']
+        press = await platform.get_realtime_supply_static_press(system[0])
+        press_set = await platform.get_realtime_supply_static_press_set(system[0])
+        hot_rate = await review_service.get_fill_rate()
+        supply_air_temperature_set = await platform.get_duration(
+            InfoCode.supply_air_temperature_set,
+            equipment_id,
+            30 * 60
+        )
+
+    equip_params = {
+        'id': equipment_id,
+        'fan_freq_set': fan_freq_set
+    }
+    ahu = AHU(**equip_params)
+
+    system_params = {
+        'id': system[0],
+        'supply_static_press': press,
+        'supply_static_press_set': press_set
+    }
+    acat_system = ACAT(**system_params)
+
+    ahu_controller = AHUController(ahu, acat_system)
+    new_freq_set = await ahu_controller.build_freq_set(supply_air_temperature_set, hot_rate)
+    logger.debug(f'freq set: {new_freq_set}')
+
+    async with AsyncClient() as client:
+        platform = DataPlatformService(client, project_id)
+        await platform.set_code_value(equipment_id, InfoCode.fan_freq_set, new_freq_set)
+
+
+@logger.catch()
+async def get_supply_air_temperature_controlled(project_id: str, equipment_id: str) -> None:
+    async with AsyncClient() as client:
+        weather_service = WeatherService(client)
+        realtime_weather = await weather_service.get_realtime_weather(project_id)
+        outdoor_temperature = realtime_weather.get('temperature')
+
+    ahu_controller = AHUController()
+    new_supply_air_temperature_set = await ahu_controller.build_supply_air_temperature_set(outdoor_temperature)
+    logger.debug(f'supply air temperature set: {new_supply_air_temperature_set}')
+
+    async with AsyncClient() as client:
+        platform = DataPlatformService(client, project_id)
+        await platform.set_code_value(equipment_id, InfoCode.supply_air_temperature_set, new_supply_air_temperature_set)

+ 30 - 0
app/controllers/equipment/events.py

@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+
+from fastapi_utils.tasks import repeat_every
+
+from app.controllers.equipment.ahu.basic import get_freq_controlled, get_supply_air_temperature_controlled
+
+
+@repeat_every(seconds=5)
+async def regulate_ahu_freq():
+    _PROJECT_ID = 'Pj1101050030'
+    _AHU_LIST = [
+        'Eq1101050030b6b2f1db3d6944afa71e213e0d45d565',
+        'Eq1101050030846e0a94670842109f7c8d8db0d44cf5'
+    ]
+
+    for ahu in _AHU_LIST:
+        await get_freq_controlled(_PROJECT_ID, ahu)
+
+
+@repeat_every(seconds=5)
+async def regulate_ahu_supply_air_temperature():
+    _PROJECT_ID = 'Pj1101050030'
+    _AHU_LIST = [
+        'Eq1101050030b6b2f1db3d6944afa71e213e0d45d565',
+        'Eq1101050030846e0a94670842109f7c8d8db0d44cf5'
+    ]
+
+    for ahu in _AHU_LIST:
+        await get_supply_air_temperature_controlled(_PROJECT_ID, ahu)
+

+ 8 - 0
app/controllers/events.py

@@ -1,10 +1,12 @@
 # -*- coding: utf-8 -*-
 
+import pandas as pd
 import scipy.io as sio
 
 from app.core.config import settings
 
 q_learning_models = {}
+ahu_supply_air_temp_set_dict = {}
 
 
 async def load_q_learning_model():
@@ -13,3 +15,9 @@ async def load_q_learning_model():
     winter_model_path = f'{base_path}/app/resources/ml_models/equipment/fcu/q_learning/net_1_winter.mat'
     q_learning_models.update({'summer': sio.loadmat(summer_model_path)['net'][0, 0][0]})
     q_learning_models.update({'winter': sio.loadmat(winter_model_path)['net'][0, 0][0]})
+
+
+async def load_ahu_supply_temperature_set_dict():
+    base_path = settings.PROJECT_DIR
+    data_path = f'{base_path}/app/resources/ml_models/equipment/ahu/supply_temp_set_dict.csv'
+    ahu_supply_air_temp_set_dict.update({'dataframe': pd.read_csv(data_path)})

+ 1 - 0
app/core/config.py

@@ -12,6 +12,7 @@ class Settings(BaseSettings):
     PLATFORM_HOST: AnyHttpUrl
     PLATFORM_SECRET: SecretStr
     TRANSFER_HOST: AnyHttpUrl
+    WEATHER_HOST: AnyHttpUrl
 
     PROJECT_DIR: DirectoryPath
     LOGS_DIR: DirectoryPath

+ 5 - 1
app/core/events.py

@@ -4,11 +4,15 @@ from typing import Callable, Optional
 
 from fastapi import FastAPI
 
-from app.controllers.events import load_q_learning_model
+from app.controllers.events import load_q_learning_model, load_ahu_supply_temperature_set_dict
+from app.controllers.equipment.events import regulate_ahu_freq, regulate_ahu_supply_air_temperature
 
 
 def create_start_app_handler(app: Optional[FastAPI] = None) -> Callable:
     async def start_app() -> None:
         await load_q_learning_model()
+        await load_ahu_supply_temperature_set_dict()
+        # await regulate_ahu_supply_air_temperature()
+        # await regulate_ahu_freq()
 
     return start_app

+ 24 - 0
app/resources/ml_models/equipment/ahu/supply_temp_set_dict.csv

@@ -0,0 +1,24 @@
+outdoor_temperature,temperature_set
+0,13,22
+1,14,22
+2,15,22
+3,16,22
+4,17,19
+5,18,22
+6,19,22
+7,20,22
+8,21,21
+9,22,20
+10,23,18
+11,24,18
+12,25,21
+13,26,20
+14,27,20
+15,28,20
+16,29,20
+17,30,20
+18,31,18
+19,32,20
+20,33,20
+21,34,18
+22,35,20

+ 6 - 0
app/schemas/equipment.py

@@ -33,3 +33,9 @@ class VAVBox(BaseEquipment):
     supply_air_flow_set: Optional[float]
     supply_air_flow_lower_limit: Optional[float]
     supply_air_flow_upper_limit: Optional[float]
+
+
+class AHU(BaseModel):
+    id: str
+    fan_freq_set: float
+    supply_air_temperature_set: Optional[float]

+ 15 - 0
app/schemas/system.py

@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class BaseSystem(BaseModel):
+    id: str
+
+
+class ACAT(BaseSystem):
+    id: str
+    supply_static_press: float
+    supply_static_press_set: float

+ 64 - 1
app/services/platform.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from enum import Enum
-from typing import Dict, Tuple
+from typing import Dict, List, Tuple
 
 import arrow
 import numpy as np
@@ -22,7 +22,12 @@ class InfoCode(str, Enum):
     humidity = 'RH'
     supply_air_flow = 'SupplyAirFlow'
     supply_air_temperature = 'SupplyAirTemp'
+    supply_air_temperature_set = 'SupplyAirTempSet'
     fan_speed = 'FanGear'
+    fan_freq = 'FanFreq'
+    fan_freq_set = 'FanFreqSet'
+    supply_static_press = 'SupplyStaticPress'
+    supply_static_press_set = 'SupplyStaticPressSet'
 
 
 class DataPlatformService(Service):
@@ -71,6 +76,41 @@ class DataPlatformService(Service):
 
         return value
 
+    async def get_duration(self, code: InfoCode, object_id: str, duration: int) -> List[Dict]:
+        url = self._base_url.join('data-platform-3/hisdata/query_by_obj')
+        params = self._common_parameters()
+        start_time = get_time_str(duration, flag='ago')
+        payload = {
+            'criteria': {
+                'id': object_id,
+                'code': code.value,
+                'receivetime': {
+                    '$gte': start_time,
+                    '$lte': self._now_time,
+                }
+            }
+        }
+        raw_info = await self._post(url, params, payload)
+
+        try:
+            content = raw_info.get('Content')
+            latest_time = content[-1].get('receivetime')
+            if arrow.get(latest_time, TIME_FMT).shift(minutes=15) < arrow.get(self._now_time, TIME_FMT):
+                result = []
+                logger.info(f'delayed data - {object_id}: ({latest_time})')
+            else:
+                result = [
+                    {
+                        'timestamp': item['receivetime'],
+                        'value': item['data']
+                    }
+                    for item in content
+                ]
+        except KeyError:
+            result = []
+
+        return result
+
     async def get_past_data(self, code: InfoCode, object_id: str, interval: int) -> float:
         """
         Query past data from data platform.
@@ -132,6 +172,9 @@ class DataPlatformService(Service):
     async def get_realtime_supply_air_temperature(self, equipment_id: str) -> float:
         return await self.get_realtime_data(InfoCode.supply_air_temperature, equipment_id)
 
+    async def get_realtime_supply_air_temperature_set(self, equipment_id: str) -> float:
+        return await self.get_realtime_data(InfoCode.supply_air_temperature_set, equipment_id)
+
     async def get_fan_speed(self, equipment_id: str) -> float:
         return await self.get_realtime_data(InfoCode.fan_speed, equipment_id)
 
@@ -165,3 +208,23 @@ class DataPlatformService(Service):
             upper = 2000.0
 
         return lower, upper
+
+    async def get_realtime_fan_freq_set(self, equipment_id: str) -> float:
+        return await self.get_realtime_data(InfoCode.fan_freq_set, equipment_id)
+
+    async def get_realtime_supply_static_press(self, system_id: str) -> float:
+        return await self.get_realtime_data(InfoCode.supply_static_press, system_id)
+
+    async def get_realtime_supply_static_press_set(self, system_id: str) -> float:
+        return await self.get_realtime_data(InfoCode.supply_static_press_set, system_id)
+
+    async def set_code_value(self, object_id: str, code: InfoCode, value: float):
+        url = self._base_url.join('data-platform-3/parameter/setting')
+        params = self._common_parameters()
+        payload = {
+            'id': object_id,
+            'code': code.value,
+            'value': value
+        }
+
+        await self._post(url, params, payload)

+ 51 - 0
app/services/transfer.py

@@ -7,6 +7,7 @@ import arrow
 import numpy as np
 import pandas as pd
 from httpx import AsyncClient, URL
+from loguru import logger
 
 from app.core.config import settings
 from app.services.service import Service
@@ -209,3 +210,53 @@ class EquipmentInfoService(Service):
                 result.append({'id': sp.get('id')})
 
         return result
+
+    async def get_system_by_equipment(self, equipment_id: str) -> List:
+        url = self._base_url.join('duoduo-service/object-service/object/system/findForCompose')
+        params = {
+            'projectId': self._project_id,
+            'equipmentId': equipment_id
+        }
+        raw_info = await self._post(url, params)
+
+        system_list = []
+        for sy in raw_info.get('data'):
+            system_list.append({'id': sy.get('id')})
+
+        return system_list
+
+
+class ReviewService(Service):
+
+    def __init__(self, client: AsyncClient, project_id: str, server_settings=settings):
+        super(ReviewService, self).__init__(client)
+        self._project_id = project_id
+        self._base_url = URL(server_settings.TRANSFER_HOST)
+        self._now_time = get_time_str()
+
+    async def get_fill_rate(self):
+        url = self._base_url.join('duoduo-service/review-service/space/report/quarter/query')
+        payload = {
+            'criteria': {
+                'projectId': self._project_id,
+                'date': arrow.get(self._now_time, TIME_FMT).date().strftime('%Y%m%d')
+            },
+            'orders': [
+                {
+                    'column': 'time',
+                    'asc': False
+                }
+            ],
+            'page': 1,
+            'size': 1
+        }
+
+        raw_info = await self._post(url, payload=payload)
+        try:
+            content = raw_info.get('content')[-1]
+            hot_rate = (content.get('hotNum')
+                        / (content.get('normalNum') + content.get('hotNum') + content.get('coldNum')))
+        except KeyError and ZeroDivisionError:
+            hot_rate = 0.0
+
+        return hot_rate

+ 39 - 0
app/services/weather.py

@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+
+import json
+from typing import Dict
+
+from httpx import AsyncClient, URL
+
+from app.core.config import settings
+from app.services.service import Service
+from app.utils.date import get_time_str
+
+
+class WeatherService(Service):
+
+    def __init__(self, client: AsyncClient, server_settings=settings):
+        super(WeatherService, self).__init__(client)
+        self._base_url = URL(server_settings.WEATHER_HOST)
+        self._now_time = get_time_str()
+
+    async def get_realtime_weather(self, project_id: str) -> Dict:
+        url = self._base_url.join('EMS_Weather/Spring/MVC/entrance/unifier/CommonCityHourWeatherService')
+        headers = {
+            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
+            'Connection': 'close'
+        }
+        params = {
+            'cityCode': project_id[2:8],
+            'startDate': get_time_str(60 * 60, flag='ago'),
+            'endDate': self._now_time,
+            'minResponse': True
+        }
+
+        raw_info = await self._post(url, params={'jsonString': json.dumps(params)}, headers=headers)
+        try:
+            result = raw_info.get('content')[-1]
+        except KeyError:
+            result = {}
+
+        return result