|
@@ -1,26 +1,20 @@
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
from operator import attrgetter
|
|
|
-from typing import Dict, List, Optional, Tuple
|
|
|
+from typing import List, Optional, Tuple
|
|
|
|
|
|
import numpy as np
|
|
|
from fastapi import HTTPException
|
|
|
-from httpx import AsyncClient
|
|
|
from loguru import logger
|
|
|
-from sqlalchemy.orm import Session
|
|
|
|
|
|
+from app.api.errors.iot import MissingIOTDataError
|
|
|
from app.controllers.equipment.controller import EquipmentController
|
|
|
-from app.crud.space.weight import get_weights_by_vav
|
|
|
-from app.models.domain.devices import (
|
|
|
- ACATVAInstructionsRequest,
|
|
|
- ACATVAInstructionsRequestV2,
|
|
|
-)
|
|
|
+from app.models.domain.devices import ACATVAInstructionsRequest, ACATVAInstructionsRequestV2
|
|
|
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 SpaceATVA
|
|
|
-from app.services.platform import DataPlatformService
|
|
|
-from app.services.transfer import Duoduo, SpaceInfoService, Season
|
|
|
+from app.services.transfer import Season
|
|
|
from app.utils.date import get_time_str
|
|
|
|
|
|
|
|
@@ -65,9 +59,9 @@ class VAVController(EquipmentController):
|
|
|
return target_result, realtime_result
|
|
|
|
|
|
async def get_supply_air_flow_set(
|
|
|
- self, temperature_set: float, temperature_realtime: float
|
|
|
+ self, temperature_set: float, temperature_realtime: float
|
|
|
) -> float:
|
|
|
- if np.isnan(temperature_set) or np.isnan(temperature_realtime):
|
|
|
+ if not (temperature_set and temperature_realtime):
|
|
|
supply_air_flow_set = 0.0
|
|
|
else:
|
|
|
temperature_supply = self.equipment.supply_air_temperature
|
|
@@ -106,15 +100,24 @@ class VAVController(EquipmentController):
|
|
|
|
|
|
class VAVControllerV2(VAVController):
|
|
|
def __init__(
|
|
|
- self,
|
|
|
- equipment: VAVBox,
|
|
|
- weights: Optional[List[SpaceWeight]] = None,
|
|
|
- season: Optional[Season] = None,
|
|
|
+ self,
|
|
|
+ equipment: VAVBox,
|
|
|
+ weights: Optional[List[SpaceWeight]] = None,
|
|
|
+ season: Optional[Season] = None,
|
|
|
):
|
|
|
super(VAVControllerV2, self).__init__(equipment)
|
|
|
self.weights = weights
|
|
|
self.season = season
|
|
|
|
|
|
+ def get_valid_spaces(self) -> List:
|
|
|
+ valid_spaces = list()
|
|
|
+ for sp in self.equipment.spaces:
|
|
|
+ if sp.realtime_temperature and sp.temperature_target:
|
|
|
+ sp.diff = sp.temperature_target - sp.realtime_temperature
|
|
|
+ valid_spaces.append(sp)
|
|
|
+
|
|
|
+ return valid_spaces
|
|
|
+
|
|
|
async def build_virtual_temperature(self) -> None:
|
|
|
valid_spaces = []
|
|
|
weights = []
|
|
@@ -128,7 +131,7 @@ class VAVControllerV2(VAVController):
|
|
|
if valid_spaces:
|
|
|
weights = sorted(weights, key=lambda x: x.temporary_weight_update_time)
|
|
|
if weights[-1].temporary_weight_update_time > get_time_str(
|
|
|
- 60 * 60 * 2, flag="ago"
|
|
|
+ 60 * 60 * 2, flag="ago"
|
|
|
):
|
|
|
# If someone has submitted a feedback in past two hours, meet the need.
|
|
|
weight_dic = {weight.space_id: 0.0 for weight in weights}
|
|
@@ -166,16 +169,17 @@ class VAVControllerV2(VAVController):
|
|
|
|
|
|
async def rectify(self) -> Tuple[float, float]:
|
|
|
bad_spaces = list()
|
|
|
- for sp in self.equipment.spaces:
|
|
|
+ valid_spaces = self.get_valid_spaces()
|
|
|
+ for sp in valid_spaces:
|
|
|
if self.season == Season.heating:
|
|
|
if sp.realtime_temperature > max(
|
|
|
- 26.0, sp.temperature_target
|
|
|
+ 26.0, sp.temperature_target
|
|
|
) or sp.realtime_temperature < min(20.0, sp.temperature_target):
|
|
|
if sp.temperature_target > 0.0:
|
|
|
bad_spaces.append(sp)
|
|
|
elif self.season == Season.cooling:
|
|
|
if sp.realtime_temperature > max(
|
|
|
- 27.0, sp.temperature_target
|
|
|
+ 27.0, sp.temperature_target
|
|
|
) or sp.realtime_temperature < min(22.0, sp.temperature_target):
|
|
|
if sp.temperature_target > 0.0:
|
|
|
bad_spaces.append(sp)
|
|
@@ -215,10 +219,13 @@ class VAVControllerV2(VAVController):
|
|
|
)
|
|
|
|
|
|
async def run(self) -> None:
|
|
|
- await self.build_virtual_temperature()
|
|
|
- temperature_set, temperature_realtime = await self.rectify()
|
|
|
- await self.get_supply_air_flow_set(temperature_set, temperature_realtime)
|
|
|
- self.equipment.running_status = True
|
|
|
+ try:
|
|
|
+ await self.build_virtual_temperature()
|
|
|
+ temperature_set, temperature_realtime = await self.rectify()
|
|
|
+ await self.get_supply_air_flow_set(temperature_set, temperature_realtime)
|
|
|
+ self.equipment.running_status = True
|
|
|
+ except TypeError:
|
|
|
+ raise MissingIOTDataError
|
|
|
|
|
|
|
|
|
class VAVControllerV3(VAVControllerV2):
|
|
@@ -226,26 +233,14 @@ class VAVControllerV3(VAVControllerV2):
|
|
|
super(VAVControllerV3, self).__init__(vav_params)
|
|
|
self.season = season
|
|
|
|
|
|
- def get_valid_spaces(self) -> List:
|
|
|
- valid_spaces = list()
|
|
|
- for sp in self.equipment.spaces:
|
|
|
- if sp.realtime_temperature > 0.0 and sp.temperature_target > 0.0:
|
|
|
- valid_spaces.append(sp)
|
|
|
-
|
|
|
- return valid_spaces
|
|
|
-
|
|
|
async def build_virtual_temperature(self) -> None:
|
|
|
valid_spaces = self.get_valid_spaces()
|
|
|
|
|
|
if not valid_spaces:
|
|
|
- virtual_realtime, virtual_target = np.NAN, np.NAN
|
|
|
+ virtual_realtime, virtual_target = None, None
|
|
|
else:
|
|
|
- sorted_spaces = sorted(
|
|
|
- valid_spaces, key=lambda x: x.vav_temporary_update_time
|
|
|
- )
|
|
|
- if sorted_spaces[-1].vav_temporary_update_time > get_time_str(
|
|
|
- 60 * 60 * 2, flag="ago"
|
|
|
- ):
|
|
|
+ sorted_spaces = sorted(valid_spaces, key=lambda x: x.vav_temporary_update_time)
|
|
|
+ if sorted_spaces[-1].vav_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:
|
|
@@ -276,129 +271,36 @@ class VAVControllerV4(VAVControllerV3):
|
|
|
super().__init__(vav_params, season)
|
|
|
self.return_air_temp = return_air_temp
|
|
|
|
|
|
- def get_next_temp_set(
|
|
|
- self, virtual_realtime_temp: float, virtual_target_temp: float
|
|
|
- ) -> float:
|
|
|
- if np.isnan(virtual_realtime_temp) or np.isnan(virtual_target_temp):
|
|
|
+ def get_next_temp_set(self, virtual_realtime_temp: float, virtual_target_temp: float) -> float:
|
|
|
+ if not (virtual_realtime_temp and virtual_target_temp):
|
|
|
next_temp_set = np.NAN
|
|
|
else:
|
|
|
- next_temp_set = (
|
|
|
- virtual_target_temp + self.return_air_temp - virtual_realtime_temp
|
|
|
- )
|
|
|
+ next_temp_set = virtual_target_temp + self.return_air_temp - virtual_realtime_temp
|
|
|
|
|
|
self.equipment.setting_temperature = next_temp_set
|
|
|
|
|
|
return next_temp_set
|
|
|
|
|
|
async def run(self) -> None:
|
|
|
- await self.build_virtual_temperature()
|
|
|
- temperature_set, temperature_realtime = await self.rectify()
|
|
|
- self.get_next_temp_set(temperature_realtime, temperature_set)
|
|
|
- self.equipment.running_status = True
|
|
|
-
|
|
|
-
|
|
|
-async def fetch_vav_control_params(project_id: str, equipment_id: str) -> Dict:
|
|
|
- async with AsyncClient() as client:
|
|
|
- duo_duo = Duoduo(client, project_id)
|
|
|
- platform = DataPlatformService(client, project_id)
|
|
|
-
|
|
|
- season = await duo_duo.get_season()
|
|
|
- served_spaces = await duo_duo.get_space_by_equipment(equipment_id)
|
|
|
- space_objects = []
|
|
|
- realtime_supply_air_temperature_list = []
|
|
|
- for sp in served_spaces:
|
|
|
- sp_id = sp.get("id")
|
|
|
- transfer = SpaceInfoService(client, project_id, sp_id)
|
|
|
- current_target = await transfer.get_current_temperature_target()
|
|
|
- realtime_temperature = await platform.get_realtime_temperature(sp_id)
|
|
|
-
|
|
|
- related_equipment = await transfer.get_equipment()
|
|
|
- equipment_objects = []
|
|
|
- for eq in related_equipment:
|
|
|
- if eq.get("category") == "ACATFC":
|
|
|
- speed = await platform.get_fan_speed(eq.get("id"))
|
|
|
- temp_fcu_params = {"id": eq.get("id"), "air_valve_speed": speed}
|
|
|
- fcu = FCU(**temp_fcu_params)
|
|
|
- equipment_objects.append(fcu)
|
|
|
- elif eq.get("category") == "ACATAH":
|
|
|
- realtime_supply_air_temperature_list.append(
|
|
|
- await platform.get_realtime_supply_air_temperature(eq.get("id"))
|
|
|
- )
|
|
|
- temp_space_params = {
|
|
|
- "id": sp_id,
|
|
|
- "realtime_temperature": realtime_temperature,
|
|
|
- "equipment": equipment_objects,
|
|
|
- "temperature_target": current_target,
|
|
|
- "diff": current_target - realtime_temperature,
|
|
|
- }
|
|
|
- space = SpaceATVA(**temp_space_params)
|
|
|
- space_objects.append(space)
|
|
|
-
|
|
|
- realtime_supply_air_temperature = np.array(
|
|
|
- realtime_supply_air_temperature_list
|
|
|
- ).mean()
|
|
|
- realtime_supply_air_flow = await platform.get_realtime_supply_air_flow(
|
|
|
- equipment_id
|
|
|
- )
|
|
|
- lower_limit, upper_limit = await platform.get_air_flow_limit(equipment_id)
|
|
|
-
|
|
|
- vav_params = {
|
|
|
- "id": equipment_id,
|
|
|
- "spaces": space_objects,
|
|
|
- "supply_air_temperature": realtime_supply_air_temperature,
|
|
|
- "supply_air_flow": realtime_supply_air_flow,
|
|
|
- "supply_air_flow_lower_limit": lower_limit,
|
|
|
- "supply_air_flow_upper_limit": upper_limit,
|
|
|
- "season": season,
|
|
|
- }
|
|
|
-
|
|
|
- return vav_params
|
|
|
-
|
|
|
-
|
|
|
-@logger.catch()
|
|
|
-async def get_vav_control_v1(project: str, equipment_id: str) -> VAVBox:
|
|
|
- vav_params = await fetch_vav_control_params(project, equipment_id)
|
|
|
- vav = VAVBox(**vav_params)
|
|
|
-
|
|
|
- vav_controller = VAVController(vav)
|
|
|
- await vav_controller.run()
|
|
|
- regulated_vav = vav_controller.get_results()
|
|
|
-
|
|
|
- return regulated_vav
|
|
|
-
|
|
|
+ try:
|
|
|
+ await self.build_virtual_temperature()
|
|
|
+ temperature_set, temperature_realtime = await self.rectify()
|
|
|
+ self.get_next_temp_set(temperature_realtime, temperature_set)
|
|
|
+ self.equipment.running_status = True
|
|
|
+ except TypeError:
|
|
|
+ raise MissingIOTDataError
|
|
|
|
|
|
-@logger.catch()
|
|
|
-async def get_vav_control_v2(db: Session, project_id: str, equipment_id: str) -> VAVBox:
|
|
|
- vav_params = await fetch_vav_control_params(project_id, equipment_id)
|
|
|
- vav = VAVBox(**vav_params)
|
|
|
- weights = get_weights_by_vav(db, equipment_id)
|
|
|
|
|
|
- vav_controller = VAVControllerV2(
|
|
|
- vav, [SpaceWeight.from_orm(weight) for weight in weights], vav_params["season"]
|
|
|
- )
|
|
|
- await vav_controller.run()
|
|
|
- regulated_vav = vav_controller.get_results()
|
|
|
-
|
|
|
- return regulated_vav
|
|
|
-
|
|
|
-
|
|
|
-@logger.catch()
|
|
|
-async def build_acatva_instructions(
|
|
|
- params: ACATVAInstructionsRequest,
|
|
|
-) -> ACATVAInstructions:
|
|
|
+async def build_acatva_instructions(params: ACATVAInstructionsRequest) -> ACATVAInstructions:
|
|
|
space_params = []
|
|
|
for sp in params.spaces:
|
|
|
temp_sp = SpaceATVA(**sp.dict())
|
|
|
- temp_sp.diff = temp_sp.temperature_target - temp_sp.realtime_temperature
|
|
|
space_params.append(temp_sp)
|
|
|
|
|
|
- if params.supply_air_temperature == -1:
|
|
|
- if params.acatah_supply_air_temperature == -1:
|
|
|
- supply_air_temperature = np.NAN
|
|
|
- else:
|
|
|
- supply_air_temperature = params.acatah_supply_air_temperature
|
|
|
- else:
|
|
|
+ if params.supply_air_temperature:
|
|
|
supply_air_temperature = params.supply_air_temperature
|
|
|
+ else:
|
|
|
+ supply_air_temperature = params.acatah_supply_air_temperature
|
|
|
|
|
|
vav_params = VAVBox(
|
|
|
spaces=space_params,
|
|
@@ -421,30 +323,20 @@ async def build_acatva_instructions(
|
|
|
return instructions
|
|
|
|
|
|
|
|
|
-@logger.catch()
|
|
|
-async def build_acatva_instructions_for_jm(
|
|
|
- params: ACATVAInstructionsRequestV2,
|
|
|
-) -> dict:
|
|
|
+async def build_acatva_instructions_for_jm(params: ACATVAInstructionsRequestV2) -> dict:
|
|
|
# Control logic for Jiaming.
|
|
|
|
|
|
space_params = []
|
|
|
for sp in params.spaces:
|
|
|
temp_sp = SpaceATVA(**sp.dict())
|
|
|
- if temp_sp.temperature_target:
|
|
|
- temp_sp.diff = temp_sp.temperature_target - temp_sp.realtime_temperature
|
|
|
- else:
|
|
|
- temp_sp.diff = 0.0
|
|
|
space_params.append(temp_sp)
|
|
|
|
|
|
vav_params = VAVBox(spaces=space_params)
|
|
|
- return_air_temp = (
|
|
|
- np.NAN if params.return_air_temperature == -1.0 else params.return_air_temperature
|
|
|
- )
|
|
|
|
|
|
controller = VAVControllerV4(
|
|
|
vav_params=vav_params,
|
|
|
season=Season(params.season),
|
|
|
- return_air_temp=return_air_temp,
|
|
|
+ return_air_temp=params.return_air_temperature,
|
|
|
)
|
|
|
await controller.run()
|
|
|
regulated_vav = controller.get_results()
|
|
@@ -455,7 +347,7 @@ async def build_acatva_instructions_for_jm(
|
|
|
next_temp_set = -1.0
|
|
|
else:
|
|
|
next_temp_set = -1.0
|
|
|
-
|
|
|
+
|
|
|
instructions = {
|
|
|
'temperature_target_set': next_temp_set,
|
|
|
'virtual_target_temperature': regulated_vav.virtual_target_temperature,
|