Browse Source

add thermal mode and supply air temperature set logic of ahu

chenhaiyang 4 years ago
parent
commit
a9e42bd4c6

+ 16 - 2
app/api/routers/devices.py

@@ -1,5 +1,7 @@
 from fastapi import APIRouter, Query
 
+from app.controllers.equipment.ahu.supply_air_temperature_set import get_next_supply_air_temperature_set
+from app.controllers.equipment.ahu.thermal_mode import get_thermal_mode
 from app.models.domain.devices import ACATAHThermalModeSetResponse, ACATAHSupplyAirTemperatureSetResponse
 
 
@@ -11,7 +13,13 @@ async def get_acatah_thermal_mode_set(
         project_id: str = Query(..., max_length=50, regex='^Pj', alias='projectId'),
         device_id: str = Query(..., max_length=50, regex='^Eq', alias='equipId')
 ):
-    pass
+    thermal_mode = await get_thermal_mode(project_id, device_id)
+
+    return {
+        'projectId': project_id,
+        'equipId': device_id,
+        'thermalModeSet': thermal_mode
+    }
 
 
 @router.get('/instructions/acatah/supply-air-temperature-set', response_model=ACATAHSupplyAirTemperatureSetResponse)
@@ -19,4 +27,10 @@ async def get_acatah_supply_air_temperature_set(
         project_id: str = Query(..., max_length=50, regex='^Pj', alias='projectId'),
         device_id: str = Query(..., max_length=50, regex='^Eq', alias='equipId')
 ):
-    pass
+    next_supply_air_temperature_set = await get_next_supply_air_temperature_set(project_id, device_id)
+
+    return {
+        'projectId': project_id,
+        'equipId': device_id,
+        'supplyAirTemperatureSet': next_supply_air_temperature_set
+    }

+ 133 - 3
app/controllers/equipment/ahu/supply_air_temperature_set.py

@@ -1,11 +1,15 @@
-from typing import List, Optional
+from typing import List
 
+import arrow
 from httpx import AsyncClient
 from loguru import logger
 
-from app.controllers.equipment.ahu.water_valve_opening import count_vav_box_weight
+from app.controllers.equipment.ahu.thermal_mode import count_vav_box_weight, fetch_status_params
 from app.models.domain.devices import ThermalMode
 from app.schemas.equipment import VAVBox
+from app.services.platform import DataPlatformService, InfoCode
+from app.services.weather import WeatherService
+from app.utils.date import get_time_str, TIME_FMT
 
 
 class ACATAHSupplyAirTemperatureController:
@@ -13,12 +17,22 @@ class ACATAHSupplyAirTemperatureController:
     Supply air temperature setting logic version 2 by WuXu.
     """
 
-    def __init__(self, vav_boxes_list: List[VAVBox], current: float, return_air: float, thermal_mode: ThermalMode):
+    def __init__(
+            self,
+            vav_boxes_list: List[VAVBox],
+            current: float,
+            return_air: float,
+            thermal_mode: ThermalMode,
+            is_off_to_on: bool,
+            is_thermal_mode_switched: bool
+    ):
         super(ACATAHSupplyAirTemperatureController, self).__init__()
         self.vav_boxes_list = vav_boxes_list
         self.current = current
         self.return_air = return_air
         self.thermal_mode = thermal_mode
+        self.is_off_to_on = is_off_to_on
+        self.is_thermal_mode_switched = is_thermal_mode_switched
 
     def calculate_by_cold_vav(self, cold_ratio: float) -> float:
         if self.thermal_mode == ThermalMode.cooling:
@@ -61,3 +75,119 @@ class ACATAHSupplyAirTemperatureController:
 
         return abs(cold / total)
 
+    def build(self) -> float:
+        if not self.is_off_to_on:
+            if self.is_thermal_mode_switched:
+                if self.thermal_mode == ThermalMode.heating:
+                    temperature = self.return_air + 1
+                else:
+                    temperature = self.return_air - 1
+            else:
+                cold_ratio = self.get_cold_ratio()
+                temperature = self.calculate_by_cold_vav(cold_ratio)
+        else:
+            temperature = 31.0
+
+        return temperature
+
+
+class ACATAHSupplyAirTemperatureDefaultController:
+    """
+    Determine supply air temperature when missing data.
+    """
+
+    def __init__(self, is_clear_day: bool):
+        super(ACATAHSupplyAirTemperatureDefaultController, self).__init__()
+        self.is_clear_day = is_clear_day
+
+    def build(self) -> float:
+        now = get_time_str()
+        now_time_str = arrow.get(now, TIME_FMT).time().strftime('%H%M%S')
+        if '080000' <= now_time_str < '100000':
+            is_morning = True
+        else:
+            is_morning = False
+
+        if is_morning:
+            temperature = 27.0
+        else:
+            if self.is_clear_day:
+                temperature = 23.0
+            else:
+                temperature = 25.0
+
+        return temperature
+
+
+async def get_planned(project_id: str, device_id: str) -> float:
+    vav_boxes_params = await fetch_status_params(project_id, device_id)
+    vav_boxes_lit = vav_boxes_params['vav_boxes_list']
+    async with AsyncClient() as client:
+        platform = DataPlatformService(client, project_id)
+
+        current_supply_air_temperature = await platform.get_realtime_supply_air_temperature(device_id)
+        return_air_temperature = await platform.query_realtime_return_air_temperature(device_id)
+
+        hot_water_valve_opening_set_duration = await platform.get_duration(
+            InfoCode.hot_water_valve_opening_set, device_id, 15 * 60
+        )
+        chill_water_valve_opening_set_duration = await platform.get_duration(
+            InfoCode.chill_water_valve_opening_set, device_id, 15 * 60
+        )
+        on_off_set_duration = await platform.get_duration(InfoCode.equip_switch_set, device_id, 15 * 60)
+
+        if hot_water_valve_opening_set_duration[-1]['value'] == 0.0:
+            thermal_mode = ThermalMode.cooling
+        if chill_water_valve_opening_set_duration[-1]['value'] == 0.0:
+            thermal_mode = ThermalMode.heating
+
+        is_off_to_on = False
+        if on_off_set_duration[-1]['value'] == 1.0:
+            for item in on_off_set_duration[::-1]:
+                if item['value'] == 0.0:
+                    is_off_to_on = True
+                    break
+
+        is_thermal_mode_switched = False
+        if len(set([item['value'] for item in hot_water_valve_opening_set_duration])) > 1:
+            is_thermal_mode_switched = True
+        if len(set([item['value'] for item in chill_water_valve_opening_set_duration])) > 1:
+            is_thermal_mode_switched = True
+
+    controller = ACATAHSupplyAirTemperatureController(
+        vav_boxes_lit,
+        current_supply_air_temperature,
+        return_air_temperature,
+        thermal_mode,
+        is_off_to_on,
+        is_thermal_mode_switched
+    )
+    next_supply_air_temperature_set = controller.build()
+
+    return next_supply_air_temperature_set
+
+
+async def get_default(project_id: str) -> float:
+    async with AsyncClient() as client:
+        weather_service = WeatherService(client)
+        realtime_weather = await weather_service.get_realtime_weather(project_id)
+
+        if realtime_weather.get('text') == '晴':
+            is_clear_day = True
+        else:
+            is_clear_day = False
+
+    controller = ACATAHSupplyAirTemperatureDefaultController(is_clear_day)
+    next_supply_air_temperature_ser = controller.build()
+
+    return next_supply_air_temperature_ser
+
+
+@logger.catch()
+async def get_next_supply_air_temperature_set(project_id: str, device_id: str) -> float:
+    try:
+        new = await get_planned(project_id, device_id)
+    except KeyError and IndexError:
+        new = await get_default(project_id)
+
+    return new

+ 84 - 0
app/controllers/equipment/ahu/thermal_mode.py

@@ -0,0 +1,84 @@
+from typing import Dict, List
+
+from httpx import AsyncClient
+from loguru import logger
+
+from app.models.domain.devices import ThermalMode
+from app.schemas.equipment import VAVBox
+from app.services.platform import DataPlatformService
+from app.services.transfer import Duoduo
+
+
+def count_vav_box_weight(realtime: float, target: float) -> float:
+    diff = abs(realtime - target)
+    if diff > 3:
+        weight = 4
+    elif diff > 2:
+        weight = 3
+    elif diff > 1:
+        weight = 2
+    elif diff > 0:
+        weight = 1
+    else:
+        weight = 0
+
+    sign = 1 if realtime - target > 0 else -1
+    return weight * sign
+
+
+class ACATAHThermalModeController:
+    """
+    Decide whether to use cooling or heating mode according to space condition controlled by VAV Box.
+    Writen by WuXu
+    """
+
+    def __init__(self, vav_boxes_list: List[VAVBox]):
+        super(ACATAHThermalModeController, self).__init__()
+        self.vav_boxes_list = vav_boxes_list
+
+    def build(self) -> str:
+        weight = 0.0
+        for box in self.vav_boxes_list:
+            weight += count_vav_box_weight(box.virtual_realtime_temperature, box.virtual_target_temperature)
+
+        if weight > 0:
+            mode = 'cooling'
+        elif weight < 0:
+            mode = 'heating'
+        else:
+            mode = 'hold'
+
+        return mode
+
+
+async def fetch_status_params(project_id: str, device_id: str) -> Dict:
+    async with AsyncClient() as client:
+        platform = DataPlatformService(client, project_id)
+        duoduo = Duoduo(client, project_id)
+
+        relations = await platform.query_relations(from_id=device_id, graph_id='GtControlEquipNetwork001')
+        vav_id_list = [item.get('to_id') for item in relations]
+        vav_boxes_list = []
+        for vav_id in vav_id_list:
+            virtual_realtime_temperature = await duoduo.query_device_virtual_data(
+                vav_id,
+                'VirtualRealtimeTemperature'
+            )
+            virtual_temperature_target = await duoduo.query_device_virtual_data(vav_id, 'TargetTemperatureSet')
+            vav_params = {
+                'id': vav_id,
+                'virtual_realtime_temperature': virtual_realtime_temperature,
+                'virtual_target_temperature': virtual_temperature_target
+            }
+            vav = VAVBox(**vav_params)
+            vav_boxes_list.append(vav)
+
+        return {'vav_boxes_list': vav_boxes_list}
+
+
+async def get_thermal_mode(project_id: str, device_id: str) -> ThermalMode:
+    prams = await fetch_status_params(project_id, device_id)
+    controller = ACATAHThermalModeController(prams.get('vav_boxes_list'))
+    mode = controller.build()
+
+    return ThermalMode(mode)

+ 0 - 48
app/controllers/equipment/ahu/water_valve_opening.py

@@ -1,48 +0,0 @@
-from typing import List, Optional
-
-from httpx import AsyncClient
-from loguru import logger
-
-from app.schemas.equipment import VAVBox
-from app.services.platform import DataPlatformService, InfoCode
-
-
-def count_vav_box_weight(realtime: float, target: float) -> float:
-    diff = abs(realtime - target)
-    if diff > 3:
-        weight = 4
-    elif diff > 2:
-        weight = 3
-    elif diff > 1:
-        weight = 2
-    elif diff > 0:
-        weight = 1
-    else:
-        weight = 0
-
-    return weight * (realtime - target)
-
-
-class ACATAHThermalModeController:
-    """
-    Decide whether to use cooling or heating mode according to space condition controlled by VAV Box.
-    Writen by WuXu
-    """
-
-    def __init__(self, vav_boxes_list: List[VAVBox]):
-        super(ACATAHThermalModeController, self).__init__()
-        self.vav_boxes_list = vav_boxes_list
-
-    def build(self) -> str:
-        weight = 0.0
-        for box in self.vav_boxes_list:
-            weight += count_vav_box_weight(box.virtual_realtime_temperature, box.virtual_target_temperature)
-
-        if weight > 0:
-            mode = 'cooling'
-        elif weight < 0:
-            mode = 'heating'
-        else:
-            mode = 'hold'
-
-        return mode

+ 2 - 3
app/models/domain/devices.py

@@ -1,5 +1,4 @@
 from enum import Enum
-from typing import Optional
 
 from pydantic import BaseModel
 
@@ -16,8 +15,8 @@ class DevicesInstructionsBaseResponse(BaseModel):
 
 
 class ACATAHThermalModeSetResponse(DevicesInstructionsBaseResponse):
-    thermal_mode_set: ThermalMode
+    thermalModeSet: ThermalMode
 
 
 class ACATAHSupplyAirTemperatureSetResponse(DevicesInstructionsBaseResponse):
-    supply_air_temperature_set: float
+    supplyAirTemperatureSet: float

+ 29 - 1
app/services/platform.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from enum import Enum
-from typing import Dict, List, Tuple
+from typing import Dict, List, Optional, Tuple
 
 import arrow
 import numpy as np
@@ -31,6 +31,9 @@ class InfoCode(str, Enum):
     running_status = 'RunStatus'
     cloud_status = 'InCloudStatus'
     equip_switch_set = 'EquipSwitchSet'
+    return_air_temperature = 'ReturnAirTemp'
+    chill_water_valve_opening_set = 'ChillWaterValveOpeningSet'
+    hot_water_valve_opening_set = 'HotWaterValveOpeningSet'
 
 
 class DataPlatformService(Service):
@@ -151,6 +154,28 @@ class DataPlatformService(Service):
 
         return value
 
+    async def query_relations(
+            self,
+            from_id: Optional[str] = None,
+            graph_id: Optional[str] = None,
+            relation_type: Optional[str] = None
+    ) -> List[Dict]:
+        url = self._base_url.join('data-platform-3/relation/query')
+        params = self._common_parameters()
+        criteria = dict()
+        if from_id:
+            criteria.update({'from_id': from_id})
+        if graph_id:
+            criteria.update({'graph_id': graph_id})
+        if relation_type:
+            criteria.update({'relation_type': relation_type})
+        payload = {
+            'criteria': criteria
+        }
+        raw_info = await self._post(url, params, payload)
+
+        return raw_info.get('Content')
+
     async def get_realtime_temperature(self, space_id: str) -> float:
         return await self.get_realtime_data(InfoCode.temperature, space_id)
 
@@ -237,6 +262,9 @@ class DataPlatformService(Service):
     async def get_cloud_status(self, equipment_id: str) -> float:
         return await self.get_realtime_data(InfoCode.cloud_status, equipment_id)
 
+    async def query_realtime_return_air_temperature(self, device_id: str) -> float:
+        return await self.get_realtime_data(InfoCode.return_air_temperature, device_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()

+ 24 - 1
app/services/transfer.py

@@ -7,7 +7,6 @@ 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
@@ -263,3 +262,27 @@ class Duoduo(Service):
         }
 
         return result
+
+    async def query_device_virtual_data(self, device_id: str, info_code: str) -> float:
+        url = self._base_url.join('duoduo-service/review-service/equipment/order/query')
+        payload = {
+            'criteria': {
+                'projectId': self._project_id,
+                'objectId': device_id,
+                'date': arrow.get(self._now_time, TIME_FMT).date().strftime('%Y%m%d'),
+                'funcId': info_code
+            }
+        }
+        raw_info = await self._post(url, payload=payload)
+
+        try:
+            latest_data = raw_info.get('data')[-1].get('value')
+            latest_time = raw_info.get('data')[-1].get('realTime')
+            if arrow.get(latest_time, TIME_FMT).shift(minutes=15) < arrow.get(self._now_time, TIME_FMT):
+                value = np.NAN
+            else:
+                value = latest_data
+        except KeyError:
+            value = np.NAN
+
+        return value