basic.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. # -*- coding: utf-8 -*-
  2. from typing import Dict, Tuple
  3. import numpy as np
  4. from httpx import AsyncClient
  5. from loguru import logger
  6. from app.controllers.equipment.controller import EquipmentController
  7. from app.models.domain.devices import (
  8. ACATFC2InstructionsRequest,
  9. ACATFC4InstructionsRequest,
  10. Speed,
  11. )
  12. from app.models.domain.feedback import FeedbackValue
  13. from app.schemas.equipment import AirValveSpeed, FCU
  14. from app.schemas.instructions import ACATFCInstructions
  15. from app.schemas.space import Space
  16. from app.services.platform import DataPlatformService, InfoCode
  17. from app.services.transfer import SpaceInfoService, Duoduo, Season
  18. from app.utils.math import round_half_up
  19. class FCUController(EquipmentController):
  20. def __init__(self, equipment: FCU, season: Season):
  21. super(FCUController, self).__init__()
  22. self._equipment = equipment
  23. self.season = season
  24. async def get_temperature_target(self) -> float:
  25. target = self._equipment.space.temperature_target
  26. if not np.isnan(target):
  27. setting_target = target
  28. else:
  29. setting_target = 25.0
  30. self._equipment.setting_temperature = setting_target
  31. return setting_target
  32. async def get_mode(self) -> int:
  33. temperature_target = self._equipment.space.temperature_target
  34. if self.season == Season.heating:
  35. mode = 2
  36. if (
  37. temperature_target > 0
  38. and self._equipment.space.realtime_temperature > 0
  39. ):
  40. if temperature_target < self._equipment.space.realtime_temperature:
  41. mode = 1
  42. elif self.season == Season.cooling:
  43. mode = 1
  44. else:
  45. mode = 0
  46. self._equipment.work_mode = mode
  47. return mode
  48. async def get_air_valve_speed(self):
  49. temperature_target = self._equipment.space.temperature_target
  50. if temperature_target > 0:
  51. if self.season == Season.heating:
  52. diff = abs(
  53. self._equipment.space.realtime_temperature - temperature_target
  54. )
  55. if diff >= 1.5:
  56. self._equipment.air_valve_speed = AirValveSpeed.high
  57. elif diff >= 1.0:
  58. self._equipment.air_valve_speed = AirValveSpeed.medium
  59. elif diff >= 0.5:
  60. self._equipment.air_valve_speed = AirValveSpeed.low
  61. else:
  62. self._equipment.air_valve_speed = AirValveSpeed.off
  63. else:
  64. diff = self._equipment.space.realtime_temperature - temperature_target
  65. if diff >= 1.0:
  66. self._equipment.air_valve_speed = AirValveSpeed.high
  67. elif diff >= 0.5:
  68. self._equipment.air_valve_speed = AirValveSpeed.medium
  69. elif diff >= 0.0:
  70. self._equipment.air_valve_speed = AirValveSpeed.low
  71. else:
  72. self._equipment.air_valve_speed = AirValveSpeed.off
  73. else:
  74. self._equipment.air_valve_speed = AirValveSpeed.off
  75. async def get_running_status(self):
  76. if self._equipment.air_valve_speed == AirValveSpeed.off:
  77. self._equipment.running_status = False
  78. else:
  79. self._equipment.running_status = True
  80. async def run(self):
  81. # await self.get_temperature_target()
  82. await self.get_air_valve_speed()
  83. await self.get_running_status()
  84. await self.get_mode()
  85. def get_results(self):
  86. return self._equipment
  87. class FCUControllerV2(EquipmentController):
  88. """Writen by Wenyan"""
  89. def __init__(self, equipment: FCU, season: Season):
  90. super(FCUControllerV2, self).__init__()
  91. self.equipment = equipment
  92. self.season = season
  93. def get_mode(self) -> int:
  94. if self.equipment.water_in_temperature > 0:
  95. if (
  96. self.equipment.water_in_temperature
  97. > self.equipment.space.temperature_target
  98. ):
  99. mode = 2
  100. else:
  101. mode = 1
  102. elif self.equipment.supply_air_temperature > 0:
  103. if (
  104. self.equipment.supply_air_temperature
  105. > self.equipment.space.temperature_target
  106. ):
  107. mode = 2
  108. else:
  109. mode = 1
  110. else:
  111. if self.season == Season.heating:
  112. mode = 2
  113. elif self.season == Season.cooling:
  114. mode = 1
  115. else:
  116. mode = 0
  117. self.equipment.work_mode = mode
  118. return mode
  119. def build_air_valve_speed(self):
  120. target = self.equipment.space.temperature_target
  121. if target > 0:
  122. if self.equipment.space.realtime_temperature > 0:
  123. mode = self.get_mode()
  124. if mode == 2:
  125. diff = target - self.equipment.space.realtime_temperature
  126. elif mode == 1:
  127. diff = self.equipment.space.realtime_temperature - target
  128. else:
  129. diff = 0
  130. if diff <= 0:
  131. speed = AirValveSpeed.off
  132. elif diff < 0.5:
  133. speed = AirValveSpeed.low
  134. elif diff < 1.5:
  135. speed = AirValveSpeed.medium
  136. else:
  137. speed = AirValveSpeed.high
  138. self.equipment.air_valve_speed = speed
  139. else:
  140. self.equipment.air_valve_speed = AirValveSpeed.medium
  141. else:
  142. self.equipment.air_valve_speed = AirValveSpeed.off
  143. def build_running_status(self):
  144. if self.equipment.air_valve_speed == AirValveSpeed.off:
  145. self.equipment.running_status = False
  146. else:
  147. self.equipment.running_status = True
  148. def build_temperature_set(self):
  149. if np.isnan(self.equipment.space.temperature_target):
  150. self.equipment.setting_temperature = 0.0
  151. else:
  152. self.equipment.setting_temperature = self.equipment.space.temperature_target
  153. async def run(self):
  154. self.build_air_valve_speed()
  155. self.build_running_status()
  156. self.get_mode()
  157. self.build_temperature_set()
  158. def get_results(self):
  159. return self.equipment
  160. class SpeedController:
  161. def __init__(self, current_speed: Speed, running_status: bool) -> None:
  162. if running_status:
  163. self.current_speed = current_speed
  164. else:
  165. self.current_speed = Speed.off
  166. self.next_speed = current_speed
  167. def turn_up(self) -> Speed:
  168. if self.current_speed == Speed.off:
  169. self.next_speed = Speed.low
  170. elif self.current_speed == Speed.low:
  171. self.next_speed = Speed.medium
  172. elif self.current_speed == Speed.medium:
  173. self.next_speed = Speed.high
  174. return self.next_speed
  175. def turn_down(self) -> Speed:
  176. if self.current_speed == Speed.high:
  177. self.next_speed = Speed.medium
  178. elif self.current_speed == Speed.medium:
  179. self.next_speed = Speed.low
  180. elif self.current_speed == Speed.low:
  181. self.next_speed = Speed.off
  182. return self.next_speed
  183. def turn_on(self) -> Speed:
  184. self.next_speed = Speed.medium
  185. return self.next_speed
  186. def turn_off(self) -> Speed:
  187. self.next_speed = Speed.off
  188. return self.next_speed
  189. def get_next_speed(self) -> Speed:
  190. return self.next_speed
  191. class ATFC2Controller:
  192. """Two-wipe ATFC logic"""
  193. def __init__(
  194. self,
  195. space_target: float,
  196. space_realtime: float,
  197. season: Season,
  198. feedback: FeedbackValue,
  199. running_status: bool,
  200. speed: Speed,
  201. ):
  202. self.space_target = space_target
  203. self.space_realtime = space_realtime
  204. self.season = season
  205. self.feedback = feedback
  206. self.running_status = running_status
  207. self.speed = speed
  208. def build_speed_set(self) -> Speed:
  209. speed_controller = SpeedController(self.speed, self.running_status)
  210. if self.space_realtime > 0.0 and self.space_target > 0.0:
  211. if self.season == Season.cooling:
  212. diff = self.space_realtime - self.space_target
  213. elif self.season == Season.heating:
  214. diff = self.space_target - self.space_realtime
  215. else:
  216. diff = 0.0
  217. if diff <= -0.2:
  218. speed = Speed.off
  219. elif diff <= 0.2:
  220. speed = Speed.hold
  221. elif diff < 0.5:
  222. speed = Speed.low
  223. elif diff < 1.5:
  224. speed = Speed.medium
  225. else:
  226. speed = Speed.high
  227. else:
  228. speed = Speed.hold
  229. if self.space_target < 0.0:
  230. speed = Speed.off
  231. else:
  232. if self.feedback == FeedbackValue.switch_on:
  233. speed = Speed.medium
  234. elif (
  235. self.feedback == FeedbackValue.so_hot
  236. or self.feedback == FeedbackValue.a_little_hot
  237. ):
  238. if self.season == Season.cooling:
  239. speed = speed_controller.turn_up()
  240. elif self.season == Season.heating:
  241. speed = speed_controller.turn_down()
  242. elif (
  243. self.feedback == FeedbackValue.so_cold
  244. or self.feedback == FeedbackValue.a_little_cold
  245. ):
  246. if self.season == Season.cooling:
  247. speed = speed_controller.turn_down()
  248. elif self.season == Season.heating:
  249. speed = speed_controller.turn_up()
  250. return speed
  251. @staticmethod
  252. def build_switch_set(new_speed_set: Speed) -> str:
  253. if new_speed_set == Speed.off:
  254. switch_set = "off"
  255. elif new_speed_set == Speed.hold:
  256. switch_set = "hold"
  257. else:
  258. switch_set = "on"
  259. return switch_set
  260. def build_water_valve_set(self, new_switch_set: str) -> str:
  261. return new_switch_set
  262. def run(self) -> dict:
  263. speed = self.build_speed_set()
  264. switch_set = self.build_switch_set(speed)
  265. water_switch_set = self.build_water_valve_set(switch_set)
  266. res = {
  267. "speed_set": speed,
  268. "switch_set": switch_set,
  269. "water_valve_set": water_switch_set,
  270. }
  271. return res
  272. class ATFC4Controller(ATFC2Controller):
  273. """Four-wipe ATFC logic"""
  274. def __init__(
  275. self,
  276. space_target: float,
  277. space_realtime: float,
  278. season: Season,
  279. feedback: FeedbackValue,
  280. running_status: bool,
  281. speed: Speed,
  282. ):
  283. super().__init__(
  284. space_target, space_realtime, season, feedback, running_status, speed
  285. )
  286. def build_speed_set(self) -> Speed:
  287. speed = super().build_speed_set()
  288. if self.season == Season.heating:
  289. speed_controller = SpeedController(self.speed, self.running_status)
  290. if self.space_target > 0.0 and self.space_realtime > 0.0:
  291. diff = abs(self.space_realtime - self.space_target)
  292. if diff < 0.2:
  293. speed = Speed.hold
  294. elif diff < 0.5:
  295. speed = Speed.low
  296. elif diff < 1.5:
  297. speed = Speed.medium
  298. else:
  299. speed = Speed.high
  300. else:
  301. if self.space_target < 0.0:
  302. speed = Speed.off
  303. else:
  304. if self.feedback == FeedbackValue.switch_on:
  305. speed = Speed.medium
  306. elif (
  307. self.feedback == FeedbackValue.so_hot
  308. or self.feedback == FeedbackValue.a_little_hot
  309. ):
  310. speed = speed_controller.turn_up()
  311. elif (
  312. self.feedback == FeedbackValue.so_cold
  313. or self.feedback == FeedbackValue.a_little_cold
  314. ):
  315. speed = speed_controller.turn_up()
  316. return speed
  317. def build_water_valve_set(self, new_switch_set: str) -> Tuple[str, str]:
  318. if new_switch_set == "off":
  319. chill_water_valve_set = "off"
  320. hot_water_valve_set = "off"
  321. else:
  322. if self.season == Season.heating:
  323. if self.space_realtime > 0.0:
  324. if self.space_realtime - self.space_target >= 0.2:
  325. chill_water_valve_set = "on"
  326. hot_water_valve_set = "off"
  327. elif self.space_target - self.space_realtime >= 0.2:
  328. chill_water_valve_set = "off"
  329. hot_water_valve_set = "on"
  330. else:
  331. chill_water_valve_set = "hold"
  332. hot_water_valve_set = "hold"
  333. else:
  334. chill_water_valve_set = "hold"
  335. hot_water_valve_set = "hold"
  336. if self.feedback == FeedbackValue.switch_on:
  337. chill_water_valve_set = "off"
  338. hot_water_valve_set = "on"
  339. elif (
  340. self.feedback == FeedbackValue.so_hot
  341. or self.feedback == FeedbackValue.a_little_hot
  342. ):
  343. chill_water_valve_set = "on"
  344. hot_water_valve_set = "off"
  345. elif (
  346. self.feedback == FeedbackValue.so_cold
  347. or self.feedback == FeedbackValue.a_little_cold
  348. ):
  349. chill_water_valve_set = "off"
  350. hot_water_valve_set = "on"
  351. else:
  352. chill_water_valve_set = "on"
  353. hot_water_valve_set = "off"
  354. return chill_water_valve_set, hot_water_valve_set
  355. def run(self) -> dict:
  356. speed = self.build_speed_set()
  357. switch_set = self.build_switch_set(speed)
  358. chill_water_valve_set, hot_water_valve_set = self.build_water_valve_set(
  359. switch_set
  360. )
  361. res = {
  362. "speed_set": speed,
  363. "switch_set": switch_set,
  364. "chill_water_valve_set": chill_water_valve_set,
  365. "hot_water_valve_set": hot_water_valve_set,
  366. }
  367. return res
  368. @logger.catch()
  369. async def get_fcu_control_result(project_id: str, equipment_id: str) -> Dict:
  370. async with AsyncClient() as client:
  371. duo_duo = Duoduo(client, project_id)
  372. platform = DataPlatformService(client, project_id)
  373. spaces = await duo_duo.get_space_by_equipment(equipment_id)
  374. if project_id == "Pj1101150003":
  375. supply_air_temperature = await platform.get_realtime_data(
  376. InfoCode.supply_temperature, equipment_id
  377. )
  378. water_in_temperature = await platform.get_realtime_data(
  379. InfoCode.water_in_temperature, equipment_id
  380. )
  381. else:
  382. supply_air_temperature = np.NAN
  383. water_in_temperature = np.NAN
  384. for sp in spaces:
  385. transfer = SpaceInfoService(client, project_id, sp.get("id"))
  386. current_target = await transfer.get_current_temperature_target()
  387. realtime_temperature = await platform.get_realtime_temperature(sp.get("id"))
  388. season = await duo_duo.get_season()
  389. temp_space_params = {
  390. "id": sp.get("id"),
  391. "temperature_target": current_target,
  392. "realtime_temperature": realtime_temperature,
  393. }
  394. space = Space(**temp_space_params)
  395. break
  396. fcu_temp_params = {
  397. "id": equipment_id,
  398. "space": space,
  399. "supply_air_temperature": supply_air_temperature,
  400. "water_in_temperature": water_in_temperature,
  401. }
  402. fcu = FCU(**fcu_temp_params)
  403. if project_id == "Pj1101050030":
  404. fcu_controller = FCUController(fcu, season)
  405. else:
  406. fcu_controller = FCUControllerV2(fcu, season)
  407. await fcu_controller.run()
  408. regulated_fcu = fcu_controller.get_results()
  409. output = {
  410. "onOff": 1 if regulated_fcu.running_status else 0,
  411. "mode": regulated_fcu.work_mode,
  412. "speed": regulated_fcu.air_valve_speed.value,
  413. "temperature": float(round_half_up(regulated_fcu.setting_temperature, 1)),
  414. "water": 1 if regulated_fcu.running_status else 0,
  415. }
  416. return output
  417. @logger.catch()
  418. async def build_acatfc2_instructions(
  419. params: ACATFC2InstructionsRequest,
  420. ) -> ACATFCInstructions:
  421. space = Space(
  422. temperature_target=params.space_temperature_target,
  423. realtime_temperature=params.space_realtime_temperature,
  424. )
  425. fcu = FCU(space=space, supply_air_temperature=-1.0, water_in_temperature=-1.0)
  426. controller = FCUControllerV2(fcu, Season(params.season))
  427. await controller.run()
  428. regulated_fcu = controller.get_results()
  429. instructions = ACATFCInstructions(
  430. switch_set=1 if regulated_fcu.running_status else 0,
  431. speed_set=regulated_fcu.air_valve_speed.value,
  432. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  433. mode_set=regulated_fcu.work_mode,
  434. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  435. )
  436. return instructions
  437. @logger.catch()
  438. async def build_acatfc4_instructions(
  439. params: ACATFC4InstructionsRequest,
  440. ) -> ACATFCInstructions:
  441. space = Space(
  442. temperature_target=params.space_temperature_target,
  443. realtime_temperature=params.space_realtime_temperature,
  444. )
  445. fcu = FCU(space=space)
  446. controller = FCUController(fcu, Season(params.season))
  447. await controller.run()
  448. regulated_fcu = controller.get_results()
  449. instructions = ACATFCInstructions(
  450. switch_set=1 if regulated_fcu.running_status else 0,
  451. speed_set=regulated_fcu.air_valve_speed.value,
  452. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  453. mode_set=regulated_fcu.work_mode,
  454. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  455. )
  456. return instructions
  457. @logger.catch()
  458. async def build_acatfc2_instructions_v2(params: ACATFC2InstructionsRequest) -> dict:
  459. space_realtime = (
  460. params.space_realtime_temperature if params.space_realtime_temperature else -1
  461. )
  462. controller = ATFC2Controller(
  463. params.space_temperature_target,
  464. space_realtime,
  465. Season(params.season),
  466. params.feedback,
  467. params.running_status,
  468. params.speed,
  469. )
  470. instructions = controller.run()
  471. return instructions
  472. @logger.catch()
  473. async def build_acatfc4_instructions_v2(params: ACATFC4InstructionsRequest) -> dict:
  474. space_realtime = (
  475. params.space_realtime_temperature if params.space_realtime_temperature else -1
  476. )
  477. controller = ATFC4Controller(
  478. params.space_temperature_target,
  479. space_realtime,
  480. Season(params.season),
  481. params.feedback,
  482. params.running_status,
  483. params.speed,
  484. )
  485. instructions = controller.run()
  486. return instructions