highing666 преди 2 години
родител
ревизия
b8a4b888ae

+ 9 - 14
app/api/errors/iot.py

@@ -1,20 +1,15 @@
-from functools import wraps
+from fastapi import FastAPI, Request
+from fastapi.responses import JSONResponse
 
-from fastapi import HTTPException
+app = FastAPI()
 
 
 class MissingIOTDataError(Exception):
-    def __init__(self):
-        super().__init__(f"Missing IOT data error.")
+    pass
 
 
-def missing_iot_data_handler(func):
-    @wraps(func)
-    async def inner_function(*args, **kwargs):
-        try:
-            results = await func(*args, **kwargs)
-        except MissingIOTDataError:
-            raise HTTPException(status_code=400, detail="Missing data.")
-        return results
-
-    return inner_function
+async def missing_data_exception_handler(request: Request, exc: MissingIOTDataError):
+    return JSONResponse(
+        status_code=400,
+        content={"message": "Missing data"}
+    )

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

@@ -28,7 +28,7 @@ from app.controllers.equipment.pau.supply_air_temperature_set import (
 from app.controllers.equipment.pau.switch import build_acatfu_switch_set, build_co2_acatfu_switch
 from app.controllers.equipment.vav import (
     build_acatva_instructions,
-    build_acatva_instructions_for_JM,
+    build_acatva_instructions_for_jm,
 )
 from app.controllers.equipment.ventilation_fan.switch import build_acvtsf_switch_set
 from app.controllers.equipment.vrf.basic import build_acatvi_instructions
@@ -324,7 +324,7 @@ async def get_acatva_instructions(params: domain_devices.ACATVAInstructionsReque
 async def get_acatva_instructions_v2(
         params: domain_devices.ACATVAInstructionsRequestV2,
 ):
-    instructions = await build_acatva_instructions_for_JM(params)
+    instructions = await build_acatva_instructions_for_jm(params)
 
     logger.info(params)
     logger.info(f"{params.device_id} - {instructions}")

+ 0 - 119
app/api/routers/equipment.py

@@ -1,119 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from enum import Enum
-
-import numpy as np
-from fastapi import APIRouter, Depends, Query
-from loguru import logger
-from sqlalchemy.orm import Session
-
-from app.api.dependencies.db import get_db
-from app.controllers.equipment.fcu.basic import get_fcu_control_result
-from app.controllers.equipment.vav import get_vav_control_v2
-from app.models.domain.equipment import (
-    EquipmentControlResponse,
-    EquipmentControlRequest,
-)
-from app.schemas.equipment import PAU
-from app.utils.date import get_time_str
-
-
-class EquipmentName(str, Enum):
-    FCU = "ACATFC"
-    VAV = "ACATVA"
-
-
-router = APIRouter()
-
-
-@router.get("/control", response_model=EquipmentControlResponse)
-async def get_equipment_command_test(
-    project_id: str = Query(..., max_length=50, regex="^Pj", alias="projectId"),
-    equip_id: str = Query(..., max_length=50, regex="^Eq", alias="equipId"),
-    equip_type: EquipmentName = Query(..., alias="equipType"),
-    db: Session = Depends(get_db),
-):
-    if equip_type.value == EquipmentName.FCU:
-        output = await get_fcu_control_result(project_id, equip_id)
-    elif equip_type.value == EquipmentName.VAV:
-        vav = await get_vav_control_v2(db, project_id, equip_id)
-        output = {
-            "SupplyAirFlowSet": vav.supply_air_flow_set,
-            "VirtualRealtimeTemperature": vav.virtual_realtime_temperature
-            if not np.isnan(vav.virtual_realtime_temperature)
-            else None,
-            "TargetTemperatureSet": vav.virtual_target_temperature
-            if not np.isnan(vav.virtual_target_temperature)
-            else None,
-        }
-    else:
-        output = {}
-
-    response = {
-        "projectId": project_id,
-        "equipId": equip_id,
-        "time": get_time_str(),
-        "output": output,
-    }
-    return response
-
-
-@router.post("/control", response_model=EquipmentControlResponse)
-async def get_equipment_command(
-    equipment_control_info: EquipmentControlRequest, db: Session = Depends(get_db)
-):
-    if equipment_control_info.equipType == EquipmentName.FCU:
-        output = await get_fcu_control_result(
-            equipment_control_info.projectId, equipment_control_info.equipId
-        )
-    elif equipment_control_info.equipType == EquipmentName.VAV:
-        vav = await get_vav_control_v2(
-            db, equipment_control_info.projectId, equipment_control_info.equipId
-        )
-        output = {
-            "SupplyAirFlowSet": vav.supply_air_flow_set,
-            "VirtualRealtimeTemperature": vav.virtual_realtime_temperature
-            if not np.isnan(vav.virtual_realtime_temperature)
-            else None,
-            "TargetTemperatureSet": vav.virtual_target_temperature
-            if not np.isnan(vav.virtual_target_temperature)
-            else None,
-        }
-    else:
-        output = {}
-
-    response = {
-        "projectId": equipment_control_info.projectId,
-        "equipId": equipment_control_info.equipId,
-        "time": get_time_str(),
-        "output": output,
-    }
-    logger.info(response)
-    return response
-
-
-@router.post("/control/atva/v2", response_model=EquipmentControlResponse)
-async def get_fcu_command(
-    equipment_control_info: EquipmentControlRequest, db: Session = Depends(get_db)
-):
-    vav = await get_vav_control_v2(
-        db, equipment_control_info.projectId, equipment_control_info.equipId
-    )
-    output = {
-        "SupplyAirFlowSet": vav.supply_air_flow_set,
-        "VirtualRealtimeTemperature": vav.virtual_realtime_temperature
-        if not np.isnan(vav.virtual_realtime_temperature)
-        else None,
-        "TargetTemperatureSet": vav.virtual_target_temperature
-        if not np.isnan(vav.virtual_target_temperature)
-        else None,
-    }
-
-    response = {
-        "projectId": equipment_control_info.projectId,
-        "equipId": equipment_control_info.equipId,
-        "time": get_time_str(),
-        "output": output,
-    }
-    logger.info(response)
-    return response

+ 8 - 18
app/api/routers/space.py

@@ -6,7 +6,6 @@ from fastapi import APIRouter, Depends, HTTPException, Query
 from sqlalchemy.orm import Session
 
 from app.api.dependencies.db import get_db
-from app.controllers.equipment.fcu.q_learning import QLearningCommandBuilder
 from app.crud.space.weight import (
     get_weights_by_space,
     get_weights_by_vav,
@@ -15,17 +14,16 @@ from app.crud.space.weight import (
 )
 from app.models.domain.space import SpaceControlResponse
 from app.schemas.sapce_weight import SpaceWeight, SpaceWeightCreate, SpaceWeightUpdate
-from app.services.transfer import Season
 
 router = APIRouter()
 
 
 @router.get("/control", response_model=SpaceControlResponse)
 async def get_space_command(
-    project_id: str = Query(..., max_length=50, regex="^Pj", alias="projectId"),
-    space_id: str = Query(..., max_length=50, regex="^Sp", alias="roomId"),
-    timestamp: str = Query(None, min_length=14, max_length=14, alias="time"),
-    method: int = Query(3),
+        project_id: str = Query(..., max_length=50, regex="^Pj", alias="projectId"),
+        space_id: str = Query(..., max_length=50, regex="^Sp", alias="roomId"),
+        timestamp: str = Query(None, min_length=14, max_length=14, alias="time"),
+        method: int = Query(3),
 ):
     response = {
         "projectId": project_id,
@@ -37,14 +35,6 @@ async def get_space_command(
     return response
 
 
-@router.get("/test")
-async def get_test_result(current: float, pre: float, target: float):
-    builder = QLearningCommandBuilder(Season("Cooling"))
-    command = await builder.get_command(current, pre, target)
-
-    return command
-
-
 @router.post("/weight", response_model=SpaceWeight)
 async def create_space_weight(weight: SpaceWeightCreate, db: Session = Depends(get_db)):
     return create_weight(db=db, weight=weight)
@@ -64,10 +54,10 @@ async def read_weight_by_vav(vav_id: str, db: Session = Depends(get_db)):
 
 @router.put("/weight/{space_id}/{vav_id}", response_model=SpaceWeight)
 async def update_weight_by_space(
-    space_id: str,
-    vav_id: str,
-    weight_in: SpaceWeightUpdate,
-    db: Session = Depends(get_db),
+        space_id: str,
+        vav_id: str,
+        weight_in: SpaceWeightUpdate,
+        db: Session = Depends(get_db),
 ):
     weights = get_weights_by_space(db, space_id=space_id)
     new_weight = None

+ 6 - 65
app/controllers/equipment/ahu/basic.py

@@ -3,14 +3,10 @@
 from typing import List, Optional
 
 import numpy as np
-from httpx import AsyncClient
-from loguru import logger
 
 from app.models.domain.devices import ACATAHFreqSetRequest
 from app.schemas.equipment import AHU
 from app.schemas.system import ACAT
-from app.services.platform import DataPlatformService, InfoCode
-from app.services.transfer import Duoduo
 
 
 class AHUController:
@@ -19,28 +15,20 @@ class AHUController:
         self._equipment = equipment
         self._system = system
 
-    async def build_freq_set(
-            self, supply_air_temperature_set_duration: List, hot_rate: float
-    ) -> float:
+    async def build_freq_set(self, supply_air_temperature_set_duration: List, hot_rate: float) -> float:
         extent = 5
         if (
-                np.isnan(self._equipment.freq_set)
-                or np.isnan(self._system.supply_static_press)
-                or np.isnan(self._system.supply_static_press_set)
+                self._equipment.freq_set is None
+                or self._system.supply_static_press is None
+                or self._system.supply_static_press_set is None
         ):
             temp_freq_set = 80.0
         else:
             pre_fan_freq_set = self._equipment.freq_set
 
-            if (
-                    self._system.supply_static_press
-                    < self._system.supply_static_press_set - extent
-            ):
+            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
-            ):
+            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
@@ -61,53 +49,6 @@ class AHUController:
         return freq_set
 
 
-@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 = Duoduo(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])
-        count = await review_service.get_fill_count()
-        try:
-            hot_rate = count.get("hotNum") / (
-                    count.get("hotNum") + count.get("coldNum") + count.get("normalNum")
-            )
-        except (ZeroDivisionError, KeyError):
-            hot_rate = 0.0
-        raw_supply_air_temperature_set = await platform.get_duration(
-            InfoCode.supply_air_temperature_set, equipment_id, 30 * 60
-        )
-        supply_air_temperature_set = [
-            item["value"] for item in raw_supply_air_temperature_set
-        ]
-
-    equip_params = {"id": equipment_id, "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"{equipment_id} 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 build_acatah_freq_set(params: ACATAHFreqSetRequest) -> float:
     acat_system = ACAT(
         supply_static_press=params.system_supply_static_press,

+ 18 - 14
app/controllers/equipment/ahu/common.py

@@ -6,6 +6,7 @@ from typing import List, Tuple
 import numpy as np
 from loguru import logger
 
+from app.api.errors.iot import MissingIOTDataError
 from app.models.domain.devices import ACATAHInstructionsRequest
 from app.schemas.equipment import AHU
 from app.schemas.season import Season
@@ -22,7 +23,7 @@ class ATAHController:
     def get_switch_set(self) -> str:
         switch_set = "off"
         for sp in self._spaces:
-            if not np.isnan(sp.temperature_target):
+            if sp.temperature_target:
                 switch_set = "on"
 
         return switch_set
@@ -105,7 +106,7 @@ class ATAHController:
     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):
+            if sp.realtime_temperature and sp.temperature_target:
                 valid_spaces.append(sp)
 
         return valid_spaces
@@ -143,26 +144,29 @@ class ATAHController:
         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)
+        try:
+            virtual_realtime, virtual_target = self.build_virtual_temperature()
+            new_switch_set = self.get_switch_set()
+            if not 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)
+        except TypeError:
+            raise MissingIOTDataError
 
         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)
+        if temp_sp.temperature_target and temp_sp.realtime_temperature:
+            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)

+ 25 - 106
app/controllers/equipment/ahu/supply_air_temperature_set.py

@@ -2,15 +2,13 @@ from typing import List
 
 import arrow
 import numpy as np
-from httpx import AsyncClient
 from loguru import logger
 
-from app.controllers.equipment.ahu.thermal_mode import count_vav_box_weight, fetch_status_params
+from app.api.errors.iot import MissingIOTDataError
+from app.controllers.equipment.ahu.thermal_mode import count_vav_box_weight
 from app.models.domain.devices import ThermalMode, ACATAHSupplyAirTempSetRequest
 from app.schemas.equipment import VAVBox
-from app.services.platform import DataPlatformService, InfoCode
-from app.services.transfer import Duoduo, Season
-from app.services.weather import WeatherService
+from app.services.transfer import Season
 from app.utils.date import get_time_str, TIME_FMT
 
 
@@ -108,25 +106,28 @@ class ACATAHSupplyAirTemperatureController:
         return ratio
 
     def build(self) -> float:
-        if not self.is_off_to_on:
-            normal_ratio = self.get_normal_ratio()
-            if normal_ratio < 0.9:
-                cold_ratio = self.get_cold_ratio()
-                temperature = self.calculate_by_cold_vav(cold_ratio)
+        try:
+            if not self.is_off_to_on:
+                normal_ratio = self.get_normal_ratio()
+                if normal_ratio < 0.9:
+                    cold_ratio = self.get_cold_ratio()
+                    temperature = self.calculate_by_cold_vav(cold_ratio)
+                else:
+                    temperature = self.current_set
             else:
-                temperature = self.current_set
-        else:
+                if self.season == Season.heating:
+                    temperature = 27.0
+                elif self.season == Season.cooling:
+                    temperature = 20.0
+                else:
+                    temperature = 25.0
+
             if self.season == Season.heating:
-                temperature = 27.0
-            elif self.season == Season.cooling:
-                temperature = 20.0
+                temperature = max(20.0, min(30.0, temperature))
             else:
-                temperature = 25.0
-
-        if self.season == Season.heating:
-            temperature = max(20.0, min(30.0, temperature))
-        else:
-            temperature = max(18.0, min(25.0, temperature))
+                temperature = max(18.0, min(25.0, temperature))
+        except TypeError:
+            raise MissingIOTDataError
 
         return temperature
 
@@ -159,96 +160,14 @@ class ACATAHSupplyAirTemperatureDefaultController:
         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)
-        duoduo = Duoduo(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, 20 * 60)
-
-        season = await duoduo.get_season()
-
-        if chill_water_valve_opening_set_duration[-1]["value"] == 0.0:
-            thermal_mode = ThermalMode.heating
-        else:
-            thermal_mode = ThermalMode.cooling
-
-        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,
-        season,
-    )
-    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_set = controller.build()
-
-    return next_supply_air_temperature_set
-
-
-@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)
-
-    logger.debug(f"next supply air temperature set: {device_id} - {new}")
-
-    return new
-
-
-@logger.catch
-def build_acatah_supply_air_temperature_set(
-        params: ACATAHSupplyAirTempSetRequest,
-) -> float:
+def build_acatah_supply_air_temperature_set(params: ACATAHSupplyAirTempSetRequest) -> float:
     try:
         vav_list = list()
         for raw_vav in params.vav_list:
             vav = VAVBox(**raw_vav.dict())
             vav.virtual_target_temperature = raw_vav.virtual_temperature_target
-            vav_list.append(vav)
+            if vav.virtual_target_temperature and vav.virtual_realtime_temperature:
+                vav_list.append(vav)
 
         if params.chill_water_valve_opening_set_list[-1] == 0.0:
             thermal_mode = ThermalMode.heating

+ 19 - 22
app/controllers/equipment/ahu/switch.py

@@ -1,14 +1,9 @@
 # -*- coding: utf-8 -*-
 
-from typing import Tuple
-
-from httpx import AsyncClient
-from loguru import logger
-
+from app.api.errors.iot import MissingIOTDataError
 from app.controllers.equipment.switch import Switch, SwitchSet
 from app.models.domain.devices import ACATAHSwitchSetRequest
 from app.schemas.equipment import AHU
-from app.services.platform import DataPlatformService
 
 
 class AHUSwitch(Switch):
@@ -16,32 +11,34 @@ class AHUSwitch(Switch):
         super(AHUSwitch, self).__init__(equipment)
 
     def break_time_action(self, begin: str, end: str, is_workday: bool) -> SwitchSet:
-        if self._equip.in_cloud_status:
-            if is_workday:
-                if begin and end:
-                    switch_flag = True
-                    if begin <= end:
-                        if begin <= self._now_time <= end:
-                            switch_flag = False
-                    else:
-                        if not end <= self._now_time <= begin:
-                            switch_flag = False
-
-                    if not switch_flag:
-                        action = SwitchSet.off
+        try:
+            if self._equip.in_cloud_status:
+                if is_workday:
+                    if begin and end:
+                        switch_flag = True
+                        if begin <= end:
+                            if begin <= self._now_time <= end:
+                                switch_flag = False
+                        else:
+                            if not end <= self._now_time <= begin:
+                                switch_flag = False
+
+                        if not switch_flag:
+                            action = SwitchSet.off
+                        else:
+                            action = SwitchSet.hold
                     else:
                         action = SwitchSet.hold
                 else:
                     action = SwitchSet.hold
             else:
                 action = SwitchSet.hold
-        else:
-            action = SwitchSet.hold
+        except TypeError:
+            raise MissingIOTDataError
 
         return action
 
 
-@logger.catch()
 async def build_acatah_switch_set(params: ACATAHSwitchSetRequest) -> SwitchSet:
     ahu = AHU(
         running_status=params.running_status,

+ 25 - 79
app/controllers/equipment/ahu/thermal_mode.py

@@ -1,13 +1,9 @@
-from typing import Dict, List
-
-from httpx import AsyncClient
-from loguru import logger
+from typing import List
 
+from app.api.errors.iot import MissingIOTDataError
 from app.models.domain.devices import ACATAHThermalModeSetRequest
-from app.models.domain.devices import ThermalMode
 from app.schemas.equipment import VAVBox
-from app.services.platform import DataPlatformService, InfoCode
-from app.services.transfer import Duoduo, Season
+from app.services.transfer import Season
 
 
 def count_vav_box_weight(
@@ -63,15 +59,18 @@ class ACATAHThermalModeController:
     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,
-                box.supply_air_flow_upper_limit,
-                box.supply_air_flow_lower_limit,
-                box.supply_air_flow_set,
-                box.valve_opening,
-                box.supply_air_temperature
-            )
+            try:
+                weight += count_vav_box_weight(
+                    box.virtual_realtime_temperature,
+                    box.virtual_target_temperature,
+                    box.supply_air_flow_upper_limit,
+                    box.supply_air_flow_lower_limit,
+                    box.supply_air_flow_set,
+                    box.valve_opening,
+                    box.supply_air_temperature
+                )
+            except TypeError:
+                raise MissingIOTDataError
 
         if weight > 0:
             mode = "cooling"
@@ -83,72 +82,19 @@ class ACATAHThermalModeController:
         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)
-
-        season = duoduo.get_season()
-        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"
-            )
-            lower_limit_flow, upper_limit_flow = await platform.get_air_flow_limit(
-                vav_id
-            )
-            current_flow_set = await platform.get_realtime_data(
-                InfoCode.supply_air_flow_set, vav_id
-            )
-            valve_opening = await platform.get_realtime_data(
-                InfoCode.valve_opening, vav_id
-            )
-            vav_params = {
-                "id": vav_id,
-                "virtual_realtime_temperature": virtual_realtime_temperature,
-                "virtual_target_temperature": virtual_temperature_target,
-                "supply_air_flow_lower_limit": lower_limit_flow,
-                "supply_air_flow_upper_limit": upper_limit_flow,
-                "supply_air_flow_set": current_flow_set,
-                "valve_opening": valve_opening,
-            }
-            vav = VAVBox(**vav_params)
-            vav_boxes_list.append(vav)
-
-        return {"vav_boxes_list": vav_boxes_list, "season": season}
-
-
-@logger.catch()
-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"), prams.get("season")
-    )
-    mode = controller.build()
-
-    return ThermalMode(mode)
-
-
-@logger.catch()
 def build_acatah_thermal_mode_set(params: ACATAHThermalModeSetRequest) -> str:
     vav_list = list()
     for raw_vav in params.vav_list:
-        vav = VAVBox(
-            virtual_realtime_temperature=raw_vav.virtual_realtime_temperature,
-            virtual_target_temperature=raw_vav.virtual_temperature_target,
-            supply_air_flow_lower_limit=raw_vav.supply_air_flow_lower_limit,
-            supply_air_flow_upper_limit=raw_vav.supply_air_flow_upper_limit,
-            supply_air_flow_set=raw_vav.supply_air_flow_set,
-            valve_opening=raw_vav.valve_opening,
-        )
-        vav_list.append(vav)
+        if raw_vav.virtual_realtime_temperature and raw_vav.virtual_temperature_target:
+            vav = VAVBox(
+                virtual_realtime_temperature=raw_vav.virtual_realtime_temperature,
+                virtual_target_temperature=raw_vav.virtual_temperature_target,
+                supply_air_flow_lower_limit=raw_vav.supply_air_flow_lower_limit,
+                supply_air_flow_upper_limit=raw_vav.supply_air_flow_upper_limit,
+                supply_air_flow_set=raw_vav.supply_air_flow_set,
+                valve_opening=raw_vav.valve_opening,
+            )
+            vav_list.append(vav)
 
     controller = ACATAHThermalModeController(vav_list, Season(params.season))
     mode = controller.build()

+ 58 - 154
app/controllers/equipment/fcu/basic.py

@@ -1,23 +1,15 @@
 # -*- coding: utf-8 -*-
 
-from typing import Dict, Tuple
-
-import numpy as np
-from httpx import AsyncClient
-from loguru import logger
+from typing import Tuple
 
+from app.api.errors.iot import MissingIOTDataError
 from app.controllers.equipment.controller import EquipmentController
-from app.models.domain.devices import (
-    ACATFC2InstructionsRequest,
-    ACATFC4InstructionsRequest,
-    Speed,
-)
+from app.models.domain.devices import ACATFC2InstructionsRequest, ACATFC4InstructionsRequest, Speed
 from app.models.domain.feedback import FeedbackValue
 from app.schemas.equipment import AirValveSpeed, FCU
 from app.schemas.instructions import ACATFCInstructions
 from app.schemas.space import Space
-from app.services.platform import DataPlatformService, InfoCode
-from app.services.transfer import SpaceInfoService, Duoduo, Season
+from app.services.transfer import Season
 from app.utils.math import round_half_up
 
 
@@ -29,7 +21,7 @@ class FCUController(EquipmentController):
 
     async def get_temperature_target(self) -> float:
         target = self._equipment.space.temperature_target
-        if not np.isnan(target):
+        if target:
             setting_target = target
         else:
             setting_target = 25.0
@@ -41,10 +33,7 @@ class FCUController(EquipmentController):
         temperature_target = self._equipment.space.temperature_target
         if self.season == Season.heating:
             mode = 2
-            if (
-                    temperature_target > 0
-                    and self._equipment.space.realtime_temperature > 0
-            ):
+            if temperature_target and self._equipment.space.realtime_temperature:
                 if temperature_target < self._equipment.space.realtime_temperature:
                     mode = 1
         elif self.season == Season.cooling:
@@ -57,11 +46,9 @@ class FCUController(EquipmentController):
 
     async def get_air_valve_speed(self):
         temperature_target = self._equipment.space.temperature_target
-        if temperature_target > 0:
+        if temperature_target:
             if self.season == Season.heating:
-                diff = abs(
-                    self._equipment.space.realtime_temperature - temperature_target
-                )
+                diff = abs(self._equipment.space.realtime_temperature - temperature_target)
                 if diff >= 1.5:
                     self._equipment.air_valve_speed = AirValveSpeed.high
                 elif diff >= 1.0:
@@ -90,10 +77,13 @@ class FCUController(EquipmentController):
             self._equipment.running_status = True
 
     async def run(self):
-        # await self.get_temperature_target()
-        await self.get_air_valve_speed()
-        await self.get_running_status()
-        await self.get_mode()
+        try:
+            await self.get_air_valve_speed()
+            await self.get_running_status()
+            await self.get_mode()
+            await self.get_temperature_target()
+        except TypeError:
+            raise MissingIOTDataError
 
     def get_results(self):
         return self._equipment
@@ -108,19 +98,13 @@ class FCUControllerV2(EquipmentController):
         self.season = season
 
     def get_mode(self) -> int:
-        if self.equipment.water_in_temperature > 0:
-            if (
-                    self.equipment.water_in_temperature
-                    > self.equipment.space.temperature_target
-            ):
+        if self.equipment.water_in_temperature and self.equipment.space.temperature_target:
+            if self.equipment.water_in_temperature > self.equipment.space.temperature_target:
                 mode = 2
             else:
                 mode = 1
-        elif self.equipment.supply_air_temperature > 0:
-            if (
-                    self.equipment.supply_air_temperature
-                    > self.equipment.space.temperature_target
-            ):
+        elif self.equipment.supply_air_temperature and self.equipment.space.temperature_target:
+            if self.equipment.supply_air_temperature > self.equipment.space.temperature_target:
                 mode = 2
             else:
                 mode = 1
@@ -137,8 +121,8 @@ class FCUControllerV2(EquipmentController):
 
     def build_air_valve_speed(self):
         target = self.equipment.space.temperature_target
-        if target > 0:
-            if self.equipment.space.realtime_temperature > 0:
+        if target:
+            if self.equipment.space.realtime_temperature:
                 mode = self.get_mode()
                 if mode == 2:
                     diff = target - self.equipment.space.realtime_temperature
@@ -169,16 +153,19 @@ class FCUControllerV2(EquipmentController):
             self.equipment.running_status = True
 
     def build_temperature_set(self):
-        if np.isnan(self.equipment.space.temperature_target):
-            self.equipment.setting_temperature = 0.0
-        else:
+        if self.equipment.space.temperature_target:
             self.equipment.setting_temperature = self.equipment.space.temperature_target
+        else:
+            self.equipment.setting_temperature = 0.0
 
     async def run(self):
-        self.build_air_valve_speed()
-        self.build_running_status()
-        self.get_mode()
-        self.build_temperature_set()
+        try:
+            self.build_air_valve_speed()
+            self.build_running_status()
+            self.get_mode()
+            self.build_temperature_set()
+        except TypeError:
+            raise MissingIOTDataError
 
     def get_results(self):
         return self.equipment
@@ -247,7 +234,7 @@ class ATFC2Controller:
 
     def build_speed_set(self) -> Speed:
         speed_controller = SpeedController(self.speed, self.running_status)
-        if self.space_realtime > 0.0 and self.space_target > 0.0:
+        if self.space_realtime and self.space_target:
             if self.season == Season.cooling:
                 diff = self.space_realtime - self.space_target
             elif self.season == Season.heating:
@@ -267,23 +254,17 @@ class ATFC2Controller:
                 speed = Speed.high
         else:
             speed = Speed.hold
-            if self.space_target < 0.0:
+            if not self.space_target:
                 speed = Speed.off
             else:
                 if self.feedback == FeedbackValue.switch_on:
                     speed = Speed.medium
-                elif (
-                        self.feedback == FeedbackValue.so_hot
-                        or self.feedback == FeedbackValue.a_little_hot
-                ):
+                elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
                     if self.season == Season.cooling:
                         speed = speed_controller.turn_up()
                     elif self.season == Season.heating:
                         speed = speed_controller.turn_down()
-                elif (
-                        self.feedback == FeedbackValue.so_cold
-                        or self.feedback == FeedbackValue.a_little_cold
-                ):
+                elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
                     if self.season == Season.cooling:
                         speed = speed_controller.turn_down()
                     elif self.season == Season.heating:
@@ -306,9 +287,12 @@ class ATFC2Controller:
         return new_switch_set
 
     def run(self) -> dict:
-        speed = self.build_speed_set()
-        switch_set = self.build_switch_set(speed)
-        water_switch_set = self.build_water_valve_set(switch_set)
+        try:
+            speed = self.build_speed_set()
+            switch_set = self.build_switch_set(speed)
+            water_switch_set = self.build_water_valve_set(switch_set)
+        except TypeError:
+            raise MissingIOTDataError
 
         res = {
             "speed_set": speed,
@@ -339,7 +323,7 @@ class ATFC4Controller(ATFC2Controller):
         speed = super().build_speed_set()
         if self.season == Season.heating:
             speed_controller = SpeedController(self.speed, self.running_status)
-            if self.space_target > 0.0 and self.space_realtime > 0.0:
+            if self.space_target and self.space_realtime:
                 diff = abs(self.space_realtime - self.space_target)
                 if diff < 0.2:
                     speed = Speed.hold
@@ -350,20 +334,14 @@ class ATFC4Controller(ATFC2Controller):
                 else:
                     speed = Speed.high
             else:
-                if self.space_target < 0.0:
+                if not self.space_target:
                     speed = Speed.off
                 else:
                     if self.feedback == FeedbackValue.switch_on:
                         speed = Speed.medium
-                    elif (
-                            self.feedback == FeedbackValue.so_hot
-                            or self.feedback == FeedbackValue.a_little_hot
-                    ):
+                    elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
                         speed = speed_controller.turn_up()
-                    elif (
-                            self.feedback == FeedbackValue.so_cold
-                            or self.feedback == FeedbackValue.a_little_cold
-                    ):
+                    elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
                         speed = speed_controller.turn_up()
 
         return speed
@@ -374,7 +352,7 @@ class ATFC4Controller(ATFC2Controller):
             hot_water_valve_set = "off"
         else:
             if self.season == Season.heating:
-                if self.space_realtime > 0.0:
+                if self.space_realtime and self.space_target:
                     if self.space_realtime - self.space_target >= 0.2:
                         chill_water_valve_set = "on"
                         hot_water_valve_set = "off"
@@ -390,16 +368,10 @@ class ATFC4Controller(ATFC2Controller):
                     if self.feedback == FeedbackValue.switch_on:
                         chill_water_valve_set = "off"
                         hot_water_valve_set = "on"
-                    elif (
-                            self.feedback == FeedbackValue.so_hot
-                            or self.feedback == FeedbackValue.a_little_hot
-                    ):
+                    elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
                         chill_water_valve_set = "on"
                         hot_water_valve_set = "off"
-                    elif (
-                            self.feedback == FeedbackValue.so_cold
-                            or self.feedback == FeedbackValue.a_little_cold
-                    ):
+                    elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
                         chill_water_valve_set = "off"
                         hot_water_valve_set = "on"
             else:
@@ -409,11 +381,12 @@ class ATFC4Controller(ATFC2Controller):
         return chill_water_valve_set, hot_water_valve_set
 
     def run(self) -> dict:
-        speed = self.build_speed_set()
-        switch_set = self.build_switch_set(speed)
-        chill_water_valve_set, hot_water_valve_set = self.build_water_valve_set(
-            switch_set
-        )
+        try:
+            speed = self.build_speed_set()
+            switch_set = self.build_switch_set(speed)
+            chill_water_valve_set, hot_water_valve_set = self.build_water_valve_set(switch_set)
+        except TypeError:
+            raise MissingIOTDataError
 
         res = {
             "speed_set": speed,
@@ -425,65 +398,7 @@ class ATFC4Controller(ATFC2Controller):
         return res
 
 
-@logger.catch()
-async def get_fcu_control_result(project_id: str, equipment_id: str) -> Dict:
-    async with AsyncClient() as client:
-        duo_duo = Duoduo(client, project_id)
-        platform = DataPlatformService(client, project_id)
-        spaces = await duo_duo.get_space_by_equipment(equipment_id)
-        if project_id == "Pj1101150003":
-            supply_air_temperature = await platform.get_realtime_data(
-                InfoCode.supply_temperature, equipment_id
-            )
-            water_in_temperature = await platform.get_realtime_data(
-                InfoCode.water_in_temperature, equipment_id
-            )
-        else:
-            supply_air_temperature = np.NAN
-            water_in_temperature = np.NAN
-        for sp in spaces:
-            transfer = SpaceInfoService(client, project_id, sp.get("id"))
-            current_target = await transfer.get_current_temperature_target()
-            realtime_temperature = await platform.get_realtime_temperature(sp.get("id"))
-            season = await duo_duo.get_season()
-            temp_space_params = {
-                "id": sp.get("id"),
-                "temperature_target": current_target,
-                "realtime_temperature": realtime_temperature,
-            }
-            space = Space(**temp_space_params)
-            break
-
-        fcu_temp_params = {
-            "id": equipment_id,
-            "space": space,
-            "supply_air_temperature": supply_air_temperature,
-            "water_in_temperature": water_in_temperature,
-        }
-        fcu = FCU(**fcu_temp_params)
-
-    if project_id == "Pj1101050030":
-        fcu_controller = FCUController(fcu, season)
-    else:
-        fcu_controller = FCUControllerV2(fcu, season)
-    await fcu_controller.run()
-    regulated_fcu = fcu_controller.get_results()
-
-    output = {
-        "onOff": 1 if regulated_fcu.running_status else 0,
-        "mode": regulated_fcu.work_mode,
-        "speed": regulated_fcu.air_valve_speed.value,
-        "temperature": float(round_half_up(regulated_fcu.setting_temperature, 1)),
-        "water": 1 if regulated_fcu.running_status else 0,
-    }
-
-    return output
-
-
-@logger.catch()
-async def build_acatfc2_instructions(
-        params: ACATFC2InstructionsRequest,
-) -> ACATFCInstructions:
+async def build_acatfc2_instructions(params: ACATFC2InstructionsRequest) -> ACATFCInstructions:
     space = Space(
         temperature_target=params.space_temperature_target,
         realtime_temperature=params.space_realtime_temperature,
@@ -505,10 +420,7 @@ async def build_acatfc2_instructions(
     return instructions
 
 
-@logger.catch()
-async def build_acatfc4_instructions(
-        params: ACATFC4InstructionsRequest,
-) -> ACATFCInstructions:
+async def build_acatfc4_instructions(params: ACATFC4InstructionsRequest) -> ACATFCInstructions:
     space = Space(
         temperature_target=params.space_temperature_target,
         realtime_temperature=params.space_realtime_temperature,
@@ -530,14 +442,10 @@ async def build_acatfc4_instructions(
     return instructions
 
 
-@logger.catch()
 async def build_acatfc2_instructions_v2(params: ACATFC2InstructionsRequest) -> dict:
-    space_realtime = (
-        params.space_realtime_temperature if params.space_realtime_temperature else -1
-    )
     controller = ATFC2Controller(
         params.space_temperature_target,
-        space_realtime,
+        params.space_realtime_temperature,
         Season(params.season),
         params.feedback,
         params.running_status,
@@ -548,14 +456,10 @@ async def build_acatfc2_instructions_v2(params: ACATFC2InstructionsRequest) -> d
     return instructions
 
 
-@logger.catch()
 async def build_acatfc4_instructions_v2(params: ACATFC4InstructionsRequest) -> dict:
-    space_realtime = (
-        params.space_realtime_temperature if params.space_realtime_temperature else -1
-    )
     controller = ATFC4Controller(
         params.space_temperature_target,
-        space_realtime,
+        params.space_realtime_temperature,
         Season(params.season),
         params.feedback,
         params.running_status,

+ 5 - 7
app/controllers/equipment/fcu/early_start.py

@@ -2,6 +2,7 @@ from joblib import load
 from loguru import logger
 from sqlalchemy.orm import Session
 
+from app.api.errors.iot import MissingIOTDataError
 from app.core.config import settings
 from app.crud.model_path.early_start import model_path_early_start_dtr
 from app.models.domain.devices import ACATFCEarlyStartPredictionRequest
@@ -41,19 +42,16 @@ class EarlyStartTimeDTRBuilder:
         except (ValueError, IndexError) as e:
             logger.debug(e)
             pre_time = 0
+        except TypeError:
+            raise MissingIOTDataError
 
         return pre_time
 
 
-@logger.catch()
-async def build_acatfc_early_start_prediction(
-    params: ACATFCEarlyStartPredictionRequest, db: Session
-) -> float:
+async def build_acatfc_early_start_prediction(params: ACATFCEarlyStartPredictionRequest, db: Session) -> float:
     model_path = model_path_early_start_dtr.get_path_by_device(db, params.device_id)
 
     builder = EarlyStartTimeDTRBuilder(model_path, params.season)
-    hour = await builder.get_prediction(
-        params.space_realtime_temperature, params.outdoor_realtime_temperature
-    )
+    hour = await builder.get_prediction(params.space_realtime_temperature, params.outdoor_realtime_temperature)
 
     return hour * 60

+ 0 - 126
app/controllers/equipment/fcu/q_learning.py

@@ -1,126 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from typing import Dict, Optional
-
-import numpy as np
-from httpx import AsyncClient
-from loguru import logger
-
-from app.controllers.events import q_learning_models
-from app.services.platform import DataPlatformService
-from app.services.transfer import Duoduo, SpaceInfoService
-from app.services.transfer import Season
-
-
-class QLearningCommandBuilder:
-    """
-    Build FCU command by Q learning net.
-    """
-
-    def __init__(self, season: Season):
-        self.season = season
-        if season == Season.cooling:
-            self.model = q_learning_models.get('summer')
-        elif season == Season.heating:
-            self.model = q_learning_models.get('winter')
-        else:
-            self.model = None
-
-    def get_type(self, layer: int) -> str:
-        return self.model[0, layer][0, 0][0][0]
-
-    def get_weight(self, layer: int, idx: int) -> np.ndarray:
-        return self.model[0, layer][0, 0][1][0, idx]
-
-    @staticmethod
-    def linear(input_v: np.ndarray, weight: np.ndarray, bias: Optional[np.ndarray] = None) -> np.ndarray:
-        y = np.dot(weight, input_v)
-        if bias.size > 0:
-            y += bias
-
-        return y
-
-    @staticmethod
-    def relu(x: np.ndarray) -> np.ndarray:
-        return np.maximum(x, 0)
-
-    def predict_speed(self, input_v: np.ndarray) -> int:
-        result = [input_v]
-        for layer in range(self.model.shape[1]):
-            if self.get_type(layer) == 'mlp' or self.get_type(layer) == 'linear':
-                y = self.linear(result[layer], self.get_weight(layer, 0), self.get_weight(layer, 1))
-                result.append(y)
-            elif self.get_type(layer) == 'relu':
-                result.append(self.relu(result[layer]))
-
-        speed = np.argmax(result[-1])
-
-        return int(speed)
-
-    async def get_command(self, current_temperature: float, pre_temperature: float, actual_target: float) -> Dict:
-        input_value = np.array([
-            [(current_temperature - actual_target) / 5],
-            [(current_temperature - pre_temperature) / 5]
-        ])
-        speed = self.predict_speed(input_value)
-        if np.isnan(current_temperature) or np.isnan(pre_temperature):
-            speed = 2
-        if np.isnan(actual_target):
-            speed = 0
-
-        if speed == 0:
-            on_off = 0
-            water_on_off = 0
-        else:
-            on_off = 1
-            water_on_off = 1
-
-        if self.season == Season.cooling:
-            season = 1
-        elif self.season == Season.heating:
-            season = 2
-        else:
-            season = 0
-
-        command = {
-            'onOff': on_off,
-            'mode': season,
-            'speed': int(speed),
-            'temperature': actual_target if not np.isnan(actual_target) else None,
-            'water': water_on_off
-        }
-        return command
-
-
-@logger.catch()
-async def get_fcu_q_learning_control_result(project_id: str, equipment_id: str) -> Dict:
-    async with AsyncClient() as client:
-        duo_duo = Duoduo(client, project_id)
-        platform = DataPlatformService(client, project_id)
-
-        spaces = await duo_duo.get_space_by_equipment(equipment_id)
-        if not spaces:
-            logger.error(f'FCU {equipment_id} does not have space')
-            return {}
-        else:
-            if len(spaces) > 1:
-                logger.error(f'FCU {equipment_id} control more than one spaces!')
-            transfer = SpaceInfoService(client, project_id, spaces[0].get('id'))
-            season = await duo_duo.get_season()
-            current_target = await transfer.get_current_temperature_target()
-            realtime_temperature = await platform.get_realtime_temperature(spaces[0].get('id'))
-            past_temperature = await platform.get_past_temperature(spaces[0].get('id'), 15 * 60)
-
-    logger.debug(
-        f'{spaces[0]["id"]} - {equipment_id} - '
-        f'realtime Tdb: {realtime_temperature} - '
-        f'pre Tdb: {past_temperature} - '
-        f'target: {current_target}'
-    )
-    if season == Season.transition:
-        command = {}
-    else:
-        builder = QLearningCommandBuilder(season)
-        command = await builder.get_command(realtime_temperature, past_temperature, current_target)
-
-    return command

+ 7 - 48
app/controllers/equipment/pau/freq_set.py

@@ -2,8 +2,8 @@
 
 from typing import List, Tuple
 
-from loguru import logger
 import numpy as np
+from loguru import logger
 
 from app.models.domain.devices import ACATFUFreqSetRequest
 from app.schemas.season import Season
@@ -13,49 +13,14 @@ from app.utils.helpers import is_off_to_on
 
 class ACATFUFanFreqController:
     """
-    Fan freq setting logic for zhonghai.
-    """
-
-    def __init__(self, current_freq: float, hot_rate: float, cold_rate: float):
-        self.current_freq = current_freq
-        self.hot_rate = hot_rate
-        self.cold_rate = cold_rate
-
-    def get_next_set(self) -> float:
-        try:
-            cold_hot_ratio = self.cold_rate / self.hot_rate
-        except ZeroDivisionError:
-            cold_hot_ratio = 99
-
-        if cold_hot_ratio < 0.5:
-            diff = 3
-        elif cold_hot_ratio < 0.9:
-            diff = 2
-        elif cold_hot_ratio < 1.1:
-            diff = 0
-        elif cold_hot_ratio < 1.5:
-            diff = -2
-        elif cold_hot_ratio >= 1.5:
-            diff = -3
-        else:  # If cold hot rate is NAN.
-            diff = 0
-
-        next_freq_set = self.current_freq + diff
-        next_freq_set = max(30.0, min(45.0, next_freq_set))
-
-        return next_freq_set
-
-
-class ACATFUFanFreqControllerV2:
-    """
     Logic writen by Wuxu.
     """
 
     def __init__(
-        self,
-        freq: float,
-        fresh_air_temperature: float,
-        season: Season,
+            self,
+            freq: float,
+            fresh_air_temperature: float,
+            season: Season,
     ) -> None:
         self._freq = freq
         self._fresh_air_temperature = fresh_air_temperature
@@ -72,9 +37,7 @@ class ACATFUFanFreqControllerV2:
 
         return next_freq_set
 
-    def get_transition_logic(
-        self, spaces_params: List[SpaceATFU], on_flag: bool
-    ) -> float:
+    def get_transition_logic(self, spaces_params: List[SpaceATFU], on_flag: bool) -> float:
         temp_avg, co2_avg = self.get_avg(spaces_params)
         if on_flag:
             if self._fresh_air_temperature <= 16.0:
@@ -210,11 +173,7 @@ class ACATFUFanFreqControllerV2:
 
 @logger.catch()
 async def build_acatfu_freq_set(params: ACATFUFreqSetRequest) -> float:
-    controller = ACATFUFanFreqControllerV2(
-        params.freq,
-        params.fresh_air_temperature,
-        params.season,
-    )
+    controller = ACATFUFanFreqController(params.freq, params.fresh_air_temperature, params.season)
     on_flag = is_off_to_on(params.running_status_list)
     spaces = [SpaceATFU(**sp.dict()) for sp in params.spaces]
     freq_set = controller.get_next_set(spaces, on_flag)

+ 22 - 55
app/controllers/equipment/switch.py

@@ -1,15 +1,12 @@
 # -*- coding: utf-8 -*-
 
 from enum import Enum
-from typing import Dict, Tuple
 
 import arrow
-from httpx import AsyncClient
 
 from app.schemas.equipment import BaseEquipment
-from app.services.platform import DataPlatformService, InfoCode
-from app.services.transfer import Duoduo
 from app.utils.date import get_time_str, TIME_FMT
+from app.api.errors.iot import MissingIOTDataError
 
 
 class SwitchSet(str, Enum):
@@ -25,61 +22,31 @@ class Switch:
         self._now_time = arrow.get(get_time_str(), TIME_FMT).time().strftime("%H%M%S")
 
     async def build_next_action(self, is_workday: bool) -> SwitchSet:
-        if self._equip.in_cloud_status:
-            if is_workday:
-                if self._equip.on_time <= self._equip.off_time:
-                    if self._equip.on_time <= self._now_time <= self._equip.off_time:
-                        switch_flag = True
+        try:
+            if self._equip.in_cloud_status:
+                if is_workday:
+                    if self._equip.on_time <= self._equip.off_time:
+                        if self._equip.on_time <= self._now_time <= self._equip.off_time:
+                            switch_flag = True
+                        else:
+                            switch_flag = False
                     else:
-                        switch_flag = False
-                else:
-                    if self._equip.off_time <= self._now_time <= self._equip.on_time:
-                        switch_flag = False
+                        if self._equip.off_time <= self._now_time <= self._equip.on_time:
+                            switch_flag = False
+                        else:
+                            switch_flag = True
+
+                    if switch_flag and not self._equip.running_status:
+                        action = SwitchSet.on
+                    elif not switch_flag and self._equip.running_status:
+                        action = SwitchSet.off
                     else:
-                        switch_flag = True
-
-                if switch_flag and not self._equip.running_status:
-                    action = SwitchSet.on
-                elif not switch_flag and self._equip.running_status:
-                    action = SwitchSet.off
+                        action = SwitchSet.hold
                 else:
                     action = SwitchSet.hold
             else:
-                action = SwitchSet.hold
-        else:
-            action = SwitchSet.off
+                action = SwitchSet.off
+        except TypeError:
+            raise MissingIOTDataError
 
         return action
-
-
-async def fetch_data(project_id: str, equipment_id: str) -> Tuple[Dict, Dict]:
-    async with AsyncClient() as client:
-        platform = DataPlatformService(client, project_id)
-        duo_duo = Duoduo(client, project_id)
-
-        day_type = await duo_duo.get_day_type()
-        running_status = await platform.get_realtime_running_status(equipment_id)
-        cloud_status = await platform.get_cloud_status(equipment_id)
-        on_time, off_time = await platform.get_schedule(equipment_id)
-
-    equip_params = {
-        "id": equipment_id,
-        "running_status": running_status,
-        "in_cloud_status": True if cloud_status == 1.0 else False,
-        "on_time": on_time,
-        "off_time": off_time,
-    }
-
-    return equip_params, day_type
-
-
-async def send_switch_command(project_id: str, equipment_id: str, action: str) -> None:
-    async with AsyncClient() as client:
-        platform = DataPlatformService(client, project_id)
-
-        if action == "on":
-            await platform.set_code_value(equipment_id, InfoCode.equip_switch_set, 1.0)
-        elif action == "off":
-            await platform.set_code_value(equipment_id, InfoCode.equip_switch_set, 0.0)
-        else:
-            pass

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

@@ -422,7 +422,7 @@ async def build_acatva_instructions(
 
 
 @logger.catch()
-async def build_acatva_instructions_for_JM(
+async def build_acatva_instructions_for_jm(
     params: ACATVAInstructionsRequestV2,
 ) -> dict:
     # Control logic for Jiaming.

+ 4 - 8
app/controllers/equipment/vrf/basic.py

@@ -3,10 +3,9 @@ from typing import Dict, Tuple
 
 import arrow
 import numpy as np
-from loguru import logger
 from sqlalchemy.orm import Session
 
-from app.api.errors.iot import MissingIOTDataError, missing_iot_data_handler
+from app.api.errors.iot import MissingIOTDataError
 from app.controllers.equipment.controller import EquipmentController
 from app.crud.device.device import device
 from app.crud.device.status_timestamp import blowy_feedback_time, high_speed_time
@@ -143,7 +142,7 @@ class VRFController(EquipmentController):
         return new_temperature_set
 
     def get_speed_set(self) -> str:
-        if self.device.work_mode == 4.0 or self.device.work_mode == 8.0:
+        if self.device.work_mode == VRFMode.ventilation:
             if self.target is None:
                 new_speed = "hold"
             else:
@@ -169,7 +168,7 @@ class VRFController(EquipmentController):
                             new_speed = "hold"
                 else:
                     new_speed = "M"
-        elif self.device.work_mode == 1.0 or self.device.work_mode == 6.0:
+        elif self.device.work_mode == VRFMode.cooling:
             new_speed = "hold"
             if self.target is None:
                 return new_speed
@@ -202,7 +201,7 @@ class VRFController(EquipmentController):
                     new_speed = "LL"
                 else:
                     new_speed = "LL"
-        elif self.device.work_mode == 2.0 or self.device.work_mode == 7.0:
+        elif self.device.work_mode == VRFMode.heating:
             new_speed = "hold"
             if self.target is None:
                 return new_speed
@@ -276,7 +275,6 @@ class VRFController(EquipmentController):
             self.get_speed_set()
             self.get_temperature_set()
         except TypeError:
-            logger.debug("missing data error")
             raise MissingIOTDataError
 
     def get_results(self):
@@ -312,8 +310,6 @@ async def query_status_time(db: Session, device_id: str) -> Tuple[datetime, date
     return feedback_time, high_time
 
 
-@logger.catch
-@missing_iot_data_handler
 async def build_acatvi_instructions(params: ACATVIInstructionsRequest) -> Dict:
     vrf = VRF(
         return_air_temp=params.return_air_temperature,

+ 4 - 4
app/core/config.py

@@ -42,10 +42,10 @@ class Settings(BaseSettings):
     REDIS_DB: int
     REDIS_PASSWORD: str
 
-    MILVUS_HOST: str
-    MILVUS_PORT: str
-    METRIC_TYPE: str
-    VECTOR_DIMENSION: int
+    MILVUS_HOST: Optional[str] = None
+    MILVUS_PORT: Optional[str] = None
+    METRIC_TYPE: Optional[str] = None
+    VECTOR_DIMENSION: Optional[int] = None
 
     POSTGRES_SERVER: str
     POSTGRES_USER: str

+ 5 - 14
app/main.py

@@ -1,12 +1,11 @@
 # -*- coding: utf-8 -*-
 
 import logging
-from pathlib import Path
 
 import uvicorn
 from fastapi import FastAPI
-from loguru import logger
 
+from app.api.errors.iot import MissingIOTDataError, missing_data_exception_handler
 from app.api.routers import (
     algorithms,
     targets,
@@ -25,12 +24,6 @@ from app.db.session import Base, engine
 Base.metadata.create_all(bind=engine)
 
 logging.getLogger().handlers = [InterceptHandler()]
-# logger.add(
-#     Path(settings.LOGS_DIR, "env_fastapi.log"),
-#     level="INFO",
-#     rotation="00:00",
-#     encoding="utf-8",
-# )
 
 
 def get_application() -> FastAPI:
@@ -41,16 +34,14 @@ def get_application() -> FastAPI:
     application.include_router(algorithms.router, prefix="/algo", tags=["Algorithms"])
     application.include_router(bluetooth.router, prefix="/bluetooth", tags=["BLE"])
     application.include_router(devices.router, prefix="/devices", tags=["Devices"])
-    application.include_router(
-        early_start.router, prefix="/model-path", tags=["Model Path"]
-    )
+    application.include_router(early_start.router, prefix="/model-path", tags=["Model Path"])
     application.include_router(nlp.router, prefix="/nlp-service", tags=["NLP"])
-    application.include_router(
-        positioning.router, prefix="/positioning-service", tags=["Positioning Service"]
-    )
+    application.include_router(positioning.router, prefix="/positioning-service", tags=["Positioning Service"])
     application.include_router(space.router, prefix="/room", tags=["Spaces"])
     application.include_router(targets.router, prefix="/target", tags=["Targets"])
 
+    application.add_exception_handler(MissingIOTDataError, missing_data_exception_handler)
+
     return application
 
 

+ 1 - 17
app/models/domain/devices.py

@@ -23,23 +23,11 @@ class Speed(str, Enum):
     hold = "hold"
 
 
-class DevicesInstructionsBaseResponse(BaseModel):
-    project_id: str = Field(None, alias="projectId")
-    device_id: str = Field(None, alias="equipId")
-    output: Dict
-
-
-class DevicesEarlyStartTime(BaseModel):
-    project_id: str = Field(None, alias="projectId")
-    space_id: str = Field(None, alias="spaceId")
-    minutes: float
-
-
 class ACATVIInstructionsRequest(BaseModel):
     device_id: str
     return_air_temperature: Optional[float]
     running_status: Optional[bool]
-    work_mode: VRFMode
+    work_mode: Optional[VRFMode]
     current_speed: Optional[str]
     current_temperature_set: Optional[float]
     space_temperature_target: Optional[float]
@@ -49,10 +37,6 @@ class ACATVIInstructionsRequest(BaseModel):
     off_time: Optional[str]
 
 
-class ACATVIInstructionsTemporaryResponse(BaseModel):
-    output: Dict
-
-
 class ACATVIInstructionsResponse(BaseModel):
     switch_set: Optional[str]
     speed_set: Optional[str]

+ 14 - 14
app/schemas/equipment.py

@@ -25,11 +25,11 @@ class VRFMode(str, Enum):
 
 class BaseEquipment(BaseModel):
     id: Optional[str]
-    running_status: Optional[bool] = False
+    running_status: Optional[bool]
     in_cloud_status: Optional[bool]
     on_time: Optional[str]
     off_time: Optional[str]
-    equip_switch_set: Optional[bool] = False
+    equip_switch_set: Optional[bool]
 
 
 class FCU(BaseEquipment):
@@ -38,10 +38,10 @@ class FCU(BaseEquipment):
     air_valve_speed_set: Optional[AirValveSpeed] = AirValveSpeed.off
     recommended_speed: Optional[AirValveSpeed] = AirValveSpeed.off
     space: Optional[Space]
-    setting_temperature: Optional[float] = 0.0
-    supply_air_temperature: Optional[float] = 0.0
-    water_out_temperature: Optional[float] = 0.0
-    water_in_temperature: Optional[float] = 0.0
+    setting_temperature: Optional[float]
+    supply_air_temperature: Optional[float]
+    water_out_temperature: Optional[float]
+    water_in_temperature: Optional[float]
     speed_limit: Optional[AirValveSpeed] = AirValveSpeed.high
 
 
@@ -60,14 +60,14 @@ class VAVBox(BaseEquipment):
 
 
 class AHU(BaseEquipment):
-    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
-    freq: Optional[float] = np.NAN
-    freq_set: Optional[float] = np.NAN
-    fan_freq_upper_limit_set: Optional[float] = np.NAN
-    fan_freq_lower_limit_set: Optional[float] = np.NAN
+    supply_air_temperature: Optional[float]
+    supply_air_temperature_set: Optional[float]
+    return_air_temperature: Optional[float]
+    return_air_temperature_set: Optional[float]
+    freq: Optional[float]
+    freq_set: Optional[float]
+    fan_freq_upper_limit_set: Optional[float]
+    fan_freq_lower_limit_set: Optional[float]
 
 
 class VentilationFan(BaseEquipment):

+ 4 - 12
app/schemas/space.py

@@ -3,20 +3,12 @@
 from typing import List, Optional
 
 import numpy as np
-from pydantic import BaseModel as PydanticBaseModel, validator
-
-
-class BaseModel(PydanticBaseModel):
-    @validator("*")
-    def change_to_nan(cls, v):
-        if v == -999.9:
-            v = np.NAN
-        return v
+from pydantic import BaseModel
 
 
 class SpaceBase(BaseModel):
     id: Optional[str]
-    realtime_temperature: float = np.NAN
+    realtime_temperature: Optional[float]
 
 
 class Space(SpaceBase):
@@ -33,8 +25,8 @@ class SpaceATVA(Space):
 
 
 class SpaceATAH(Space):
-    ahu_default_weight: Optional[float] = np.NAN
-    ahu_temporary_weight: Optional[float] = np.NAN
+    ahu_default_weight: Optional[float]
+    ahu_temporary_weight: Optional[float]
     ahu_temporary_update_time: Optional[str] = ""