Browse Source

add AHU logic for Zhejiang

highing666 2 years ago
parent
commit
5212f3546d

+ 19 - 7
app/api/routers/devices.py

@@ -6,6 +6,7 @@ from sqlalchemy.orm import Session
 import app.models.domain.devices as domain_devices
 from app.api.dependencies.db import get_db
 from app.controllers.equipment.ahu.basic import build_acatah_freq_set
+from app.controllers.equipment.ahu.common import build_acatah_instructions
 from app.controllers.equipment.ahu.supply_air_temperature_set import (
     build_acatah_supply_air_temperature_set,
 )
@@ -41,7 +42,7 @@ router = APIRouter()
     response_model=domain_devices.ACATAHThermalModeSetResponse,
 )
 async def get_acatah_thermal_mode_set_v2(
-    params: domain_devices.ACATAHThermalModeSetRequest,
+        params: domain_devices.ACATAHThermalModeSetRequest,
 ):
     thermal_mode = build_acatah_thermal_mode_set(params)
 
@@ -56,7 +57,7 @@ async def get_acatah_thermal_mode_set_v2(
     response_model=domain_devices.ACATAHSupplyAirTempSetResponse,
 )
 async def get_acatah_supply_air_temperature_set_v2(
-    params: domain_devices.ACATAHSupplyAirTempSetRequest,
+        params: domain_devices.ACATAHSupplyAirTempSetRequest,
 ):
     supply_air_temperature_set = build_acatah_supply_air_temperature_set(params)
 
@@ -100,6 +101,17 @@ async def get_acatah_switch_set(params: domain_devices.ACATAHSwitchSetRequest):
 
 
 @router.post(
+    "/instructions/acatah", response_model=domain_devices.ACATAHInstructionsResponse
+)
+async def get_acatah_instructions(params: domain_devices.ACATAHInstructionsRequest):
+    instructions = await build_acatah_instructions(params)
+
+    logger.info(f"{params.device_id}: {instructions}")
+
+    return instructions
+
+
+@router.post(
     "/instructions/acvtsf/switch-set",
     response_model=domain_devices.ACVTSFSwitchSetResponse,
 )
@@ -134,7 +146,7 @@ async def get_acatfu_switch_set(params: domain_devices.ACATFUSwitchSetRequest):
     response_model=domain_devices.ACATFUSupplyAirTempSetResponse,
 )
 async def get_acatfu_supply_air_temperature_set_v2(
-    params: domain_devices.ACATFUSupplyAirTempSetRequest,
+        params: domain_devices.ACATFUSupplyAirTempSetRequest,
 ):
     supply_air_temperature_set = build_acatfu_supply_air_temperature(params)
 
@@ -163,8 +175,8 @@ async def get_acatfu_freq_set(params: domain_devices.ACATFUFreqSetRequest):
     response_model=domain_devices.ACATFCEarlyStartPredictionResponse,
 )
 async def get_acatfc_early_start_prediction(
-    params: domain_devices.ACATFCEarlyStartPredictionRequest,
-    db: Session = Depends(get_db),
+        params: domain_devices.ACATFCEarlyStartPredictionRequest,
+        db: Session = Depends(get_db),
 ):
     minutes = await build_acatfc_early_start_prediction(params, db)
 
@@ -180,7 +192,7 @@ async def get_acatfc_early_start_prediction(
     "/instructions/acatvi", response_model=domain_devices.ACATVIInstructionsResponse
 )
 async def get_acatvi_instructions(
-    params: domain_devices.ACATVIInstructionsRequest, db: Session = Depends(get_db)
+        params: domain_devices.ACATVIInstructionsRequest, db: Session = Depends(get_db)
 ):
     instructions = await build_acatvi_instructions(params, db)
 
@@ -300,7 +312,7 @@ async def get_acatva_instructions(params: domain_devices.ACATVAInstructionsReque
     response_model=domain_devices.ACATVAInstructionsResponseV2,
 )
 async def get_acatva_instructions_v2(
-    params: domain_devices.ACATVAInstructionsRequestV2,
+        params: domain_devices.ACATVAInstructionsRequestV2,
 ):
     instructions = await build_acatva_instructions_for_JM(params)
 

+ 179 - 0
app/controllers/equipment/ahu/common.py

@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+
+
+from typing import List, Tuple
+
+import numpy as np
+from loguru import logger
+
+from app.models.domain.devices import ACATAHInstructionsRequest
+from app.schemas.equipment import AHU
+from app.schemas.season import Season
+from app.schemas.space import SpaceATAH
+from app.utils.date import get_time_str
+
+
+class ATAHController:
+    def __init__(self, ahu: AHU, spaces: List[SpaceATAH], season: Season) -> None:
+        self._ahu = ahu
+        self._spaces = spaces
+        self._season = season
+
+    def get_switch_set(self) -> str:
+        switch_set = "off"
+        for sp in self._spaces:
+            if not np.isnan(sp.temperature_target):
+                switch_set = "on"
+
+        return switch_set
+
+    @staticmethod
+    def get_return_air_temp_set(virtual_target: float) -> float:
+        return virtual_target
+
+    def get_supply_air_temp_set(self, next_switch_set: str, virtual_realtime: float) -> float:
+        if next_switch_set == "on" and not self._ahu.running_status:
+            if self._season == Season.cooling:
+                next_supply_set = 20.0
+            elif self._season == Season.transition:
+                next_supply_set = 22.0
+            else:
+                next_supply_set = 28.0
+        else:
+            next_supply_set = self._ahu.supply_air_temperature_set
+            diff = self._ahu.return_air_temperature - virtual_realtime
+            if self._season == Season.cooling:
+                if diff > 1.0 and self._ahu.fan_freq >= self._ahu.fan_freq_upper_limit_set:
+                    next_supply_set -= 1.0
+                if diff < -1.0 and self._ahu.fan_freq <= self._ahu.fan_freq_lower_limit_set:
+                    next_supply_set += 1.0
+                next_supply_set = np.max([np.min([23.0, next_supply_set]), 18.0])
+                logger.debug(next_supply_set)
+            elif self._season == Season.heating:
+                if diff > 1.0 and self._ahu.fan_freq <= self._ahu.fan_freq_lower_limit_set:
+                    next_supply_set -= 1.0
+                if diff < 1.0 and self._ahu.fan_freq >= self._ahu.fan_freq_upper_limit_set:
+                    next_supply_set += 1.0
+                next_supply_set = np.max([np.min([28.0, next_supply_set]), 22.0])
+
+        return next_supply_set
+
+    def get_freq_set(self, next_switch_set: str, virtual_target: float) -> float:
+        if next_switch_set == "on" and not self._ahu.running_status:
+            next_freq_set = 40.0
+        else:
+            diff = self._ahu.return_air_temperature - virtual_target
+            if self._season == Season.heating:
+                if diff > 1.0:
+                    adjust = -2.0
+                elif diff < -1.0:
+                    adjust = 2.0
+                else:
+                    adjust = 0.0
+            elif self._season == Season.cooling:
+                if diff > 1.0:
+                    adjust = 2.0
+                elif diff < 1.0:
+                    adjust = -2.0
+                else:
+                    adjust = 0.0
+            else:
+                if diff > 1.0:
+                    if self._ahu.supply_air_temperature > self._ahu.return_air_temperature:
+                        adjust = -2.0
+                    elif self._ahu.supply_air_temperature < self._ahu.return_air_temperature - 1.0:
+                        adjust = 2.0
+                    else:
+                        adjust = 1.0
+                elif diff < -1.0:
+                    if self._ahu.supply_air_temperature_set < self._ahu.return_air_temperature:
+                        adjust = -2.0
+                    elif self._ahu.supply_air_temperature > self._ahu.return_air_temperature + 1:
+                        adjust = 2.0
+                    else:
+                        adjust = 1.0
+                else:
+                    adjust = 0.0
+
+            next_freq_set = self._ahu.fan_freq_set + adjust
+
+        next_freq_set = np.max(
+            [np.min([self._ahu.fan_freq_upper_limit_set, next_freq_set]), self._ahu.fan_freq_lower_limit_set])
+
+        return next_freq_set
+
+    def get_valid_spaces(self) -> List:
+        valid_spaces = list()
+        for sp in self._spaces:
+            if not np.isnan(sp.realtime_temperature) and not np.isnan(sp.temperature_target):
+                valid_spaces.append(sp)
+
+        return valid_spaces
+
+    def build_virtual_temperature(self) -> Tuple[float, float]:
+        valid_spaces = self.get_valid_spaces()
+
+        if not valid_spaces:
+            virtual_realtime, virtual_target = np.NAN, np.NAN
+        else:
+            sorted_spaces = sorted(valid_spaces, key=lambda x: x.ahu_temporary_update_time)
+            if sorted_spaces[-1].ahu_temporary_update_time > get_time_str(60 * 60 * 2, flag="ago"):
+                virtual_realtime = sorted_spaces[-1].realtime_temperature
+                virtual_target = sorted_spaces[-1].temperature_target
+            else:
+                virtual_realtime, virtual_target = 0.0, 0.0
+                total_weight = 0.0
+                for sp in valid_spaces:
+                    weight = sp.ahu_default_weight
+                    virtual_realtime += sp.realtime_temperature * weight
+                    virtual_target += sp.temperature_target * weight
+                    total_weight += weight
+
+                if total_weight == 0:
+                    for sp in valid_spaces:
+                        virtual_realtime += sp.realtime_temperature
+                        virtual_target += sp.temperature_target
+                    valid_spaces_length = len(valid_spaces)
+                    virtual_realtime /= valid_spaces_length
+                    virtual_target /= valid_spaces_length
+                else:
+                    virtual_realtime /= total_weight
+                    virtual_target /= total_weight
+
+        return virtual_realtime, virtual_target
+
+    def run(self) -> Tuple[str, float, float, float]:
+        virtual_realtime, virtual_target = self.build_virtual_temperature()
+        new_switch_set = self.get_switch_set()
+        if np.isnan(self._ahu.return_air_temperature_set):
+            new_freq_set = self.get_freq_set(new_switch_set, virtual_target)
+            new_return_air_temp_set = np.NAN
+        else:
+            new_return_air_temp_set = self.get_return_air_temp_set(virtual_target)
+            new_freq_set = np.NAN
+        new_supply_air_temp_set = self.get_supply_air_temp_set(new_switch_set, virtual_realtime)
+
+        return new_switch_set, new_return_air_temp_set, new_freq_set, new_supply_air_temp_set
+
+
+@logger.catch()
+async def build_acatah_instructions(params: ACATAHInstructionsRequest):
+    space_params = []
+    for sp in params.spaces:
+        temp_sp = SpaceATAH(**sp.dict())
+        temp_sp.diff = temp_sp.temperature_target - temp_sp.realtime_temperature
+        space_params.append(temp_sp)
+
+    ahu = AHU(**params.dict())
+    ahu_controller = ATAHController(ahu, space_params, params.season)
+    switch_set, return_air_temp_set, freq_set, supply_air_temp_set = ahu_controller.run()
+
+    instructions = {"switch_set": switch_set}
+    if not np.isnan(return_air_temp_set):
+        instructions.update({"return_air_temp_set": return_air_temp_set})
+    if not np.isnan(freq_set):
+        instructions.update({"freq_set": freq_set})
+    if not np.isnan(supply_air_temp_set):
+        instructions.update({"supply_air_temp_set": supply_air_temp_set})
+
+    return instructions

+ 4 - 4
app/controllers/equipment/vav.py

@@ -18,7 +18,7 @@ from app.models.domain.devices import (
 from app.schemas.equipment import VAVBox, FCU
 from app.schemas.instructions import ACATVAInstructions
 from app.schemas.sapce_weight import SpaceWeight
-from app.schemas.space import Space
+from app.schemas.space import SpaceATVA
 from app.services.platform import DataPlatformService
 from app.services.transfer import Duoduo, SpaceInfoService, Season
 from app.utils.date import get_time_str
@@ -344,7 +344,7 @@ async def fetch_vav_control_params(project_id: str, equipment_id: str) -> Dict:
                 "temperature_target": current_target,
                 "diff": current_target - realtime_temperature,
             }
-            space = Space(**temp_space_params)
+            space = SpaceATVA(**temp_space_params)
             space_objects.append(space)
 
         realtime_supply_air_temperature = np.array(
@@ -401,7 +401,7 @@ async def build_acatva_instructions(
 ) -> ACATVAInstructions:
     space_params = []
     for sp in params.spaces:
-        temp_sp = Space(**sp.dict())
+        temp_sp = SpaceATVA(**sp.dict())
         temp_sp.diff = temp_sp.temperature_target - temp_sp.realtime_temperature
         space_params.append(temp_sp)
 
@@ -442,7 +442,7 @@ async def build_acatva_instructions_for_JM(
 
     space_params = []
     for sp in params.spaces:
-        temp_sp = Space(**sp.dict())
+        temp_sp = SpaceATVA(**sp.dict())
         if temp_sp.temperature_target:
             temp_sp.diff = temp_sp.temperature_target - temp_sp.realtime_temperature
         else:

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

@@ -6,7 +6,6 @@ from pydantic import BaseModel, Field
 from app.controllers.equipment.switch import SwitchSet
 from app.models.domain.feedback import FeedbackValue
 from app.schemas.season import Season
-from app.schemas.space import SpaceATFU
 
 
 class ThermalMode(str, Enum):
@@ -123,15 +122,29 @@ class ACATFCEarlyStartPredictionResponse(BaseModel):
 
 class Space(BaseModel):
     realtime_temperature: float
+
+
+class ACATVASpace(Space):
     temperature_target: float
     vav_default_weight: float
     vav_temporary_weight: float
     vav_temporary_update_time: str
 
 
+class ACATAHSpace(Space):
+    temperature_target: float
+    ahu_default_weight: float
+    ahu_temporary_weight: float
+    ahu_temporary_update_time: str
+
+
+class ACATFUSpace(Space):
+    realtime_co2: float
+
+
 class ACATVAInstructionsRequestBase(BaseModel):
     device_id: str
-    spaces: List[Space]
+    spaces: List[ACATVASpace]
 
 
 class ACATVAInstructionsRequest(ACATVAInstructionsRequestBase):
@@ -176,6 +189,28 @@ class ACATAHFreqSetResponse(BaseModel):
     freq_set: float
 
 
+class ACATAHInstructionsRequest(BaseModel):
+    device_id: str
+    season: Season
+    spaces: List[ACATAHSpace]
+    running_status: bool
+    return_air_temperature: float
+    return_air_temperature_set: float
+    supply_air_temperature: float
+    supply_air_temperature_set: float
+    fan_freq: float
+    fan_freq_set: float
+    fan_freq_upper_limit_set: float
+    fan_freq_lower_limit_set: float
+
+
+class ACATAHInstructionsResponse(BaseModel):
+    switch_set: str
+    return_air_temp_set: Optional[float]
+    supply_air_temp_set: Optional[float]
+    freq_set: Optional[float]
+
+
 class SwitchSetRequestBase(BaseModel):
     device_id: str
     running_status: bool
@@ -269,7 +304,7 @@ class ACATFUFreqSetRequest(BaseModel):
     device_id: str
     freq: float
     fresh_air_temperature: float
-    spaces: List[SpaceATFU]
+    spaces: List[ACATFUSpace]
     season: Season
     running_status_list: List[float]
 

+ 15 - 2
app/schemas/equipment.py

@@ -4,11 +4,19 @@ from enum import Enum
 from typing import List, Optional
 
 import numpy as np
-from pydantic import BaseModel
+from pydantic import BaseModel as PydanticBaseModel, validator
 
 from app.schemas.space import Space
 
 
+class BaseModel(PydanticBaseModel):
+    @validator("*")
+    def change_to_nan(cls, v):
+        if v == -999.9:
+            v = np.NAN
+        return v
+
+
 class AirValveSpeed(float, Enum):
     off = 0.0
     low = 1.0
@@ -53,9 +61,14 @@ class VAVBox(BaseEquipment):
 
 
 class AHU(BaseEquipment):
-    fan_freq_set: Optional[float]
+    fan_freq_set: Optional[float] = np.NAN
     supply_air_temperature: Optional[float] = np.NAN
     supply_air_temperature_set: Optional[float] = np.NAN
+    return_air_temperature: Optional[float] = np.NAN
+    return_air_temperature_set: Optional[float] = np.NAN
+    fan_freq: Optional[float] = np.NAN
+    fan_freq_upper_limit_set: Optional[float] = np.NAN
+    fan_freq_lower_limit_set: Optional[float] = np.NAN
 
 
 class VentilationFan(BaseEquipment):

+ 28 - 11
app/schemas/space.py

@@ -2,23 +2,40 @@
 
 from typing import List, Optional
 
-from pydantic import BaseModel
+import numpy as np
+from pydantic import BaseModel as PydanticBaseModel, validator
 
 
-class Space(BaseModel):
+class BaseModel(PydanticBaseModel):
+    @validator("*")
+    def change_to_nan(cls, v):
+        if v == -999.9:
+            v = np.NAN
+        return v
+
+
+class SpaceBase(BaseModel):
     id: Optional[str]
-    realtime_temperature: float
+    realtime_temperature: float = np.NAN
+
+
+class Space(SpaceBase):
     equipment: Optional[List]
-    temperature_target: Optional[float]
-    comfortable_temperature: Optional[float]
-    diff: Optional[float]
-    vav_default_weight: Optional[float]
-    vav_temporary_weight: Optional[float]
-    vav_temporary_update_time: Optional[str]
+    temperature_target: Optional[float] = np.NAN
+    comfortable_temperature: Optional[float] = np.NAN
+    diff: Optional[float] = np.NAN
 
 
-class SpaceBase(BaseModel):
-    realtime_temperature: float
+class SpaceATVA(Space):
+    vav_default_weight: Optional[float] = np.NAN
+    vav_temporary_weight: Optional[float] = np.NAN
+    vav_temporary_update_time: Optional[str] = ""
+
+
+class SpaceATAH(Space):
+    ahu_default_weight: Optional[float] = np.NAN
+    ahu_temporary_weight: Optional[float] = np.NAN
+    ahu_temporary_update_time: Optional[str] = ""
 
 
 class SpaceATFU(SpaceBase):