supply_air_temperature_set.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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: 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 = current
  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 - 1.0
  39. elif cold_ratio < 0.45:
  40. new = self.current - 0.5
  41. elif cold_ratio <= 0.55:
  42. new = self.current
  43. elif cold_ratio <= 0.7:
  44. new = self.current + 1.0
  45. elif cold_ratio <= 1.0:
  46. new = self.current + 1.5
  47. else:
  48. new = self.current
  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 - 1.0
  54. elif cold_ratio <= 0.55:
  55. new = self.current
  56. elif cold_ratio <= 0.7:
  57. new = self.current + 0.5
  58. elif cold_ratio <= 1.0:
  59. new = self.current + 1.0
  60. else:
  61. new = self.current
  62. else:
  63. new = self.current
  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. self.season
  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
  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 hot_water_valve_opening_set_duration[-1]['value'] == 0.0:
  154. # thermal_mode = ThermalMode.cooling
  155. if chill_water_valve_opening_set_duration[-1]['value'] == 0.0:
  156. thermal_mode = ThermalMode.heating
  157. else:
  158. thermal_mode = ThermalMode.cooling
  159. is_off_to_on = False
  160. if on_off_set_duration[-1]['value'] == 1.0:
  161. for item in on_off_set_duration[::-1]:
  162. if item['value'] == 0.0:
  163. is_off_to_on = True
  164. break
  165. # logger.debug(f'{device_id} was off to on: {is_off_to_on}')
  166. is_thermal_mode_switched = False
  167. if len(set([item['value'] for item in hot_water_valve_opening_set_duration])) > 1:
  168. is_thermal_mode_switched = True
  169. if len(set([item['value'] for item in chill_water_valve_opening_set_duration])) > 1:
  170. is_thermal_mode_switched = True
  171. controller = ACATAHSupplyAirTemperatureController(
  172. vav_boxes_lit,
  173. current_supply_air_temperature,
  174. return_air_temperature,
  175. thermal_mode,
  176. is_off_to_on,
  177. is_thermal_mode_switched,
  178. season
  179. )
  180. next_supply_air_temperature_set = controller.build()
  181. return next_supply_air_temperature_set
  182. async def get_default(project_id: str) -> float:
  183. async with AsyncClient() as client:
  184. weather_service = WeatherService(client)
  185. realtime_weather = await weather_service.get_realtime_weather(project_id)
  186. if realtime_weather.get('text') == '晴':
  187. is_clear_day = True
  188. else:
  189. is_clear_day = False
  190. controller = ACATAHSupplyAirTemperatureDefaultController(is_clear_day)
  191. next_supply_air_temperature_set = controller.build()
  192. return next_supply_air_temperature_set
  193. @logger.catch()
  194. async def get_next_supply_air_temperature_set(project_id: str, device_id: str) -> float:
  195. try:
  196. new = await get_planned(project_id, device_id)
  197. except KeyError and IndexError:
  198. new = await get_default(project_id)
  199. logger.debug(f'next supply air temperature set: {device_id} - {new}')
  200. return new
  201. @logger.catch()
  202. def build_acatah_supply_air_temperature_set(params: ACATAHSupplyAirTempSetRequest) -> float:
  203. try:
  204. vav_list = list()
  205. for raw_vav in params.vav_list:
  206. vav = VAVBox(**raw_vav.dict())
  207. vav.virtual_target_temperature = raw_vav.virtual_temperature_target
  208. vav_list.append(vav)
  209. if params.chill_water_valve_opening_set_list[-1] == 0.0:
  210. thermal_mode = ThermalMode.heating
  211. else:
  212. thermal_mode = ThermalMode.cooling
  213. is_off_to_on = False
  214. if params.equip_switch_set_list[-1] == 1.0:
  215. for item in params.equip_switch_set_list[::-1]:
  216. if item == 0.0:
  217. is_off_to_on = True
  218. break
  219. is_thermal_mode_switched = False
  220. if len(set([item for item in params.hot_water_valve_opening_set_list])) > 1:
  221. is_thermal_mode_switched = True
  222. if len(set([item for item in params.chill_water_valve_opening_set_list])) > 1:
  223. is_thermal_mode_switched = True
  224. controller = ACATAHSupplyAirTemperatureController(
  225. vav_list,
  226. params.supply_air_temperature,
  227. params.return_air_temperature,
  228. thermal_mode,
  229. is_off_to_on,
  230. is_thermal_mode_switched,
  231. Season(params.season)
  232. )
  233. supply_air_temperature_set = controller.build()
  234. except (KeyError, IndexError):
  235. controller = ACATAHSupplyAirTemperatureDefaultController(params.is_clear_day)
  236. supply_air_temperature_set = controller.build()
  237. return supply_air_temperature_set