Browse Source

add restriction and recovery logic for ACATVI

highing666 3 years ago
parent
commit
db81c687e0

+ 187 - 50
app/controllers/equipment/vrf/basic.py

@@ -1,17 +1,29 @@
-from typing import Dict
+from datetime import datetime
+from typing import Dict, Optional, Tuple
 
+import arrow
 import numpy as np
 from loguru import logger
+from sqlalchemy.orm import Session
+from sqlalchemy.exc import SQLAlchemyError
 
 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
 from app.models.domain.devices import ACATVIInstructionsRequest
 from app.models.domain.feedback import FeedbackValue
+from app.schemas.device.device import DeviceCreate
 from app.schemas.equipment import VRF
+from app.schemas.device.status_timestamp import (
+    BlowyFeedbackTimeCreate,
+    BlowyFeedbackTimeUpdate,
+    HighSpeedTimeCreate,
+    HighSpeedTimeUpdate,
+)
 from app.utils.math import round_half_up
 
 
 class VRFController(EquipmentController):
-
     def __init__(self, device: VRF, target: float, realtime: float):
         super(VRFController, self).__init__()
         self.device = device
@@ -20,9 +32,9 @@ class VRFController(EquipmentController):
 
     def get_switch_set(self) -> str:
         if np.isnan(self.target):
-            switch_set = 'off'
+            switch_set = "off"
         else:
-            switch_set = 'on'
+            switch_set = "on"
 
         self.device.equip_switch_set = switch_set
 
@@ -30,9 +42,9 @@ class VRFController(EquipmentController):
 
     def get_mode_set(self) -> str:
         if self.target > self.realtime:
-            mode_set = 'cooling'
+            mode_set = "cooling"
         else:
-            mode_set = 'cooling'
+            mode_set = "cooling"
 
         self.device.mode_set = mode_set
 
@@ -65,16 +77,16 @@ class VRFController(EquipmentController):
 
     def get_speed_set(self) -> str:
         if self.realtime >= 24.0:
-            speed = 'M'
+            speed = "M"
         elif self.realtime <= 22.0:
-            speed = 'LL'
+            speed = "LL"
         elif self.device.return_air_temp - self.target >= 3.0:
-            speed = 'HH'
+            speed = "HH"
         elif self.device.return_air_temp - self.target >= 1.0:
-            speed = 'M'
+            speed = "M"
         else:
-            if self.device.speed == 'missing':
-                speed = 'M'
+            if self.device.speed == "missing":
+                speed = "M"
             else:
                 speed = self.device.speed
 
@@ -83,7 +95,7 @@ class VRFController(EquipmentController):
         return speed
 
     async def run(self):
-        if self.get_switch_set() == 'on':
+        if self.get_switch_set() == "on":
             self.get_speed_set()
             self.get_mode_set()
             self.get_temperature_set()
@@ -92,25 +104,34 @@ class VRFController(EquipmentController):
                 self.device.temperature_set = 0.0
 
             if self.device.speed_set == self.device.speed:
-                self.device.speed_set = 'hold'
-                self.device.equip_switch_set = 'hold'
+                self.device.speed_set = "hold"
+                self.device.equip_switch_set = "hold"
 
             if not self.device.running_status:
-                self.device.equip_switch_set = 'on'
+                self.device.equip_switch_set = "on"
                 self.get_speed_set()
         else:
             if not self.device.running_status:
-                self.device.equip_switch_set = 'hold'
+                self.device.equip_switch_set = "hold"
 
     def get_results(self):
         return self.device
 
 
 class VRFController1(VRFController):
-
-    def __init__(self, device: VRF,  target: float, realtime: float, feedback: FeedbackValue):
+    def __init__(
+        self,
+        device: VRF,
+        target: float,
+        realtime: float,
+        feedback: FeedbackValue,
+        blow_feedback_time: datetime,
+        high_speed_time: datetime,
+    ):
         super(VRFController1, self).__init__(device, target, realtime)
         self.feedback = feedback
+        self.blow_feedback_time = blow_feedback_time
+        self.high_speed_time = high_speed_time
 
     def get_temperature_set(self) -> float:
         new_temperature_set = self.device.current_temperature_set
@@ -128,16 +149,22 @@ class VRFController1(VRFController):
         #     new_temperature_set = self.target
 
         # feedback
-        if self.feedback == FeedbackValue.a_little_cold or self.feedback == FeedbackValue.so_cold:
-            if self.feedback == FeedbackValue.so_cold and self.device.speed == 'LL':
+        if (
+            self.feedback == FeedbackValue.a_little_cold
+            or self.feedback == FeedbackValue.so_cold
+        ):
+            if self.feedback == FeedbackValue.so_cold and self.device.speed == "LL":
                 new_temperature_set = self.device.current_temperature_set + 2.0
             else:
                 new_temperature_set = self.device.current_temperature_set + 1.0
                 if self.device.return_air_temp <= self.device.current_temperature_set:
                     if self.feedback == FeedbackValue.so_cold:
                         new_temperature_set = self.device.current_temperature_set + 2.0
-        elif self.feedback == FeedbackValue.a_little_hot or self.feedback == FeedbackValue.so_hot:
-            if self.feedback == FeedbackValue.so_hot and self.device.speed == 'HH':
+        elif (
+            self.feedback == FeedbackValue.a_little_hot
+            or self.feedback == FeedbackValue.so_hot
+        ):
+            if self.feedback == FeedbackValue.so_hot and self.device.speed == "HH":
                 new_temperature_set = self.device.current_temperature_set - 2.0
             else:
                 new_temperature_set = self.device.current_temperature_set - 1.0
@@ -152,62 +179,172 @@ class VRFController1(VRFController):
 
         # Default speed set:
         if not self.device.running_status:
-            new_speed = 'M'
+            new_speed = "M"
 
         # Lower limit.
         if self.realtime <= 22.0:
-            new_speed = 'LL'
+            new_speed = "LL"
 
         # Feedback.
         if self.feedback == FeedbackValue.so_cold:
             if self.device.return_air_temp > self.device.current_temperature_set:
-                if self.device.speed == 'HH':
-                    new_speed = 'M'
-                elif self.device.speed == 'M':
-                    new_speed = 'LL'
+                if self.device.speed == "HH":
+                    new_speed = "M"
+                elif self.device.speed == "M":
+                    new_speed = "LL"
         elif self.feedback == FeedbackValue.so_hot:
-            if self.device.speed == 'LL':
-                new_speed = 'M'
-            elif self.device.speed == 'M':
-                new_speed = 'HH'
+            if self.device.speed == "LL":
+                new_speed = "M"
+            elif self.device.speed == "M":
+                new_speed = "HH"
         elif self.feedback == FeedbackValue.noisy_or_blowy:
-            if self.device.speed == 'HH':
-                new_speed = 'M'
-            elif self.device.speed == 'M':
-                new_speed = 'LL'
+            if self.device.speed == "HH":
+                new_speed = "M"
+            elif self.device.speed == "M":
+                new_speed = "LL"
             else:
-                new_speed = 'LL'
+                new_speed = "LL"
+
+        a_now = arrow.utcnow()
+
+        # Restriction.
+        if a_now.date() == arrow.get(self.blow_feedback_time).date():
+            if new_speed == "HH":
+                new_speed = "M"
+
+        # Recovery.
+        if (a_now - arrow.get(self.high_speed_time)).seconds > 60 * 60:
+            if new_speed == "HH":
+                new_speed = "M"
 
         self.device.speed_set = new_speed
 
         return new_speed
 
 
+async def query_status_time(db: Session, device_id: str) -> Tuple[datetime, datetime]:
+    feedback_time_in_db = blowy_feedback_time.get_time_by_device(db, device_id)
+    if feedback_time_in_db:
+        feedback_time = feedback_time_in_db.timestamp
+    else:
+        past = arrow.utcnow().shift(hours=-24)
+        feedback_time = past.naive
+        if not device.get(db, device_id):
+            device.create(db=db, obj_in=DeviceCreate(id=device_id))
+        blowy_feedback_time.create(
+            db=db,
+            obj_in=BlowyFeedbackTimeCreate(
+                timestamp=feedback_time, device_id=device_id
+            ),
+        )
+
+    high_speed_time_in_db = high_speed_time.get_time_by_device(db, device_id)
+    if high_speed_time_in_db:
+        high_time = high_speed_time_in_db.timestamp
+    else:
+        past = arrow.utcnow().shift(hours=-24)
+        high_time = past.naive
+        if not device.get(db, device_id):
+            device.create(db=db, obj_in=DeviceCreate(id=device_id))
+        high_speed_time.create(
+            db=db, obj_in=HighSpeedTimeCreate(timestamp=high_time, device_id=device_id)
+        )
+
+    return feedback_time, high_time
+
+
 @logger.catch()
-async def build_acatvi_instructions(params: ACATVIInstructionsRequest) -> Dict:
-    target = np.NAN if params.space_temperature_target == -1.0 else params.space_temperature_target
-    realtime = np.NAN if params.space_realtime_temperature == -1.0 else params.space_realtime_temperature
-    return_air_temp = np.NAN if params.return_air_temperature == -1.0 else params.return_air_temperature
-    current_temperature_set = np.NAN if params.current_temperature_set == -1.0 else round_half_up(
-        params.current_temperature_set
+async def build_acatvi_instructions(
+    params: ACATVIInstructionsRequest, db: Session
+) -> Dict:
+    target = (
+        np.NAN
+        if params.space_temperature_target == -1.0
+        else params.space_temperature_target
+    )
+    realtime = (
+        np.NAN
+        if params.space_realtime_temperature == -1.0
+        else params.space_realtime_temperature
+    )
+    return_air_temp = (
+        np.NAN
+        if params.return_air_temperature == -1.0
+        else params.return_air_temperature
+    )
+    current_temperature_set = (
+        np.NAN
+        if params.current_temperature_set == -1.0
+        else round_half_up(params.current_temperature_set)
     )
 
     vrf = VRF(
         return_air_temp=return_air_temp,
         current_temperature_set=current_temperature_set,
         speed=params.current_speed,
-        running_status=params.running_status
+        running_status=params.running_status,
+    )
+
+    act_blowy_feedback_time, act_high_speed_time = await query_status_time(
+        db, params.device_id
     )
 
-    controller = VRFController1(vrf, target, realtime, params.feedback)
+    controller = VRFController1(
+        vrf,
+        target,
+        realtime,
+        params.feedback,
+        act_blowy_feedback_time,
+        act_high_speed_time,
+    )
     await controller.run()
     regulated_vrf = controller.get_results()
 
+    if params.feedback == FeedbackValue.noisy_or_blowy:
+        blowy_feedback_time_in_db = blowy_feedback_time.get_time_by_device(
+            db=db, device_id=params.device_id
+        )
+        if blowy_feedback_time_in_db:
+            blowy_feedback_time.update(
+                db=db,
+                db_obj=blowy_feedback_time_in_db,
+                obj_in=BlowyFeedbackTimeUpdate(
+                    timestamp=datetime.utcnow(), device_id=params.device_id
+                ),
+            )
+        else:
+            blowy_feedback_time.create(
+                db=db,
+                obj_in=BlowyFeedbackTimeCreate(
+                    timestamp=datetime.utcnow(), device_id=params.device_id
+                ),
+            )
+
+    if regulated_vrf.speed_set == "HH":
+        high_speed_time_in_db = high_speed_time.get_time_by_device(
+            db=db, device_id=params.device_id
+        )
+        if high_speed_time_in_db:
+            high_speed_time.update(
+                db=db,
+                db_obj=high_speed_time_in_db,
+                obj_in=HighSpeedTimeUpdate(
+                    timestamp=datetime.utcnow(), device_id=params.device_id
+                ),
+            )
+        else:
+            high_speed_time.create(
+                db=db,
+                obj_in=HighSpeedTimeCreate(
+                    timestamp=datetime.utcnow(), device_id=params.device_id
+                ),
+            )
+
     instructions = {
-        'switch_set': regulated_vrf.equip_switch_set,
-        'speed_set': regulated_vrf.speed_set,
-        'temperature_set': regulated_vrf.temperature_set,
-        'mode_set': regulated_vrf.mode_set
+        "switch_set": regulated_vrf.equip_switch_set,
+        "speed_set": regulated_vrf.speed_set,
+        "temperature_set": regulated_vrf.temperature_set,
+        "mode_set": regulated_vrf.mode_set,
     }
 
     return instructions

+ 0 - 0
app/crud/device/__init__.py


+ 10 - 0
app/crud/device/device.py

@@ -0,0 +1,10 @@
+from app.crud.base import CRUDBase
+from app.models.devices.device import Device
+from app.schemas.device.device import DeviceCreate, DeviceUpdate
+
+
+class CRUDDevice(CRUDBase[Device, DeviceCreate, DeviceUpdate]):
+    pass
+
+
+device = CRUDDevice(Device)

+ 42 - 0
app/crud/device/status_timestamp.py

@@ -0,0 +1,42 @@
+from app.schemas.device.device import Device
+from typing import Optional
+from sqlalchemy.orm import Session
+
+from app.crud.base import CRUDBase
+from app.models.devices.status_timestamp import BlowyFeedbackTime, HighSpeedTime
+from app.schemas.device.status_timestamp import (
+    BlowyFeedbackTimeCreate,
+    BlowyFeedbackTimeUpdate,
+    HighSpeedTimeCreate,
+    HighSpeedTimeUpdate,
+)
+
+
+class CRUDBlowyFeedbackTime(
+    CRUDBase[
+        BlowyFeedbackTime,
+        BlowyFeedbackTimeCreate,
+        BlowyFeedbackTimeUpdate,
+    ]
+):
+    def get_time_by_device(
+        self, db: Session, device_id: str
+    ) -> Optional[BlowyFeedbackTime]:
+        return (
+            db.query(self.model)
+            .filter(BlowyFeedbackTime.device_id == device_id)
+            .first()
+        )
+
+
+class CRUDHighSpeedTime(
+    CRUDBase[HighSpeedTime, HighSpeedTimeCreate, HighSpeedTimeUpdate]
+):
+    def get_time_by_device(
+        self, db: Session, device_id: str
+    ) -> Optional[HighSpeedTime]:
+        return db.query(self.model).filter(HighSpeedTime.device_id == device_id).first()
+
+
+blowy_feedback_time = CRUDBlowyFeedbackTime(BlowyFeedbackTime)
+high_speed_time = CRUDHighSpeedTime(HighSpeedTime)

+ 17 - 0
app/models/devices/device.py

@@ -0,0 +1,17 @@
+from typing import TYPE_CHECKING
+
+from sqlalchemy import Column, String
+from sqlalchemy.orm import relationship
+
+from app.db.session import Base
+
+if TYPE_CHECKING:
+    from app.models.devices.status_timestamp import BlowyFeedbackTime, HighSpeedTime
+
+
+class Device(Base):
+    __tablename__ = "devices"
+
+    id = Column(String, primary_key=True, index=True)
+    blowy_feedback_time = relationship("BlowyFeedbackTime", back_populates="device")
+    high_speed_time = relationship("HighSpeedTime", back_populates="device")

+ 32 - 0
app/models/devices/status_timestamp.py

@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+
+from datetime import datetime
+from typing import TYPE_CHECKING
+
+from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from app.db.session import Base
+
+if TYPE_CHECKING:
+    from app.models.devices.device import Device
+
+
+class BlowyFeedbackTime(Base):
+    __tablename__ = "blowy_feedback_time"
+
+    id = Column(Integer, primary_key=True, index=True)
+    timestamp = Column(DateTime, default=datetime.utcnow())
+    device_id = Column(String, ForeignKey("devices.id"))
+
+    device = relationship("Device", back_populates="blowy_feedback_time")
+
+
+class HighSpeedTime(Base):
+    __tablename__ = "high_speed_time"
+
+    id = Column(Integer, primary_key=True, index=True)
+    timestamp = Column(DateTime, default=datetime.utcnow())
+    device_id = Column(String, ForeignKey("devices.id"))
+    
+    device = relationship("Device", back_populates="high_speed_time")

+ 0 - 0
app/schemas/device/__init__.py


+ 28 - 0
app/schemas/device/device.py

@@ -0,0 +1,28 @@
+from pydantic import BaseModel
+
+
+class DeviceBase(BaseModel):
+    pass
+
+
+class DeviceCreate(DeviceBase):
+    id: str
+
+
+class DeviceUpdate(DeviceBase):
+    pass
+
+
+class DeviceInDBBase(DeviceBase):
+    id: str
+
+    class Config:
+        orm_mode = True
+
+
+class Device(DeviceInDBBase):
+    pass
+
+
+class DeviceInDB(DeviceInDBBase):
+    pass

+ 64 - 0
app/schemas/device/status_timestamp.py

@@ -0,0 +1,64 @@
+from datetime import datetime
+from typing import Optional
+
+from pydantic.main import BaseModel
+
+
+class BlowyFeedbackTimeBase(BaseModel):
+    timestamp: Optional[datetime] = None
+    device_id: Optional[str] = None
+
+
+class BlowyFeedbackTimeCreate(BlowyFeedbackTimeBase):
+    timestamp: datetime
+    device_id: str
+
+
+class BlowyFeedbackTimeUpdate(BlowyFeedbackTimeBase):
+    timestamp: datetime
+    device_id: str
+
+
+class BlowyFeedbackTimeInDBBase(BlowyFeedbackTimeBase):
+    id: int
+
+    class Config:
+        orm_mode = True
+
+
+class BlowyFeedbackTime(BlowyFeedbackTimeInDBBase):
+    pass
+
+
+class BlowyFeedbackTimeInDB(BlowyFeedbackTimeInDBBase):
+    pass
+
+
+class HighSpeedTimeBase(BaseModel):
+    timestamp: Optional[datetime] = None
+    device_id: Optional[str] = None
+
+
+class HighSpeedTimeCreate(HighSpeedTimeBase):
+    timestamp: datetime
+    device_id: str
+
+
+class HighSpeedTimeUpdate(HighSpeedTimeBase):
+    timestamp: datetime
+    device_id: str
+
+
+class HighSpeedTimeInDBBase(HighSpeedTimeBase):
+    id: int
+
+    class Config:
+        orm_mode = True
+
+
+class HighSpeedTime(HighSpeedTimeInDBBase):
+    pass
+
+
+class HighSpeedTimeInDB(HighSpeedTimeInDBBase):
+    pass