supply_air_temperature_set.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. from typing import List
  2. import arrow
  3. import numpy as np
  4. from httpx import AsyncClient
  5. from loguru import logger
  6. from app.controllers.equipment.ahu.thermal_mode import count_vav_box_weight, fetch_status_params
  7. from app.models.domain.devices import ThermalMode, ACATAHSupplyAirTempSetRequest
  8. from app.schemas.equipment import VAVBox
  9. from app.services.platform import DataPlatformService, InfoCode
  10. from app.services.transfer import Duoduo, Season
  11. from app.services.weather import WeatherService
  12. from app.utils.date import get_time_str, TIME_FMT
  13. class ACATAHSupplyAirTemperatureController:
  14. """
  15. Supply air temperature setting logic version 2 by WuXu.
  16. """
  17. def __init__(
  18. self,
  19. vav_boxes_list: List[VAVBox],
  20. current_set: float,
  21. return_air: float,
  22. thermal_mode: ThermalMode,
  23. is_off_to_on: bool,
  24. is_thermal_mode_switched: bool,
  25. season: Season,
  26. ):
  27. super(ACATAHSupplyAirTemperatureController, self).__init__()
  28. self.vav_boxes_list = vav_boxes_list
  29. self.current_set = current_set
  30. self.return_air = return_air
  31. self.thermal_mode = thermal_mode
  32. self.is_off_to_on = is_off_to_on
  33. self.is_thermal_mode_switched = is_thermal_mode_switched
  34. self.season = season
  35. def calculate_by_cold_vav(self, cold_ratio: float) -> float:
  36. if self.thermal_mode == ThermalMode.cooling:
  37. if cold_ratio < 0.3:
  38. new = self.current_set - 1.0
  39. elif cold_ratio < 0.45:
  40. new = self.current_set - 0.5
  41. elif cold_ratio <= 0.55:
  42. new = self.current_set
  43. elif cold_ratio <= 0.7:
  44. new = self.current_set + 1.0
  45. elif cold_ratio <= 1.0:
  46. new = self.current_set + 1.5
  47. else:
  48. new = self.current_set
  49. elif self.thermal_mode == ThermalMode.heating:
  50. if cold_ratio < 0.3:
  51. new = self.return_air
  52. elif cold_ratio < 0.45:
  53. new = self.current_set - 1.0
  54. elif cold_ratio <= 0.55:
  55. new = self.current_set
  56. elif cold_ratio <= 0.7:
  57. new = self.current_set + 0.5
  58. elif cold_ratio <= 1.0:
  59. new = self.current_set + 1.0
  60. else:
  61. new = self.current_set
  62. else:
  63. new = self.current_set
  64. return new
  65. def get_cold_ratio(self):
  66. cold, total = 0, 0
  67. for box in self.vav_boxes_list:
  68. temp = count_vav_box_weight(
  69. box.virtual_realtime_temperature,
  70. box.virtual_target_temperature,
  71. box.supply_air_flow_upper_limit,
  72. box.supply_air_flow_lower_limit,
  73. box.supply_air_flow_set,
  74. box.valve_opening,
  75. box.supply_air_temperature
  76. )
  77. cold += temp if temp < 0 else 0
  78. total += abs(temp)
  79. try:
  80. cold_ratio = abs(cold / total)
  81. except ZeroDivisionError:
  82. cold_ratio = np.NAN
  83. logger.debug(f"cold ratio: {cold_ratio}")
  84. return cold_ratio
  85. def get_normal_ratio(self):
  86. normal = 0
  87. for box in self.vav_boxes_list:
  88. if abs(box.virtual_realtime_temperature - box.virtual_target_temperature) <= 1:
  89. normal += 1
  90. try:
  91. ratio = normal / len(self.vav_boxes_list)
  92. except ZeroDivisionError:
  93. ratio = np.NAN
  94. return ratio
  95. def build(self) -> float:
  96. if not self.is_off_to_on:
  97. normal_ratio = self.get_normal_ratio()
  98. if normal_ratio < 0.9:
  99. cold_ratio = self.get_cold_ratio()
  100. temperature = self.calculate_by_cold_vav(cold_ratio)
  101. else:
  102. temperature = self.current_set
  103. else:
  104. if self.season == Season.heating:
  105. temperature = 27.0
  106. elif self.season == Season.cooling:
  107. temperature = 20.0
  108. else:
  109. temperature = 25.0
  110. if self.season == Season.heating:
  111. temperature = max(20.0, min(30.0, temperature))
  112. else:
  113. temperature = max(18.0, min(25.0, temperature))
  114. return temperature
  115. class ACATAHSupplyAirTemperatureDefaultController:
  116. """
  117. Determine supply air temperature when missing data.
  118. """
  119. def __init__(self, is_clear_day: bool):
  120. super(ACATAHSupplyAirTemperatureDefaultController, self).__init__()
  121. self.is_clear_day = is_clear_day
  122. def build(self) -> float:
  123. now = get_time_str()
  124. now_time_str = arrow.get(now, TIME_FMT).time().strftime("%H%M%S")
  125. if "080000" <= now_time_str < "100000":
  126. is_morning = True
  127. else:
  128. is_morning = False
  129. if is_morning:
  130. temperature = 27.0
  131. else:
  132. if self.is_clear_day:
  133. temperature = 23.0
  134. else:
  135. temperature = 25.0
  136. return temperature
  137. async def get_planned(project_id: str, device_id: str) -> float:
  138. vav_boxes_params = await fetch_status_params(project_id, device_id)
  139. vav_boxes_lit = vav_boxes_params["vav_boxes_list"]
  140. async with AsyncClient() as client:
  141. platform = DataPlatformService(client, project_id)
  142. duoduo = Duoduo(client, project_id)
  143. current_supply_air_temperature = await platform.get_realtime_supply_air_temperature(device_id)
  144. return_air_temperature = await platform.query_realtime_return_air_temperature(device_id)
  145. hot_water_valve_opening_set_duration = await platform.get_duration(
  146. InfoCode.hot_water_valve_opening_set, device_id, 15 * 60
  147. )
  148. chill_water_valve_opening_set_duration = await platform.get_duration(
  149. InfoCode.chill_water_valve_opening_set, device_id, 15 * 60
  150. )
  151. on_off_set_duration = await platform.get_duration(InfoCode.equip_switch_set, device_id, 20 * 60)
  152. season = await duoduo.get_season()
  153. if chill_water_valve_opening_set_duration[-1]["value"] == 0.0:
  154. thermal_mode = ThermalMode.heating
  155. else:
  156. thermal_mode = ThermalMode.cooling
  157. is_off_to_on = False
  158. if on_off_set_duration[-1]["value"] == 1.0:
  159. for item in on_off_set_duration[::-1]:
  160. if item["value"] == 0.0:
  161. is_off_to_on = True
  162. break
  163. is_thermal_mode_switched = False
  164. if len(set([item["value"] for item in hot_water_valve_opening_set_duration])) > 1:
  165. is_thermal_mode_switched = True
  166. if len(set([item["value"] for item in chill_water_valve_opening_set_duration])) > 1:
  167. is_thermal_mode_switched = True
  168. controller = ACATAHSupplyAirTemperatureController(
  169. vav_boxes_lit,
  170. current_supply_air_temperature,
  171. return_air_temperature,
  172. thermal_mode,
  173. is_off_to_on,
  174. is_thermal_mode_switched,
  175. season,
  176. )
  177. next_supply_air_temperature_set = controller.build()
  178. return next_supply_air_temperature_set
  179. async def get_default(project_id: str) -> float:
  180. async with AsyncClient() as client:
  181. weather_service = WeatherService(client)
  182. realtime_weather = await weather_service.get_realtime_weather(project_id)
  183. if realtime_weather.get("text") == "晴":
  184. is_clear_day = True
  185. else:
  186. is_clear_day = False
  187. controller = ACATAHSupplyAirTemperatureDefaultController(is_clear_day)
  188. next_supply_air_temperature_set = controller.build()
  189. return next_supply_air_temperature_set
  190. @logger.catch
  191. async def get_next_supply_air_temperature_set(project_id: str, device_id: str) -> float:
  192. try:
  193. new = await get_planned(project_id, device_id)
  194. except KeyError and IndexError:
  195. new = await get_default(project_id)
  196. logger.debug(f"next supply air temperature set: {device_id} - {new}")
  197. return new
  198. @logger.catch
  199. def build_acatah_supply_air_temperature_set(
  200. params: ACATAHSupplyAirTempSetRequest,
  201. ) -> float:
  202. try:
  203. vav_list = list()
  204. for raw_vav in params.vav_list:
  205. vav = VAVBox(**raw_vav.dict())
  206. vav.virtual_target_temperature = raw_vav.virtual_temperature_target
  207. vav_list.append(vav)
  208. if params.chill_water_valve_opening_set_list[-1] == 0.0:
  209. thermal_mode = ThermalMode.heating
  210. else:
  211. thermal_mode = ThermalMode.cooling
  212. is_off_to_on = False
  213. if params.equip_switch_set_list[-1] == 1.0:
  214. for item in params.equip_switch_set_list[::-1]:
  215. if item == 0.0:
  216. is_off_to_on = True
  217. break
  218. is_thermal_mode_switched = False
  219. if len(set([item for item in params.hot_water_valve_opening_set_list])) > 1:
  220. is_thermal_mode_switched = True
  221. if len(set([item for item in params.chill_water_valve_opening_set_list])) > 1:
  222. is_thermal_mode_switched = True
  223. controller = ACATAHSupplyAirTemperatureController(
  224. vav_list,
  225. params.supply_air_temperature_set,
  226. params.return_air_temperature,
  227. thermal_mode,
  228. is_off_to_on,
  229. is_thermal_mode_switched,
  230. Season(params.season),
  231. )
  232. supply_air_temperature_set = controller.build()
  233. except (KeyError, IndexError):
  234. controller = ACATAHSupplyAirTemperatureDefaultController(params.is_clear_day)
  235. supply_air_temperature_set = controller.build()
  236. return supply_air_temperature_set