supply_air_temperature_set.py 9.8 KB

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