basic.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. # -*- coding: utf-8 -*-
  2. from typing import Tuple
  3. from app.api.errors.iot import MissingIOTDataError
  4. from app.controllers.equipment.controller import EquipmentController
  5. from app.models.domain.devices import ACATFC2InstructionsRequest, ACATFC4InstructionsRequest, Speed
  6. from app.models.domain.feedback import FeedbackValue
  7. from app.schemas.equipment import AirValveSpeed, FCU
  8. from app.schemas.instructions import ACATFCInstructions
  9. from app.schemas.space import Space
  10. from app.services.transfer import Season
  11. from app.utils.math import round_half_up
  12. class FCUController(EquipmentController):
  13. def __init__(self, equipment: FCU, season: Season):
  14. super(FCUController, self).__init__()
  15. self._equipment = equipment
  16. self.season = season
  17. async def get_temperature_target(self) -> float:
  18. target = self._equipment.space.temperature_target
  19. if target:
  20. setting_target = target
  21. else:
  22. setting_target = 25.0
  23. self._equipment.setting_temperature = setting_target
  24. return setting_target
  25. async def get_mode(self) -> int:
  26. temperature_target = self._equipment.space.temperature_target
  27. if self.season == Season.heating:
  28. mode = 2
  29. if temperature_target and self._equipment.space.realtime_temperature:
  30. if temperature_target < self._equipment.space.realtime_temperature:
  31. mode = 1
  32. elif self.season == Season.cooling:
  33. mode = 1
  34. else:
  35. mode = 0
  36. self._equipment.work_mode = mode
  37. return mode
  38. async def get_air_valve_speed(self):
  39. temperature_target = self._equipment.space.temperature_target
  40. if temperature_target:
  41. if self.season == Season.heating:
  42. diff = abs(self._equipment.space.realtime_temperature - temperature_target)
  43. if diff >= 1.5:
  44. self._equipment.air_valve_speed = AirValveSpeed.high
  45. elif diff >= 1.0:
  46. self._equipment.air_valve_speed = AirValveSpeed.medium
  47. elif diff >= 0.5:
  48. self._equipment.air_valve_speed = AirValveSpeed.low
  49. else:
  50. self._equipment.air_valve_speed = AirValveSpeed.off
  51. else:
  52. diff = self._equipment.space.realtime_temperature - temperature_target
  53. if diff >= 1.0:
  54. self._equipment.air_valve_speed = AirValveSpeed.high
  55. elif diff >= 0.5:
  56. self._equipment.air_valve_speed = AirValveSpeed.medium
  57. elif diff >= 0.0:
  58. self._equipment.air_valve_speed = AirValveSpeed.low
  59. else:
  60. self._equipment.air_valve_speed = AirValveSpeed.off
  61. else:
  62. self._equipment.air_valve_speed = AirValveSpeed.off
  63. async def get_running_status(self):
  64. if self._equipment.air_valve_speed == AirValveSpeed.off:
  65. self._equipment.running_status = False
  66. else:
  67. self._equipment.running_status = True
  68. async def run(self):
  69. try:
  70. await self.get_air_valve_speed()
  71. await self.get_running_status()
  72. await self.get_mode()
  73. await self.get_temperature_target()
  74. except TypeError:
  75. raise MissingIOTDataError
  76. def get_results(self):
  77. return self._equipment
  78. class FCUControllerV2(EquipmentController):
  79. """Writen by Wenyan"""
  80. def __init__(self, equipment: FCU, season: Season):
  81. super(FCUControllerV2, self).__init__()
  82. self.equipment = equipment
  83. self.season = season
  84. def get_mode(self) -> int:
  85. if self.equipment.water_in_temperature and self.equipment.space.temperature_target:
  86. if self.equipment.water_in_temperature > self.equipment.space.temperature_target:
  87. mode = 2
  88. else:
  89. mode = 1
  90. elif self.equipment.supply_air_temperature and self.equipment.space.temperature_target:
  91. if self.equipment.supply_air_temperature > self.equipment.space.temperature_target:
  92. mode = 2
  93. else:
  94. mode = 1
  95. else:
  96. if self.season == Season.heating:
  97. mode = 2
  98. elif self.season == Season.cooling:
  99. mode = 1
  100. else:
  101. mode = 0
  102. self.equipment.work_mode = mode
  103. return mode
  104. def build_air_valve_speed(self):
  105. target = self.equipment.space.temperature_target
  106. if target:
  107. if self.equipment.space.realtime_temperature:
  108. mode = self.get_mode()
  109. if mode == 2:
  110. diff = target - self.equipment.space.realtime_temperature
  111. elif mode == 1:
  112. diff = self.equipment.space.realtime_temperature - target
  113. else:
  114. diff = 0
  115. if diff <= 0:
  116. speed = AirValveSpeed.off
  117. elif diff < 0.5:
  118. speed = AirValveSpeed.low
  119. elif diff < 1.5:
  120. speed = AirValveSpeed.medium
  121. else:
  122. speed = AirValveSpeed.high
  123. self.equipment.air_valve_speed = speed
  124. else:
  125. self.equipment.air_valve_speed = AirValveSpeed.medium
  126. else:
  127. self.equipment.air_valve_speed = AirValveSpeed.off
  128. def build_running_status(self):
  129. if self.equipment.air_valve_speed == AirValveSpeed.off:
  130. self.equipment.running_status = False
  131. else:
  132. self.equipment.running_status = True
  133. def build_temperature_set(self):
  134. if self.equipment.space.temperature_target:
  135. self.equipment.setting_temperature = self.equipment.space.temperature_target
  136. else:
  137. self.equipment.setting_temperature = 0.0
  138. async def run(self):
  139. try:
  140. self.build_air_valve_speed()
  141. self.build_running_status()
  142. self.get_mode()
  143. self.build_temperature_set()
  144. except TypeError:
  145. raise MissingIOTDataError
  146. def get_results(self):
  147. return self.equipment
  148. class SpeedController:
  149. def __init__(self, current_speed: Speed, running_status: bool) -> None:
  150. if running_status:
  151. self.current_speed = current_speed
  152. else:
  153. self.current_speed = Speed.off
  154. self.next_speed = current_speed
  155. def turn_up(self) -> Speed:
  156. if self.current_speed == Speed.off:
  157. self.next_speed = Speed.low
  158. elif self.current_speed == Speed.low:
  159. self.next_speed = Speed.medium
  160. elif self.current_speed == Speed.medium:
  161. self.next_speed = Speed.high
  162. return self.next_speed
  163. def turn_down(self) -> Speed:
  164. if self.current_speed == Speed.high:
  165. self.next_speed = Speed.medium
  166. elif self.current_speed == Speed.medium:
  167. self.next_speed = Speed.low
  168. elif self.current_speed == Speed.low:
  169. self.next_speed = Speed.off
  170. return self.next_speed
  171. def turn_on(self) -> Speed:
  172. self.next_speed = Speed.medium
  173. return self.next_speed
  174. def turn_off(self) -> Speed:
  175. self.next_speed = Speed.off
  176. return self.next_speed
  177. def get_next_speed(self) -> Speed:
  178. return self.next_speed
  179. class ATFC2Controller:
  180. """Two-wipe ATFC logic"""
  181. def __init__(
  182. self,
  183. space_target: float,
  184. space_realtime: float,
  185. season: Season,
  186. feedback: FeedbackValue,
  187. running_status: bool,
  188. speed: Speed,
  189. ):
  190. self.space_target = space_target
  191. self.space_realtime = space_realtime
  192. self.season = season
  193. self.feedback = feedback
  194. self.running_status = running_status
  195. self.speed = speed
  196. def build_speed_set(self) -> Speed:
  197. speed_controller = SpeedController(self.speed, self.running_status)
  198. if self.space_realtime and self.space_target:
  199. if self.season == Season.cooling:
  200. diff = self.space_realtime - self.space_target
  201. elif self.season == Season.heating:
  202. diff = self.space_target - self.space_realtime
  203. else:
  204. diff = 0.0
  205. if diff <= -0.2:
  206. speed = Speed.off
  207. elif diff <= 0.2:
  208. speed = Speed.hold
  209. elif diff < 0.5:
  210. speed = Speed.low
  211. elif diff < 1.5:
  212. speed = Speed.medium
  213. else:
  214. speed = Speed.high
  215. else:
  216. speed = Speed.hold
  217. if not self.space_target:
  218. speed = Speed.off
  219. else:
  220. if self.feedback == FeedbackValue.switch_on:
  221. speed = Speed.medium
  222. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  223. if self.season == Season.cooling:
  224. speed = speed_controller.turn_up()
  225. elif self.season == Season.heating:
  226. speed = speed_controller.turn_down()
  227. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  228. if self.season == Season.cooling:
  229. speed = speed_controller.turn_down()
  230. elif self.season == Season.heating:
  231. speed = speed_controller.turn_up()
  232. return speed
  233. @staticmethod
  234. def build_switch_set(new_speed_set: Speed) -> str:
  235. if new_speed_set == Speed.off:
  236. switch_set = "off"
  237. elif new_speed_set == Speed.hold:
  238. switch_set = "hold"
  239. else:
  240. switch_set = "on"
  241. return switch_set
  242. def build_water_valve_set(self, new_switch_set: str) -> str:
  243. return new_switch_set
  244. def run(self) -> dict:
  245. try:
  246. speed = self.build_speed_set()
  247. switch_set = self.build_switch_set(speed)
  248. water_switch_set = self.build_water_valve_set(switch_set)
  249. except TypeError:
  250. raise MissingIOTDataError
  251. res = {
  252. "speed_set": speed,
  253. "switch_set": switch_set,
  254. "water_valve_set": water_switch_set,
  255. }
  256. return res
  257. class ATFC4Controller(ATFC2Controller):
  258. """Four-wipe ATFC logic"""
  259. def __init__(
  260. self,
  261. space_target: float,
  262. space_realtime: float,
  263. season: Season,
  264. feedback: FeedbackValue,
  265. running_status: bool,
  266. speed: Speed,
  267. ):
  268. super().__init__(
  269. space_target, space_realtime, season, feedback, running_status, speed
  270. )
  271. def build_speed_set(self) -> Speed:
  272. speed = super().build_speed_set()
  273. if self.season == Season.heating:
  274. speed_controller = SpeedController(self.speed, self.running_status)
  275. if self.space_target and self.space_realtime:
  276. diff = abs(self.space_realtime - self.space_target)
  277. if diff < 0.2:
  278. speed = Speed.hold
  279. elif diff < 0.5:
  280. speed = Speed.low
  281. elif diff < 1.5:
  282. speed = Speed.medium
  283. else:
  284. speed = Speed.high
  285. else:
  286. if not self.space_target:
  287. speed = Speed.off
  288. else:
  289. if self.feedback == FeedbackValue.switch_on:
  290. speed = Speed.medium
  291. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  292. speed = speed_controller.turn_up()
  293. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  294. speed = speed_controller.turn_up()
  295. return speed
  296. def build_water_valve_set(self, new_switch_set: str) -> Tuple[str, str]:
  297. if new_switch_set == "off":
  298. chill_water_valve_set = "off"
  299. hot_water_valve_set = "off"
  300. else:
  301. if self.season == Season.heating:
  302. if self.space_realtime and self.space_target:
  303. if self.space_realtime - self.space_target >= 0.2:
  304. chill_water_valve_set = "on"
  305. hot_water_valve_set = "off"
  306. elif self.space_target - self.space_realtime >= 0.2:
  307. chill_water_valve_set = "off"
  308. hot_water_valve_set = "on"
  309. else:
  310. chill_water_valve_set = "hold"
  311. hot_water_valve_set = "hold"
  312. else:
  313. chill_water_valve_set = "hold"
  314. hot_water_valve_set = "hold"
  315. if self.feedback == FeedbackValue.switch_on:
  316. chill_water_valve_set = "off"
  317. hot_water_valve_set = "on"
  318. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  319. chill_water_valve_set = "on"
  320. hot_water_valve_set = "off"
  321. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  322. chill_water_valve_set = "off"
  323. hot_water_valve_set = "on"
  324. else:
  325. chill_water_valve_set = "on"
  326. hot_water_valve_set = "off"
  327. return chill_water_valve_set, hot_water_valve_set
  328. def run(self) -> dict:
  329. try:
  330. speed = self.build_speed_set()
  331. switch_set = self.build_switch_set(speed)
  332. chill_water_valve_set, hot_water_valve_set = self.build_water_valve_set(switch_set)
  333. except TypeError:
  334. raise MissingIOTDataError
  335. res = {
  336. "speed_set": speed,
  337. "switch_set": switch_set,
  338. "chill_water_valve_set": chill_water_valve_set,
  339. "hot_water_valve_set": hot_water_valve_set,
  340. }
  341. return res
  342. async def build_acatfc2_instructions(params: ACATFC2InstructionsRequest) -> ACATFCInstructions:
  343. space = Space(
  344. temperature_target=params.space_temperature_target,
  345. realtime_temperature=params.space_realtime_temperature,
  346. )
  347. fcu = FCU(space=space, supply_air_temperature=-1.0, water_in_temperature=-1.0)
  348. controller = FCUControllerV2(fcu, Season(params.season))
  349. await controller.run()
  350. regulated_fcu = controller.get_results()
  351. instructions = ACATFCInstructions(
  352. switch_set=1 if regulated_fcu.running_status else 0,
  353. speed_set=regulated_fcu.air_valve_speed.value,
  354. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  355. mode_set=regulated_fcu.work_mode,
  356. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  357. )
  358. return instructions
  359. async def build_acatfc4_instructions(params: ACATFC4InstructionsRequest) -> ACATFCInstructions:
  360. space = Space(
  361. temperature_target=params.space_temperature_target,
  362. realtime_temperature=params.space_realtime_temperature,
  363. )
  364. fcu = FCU(space=space)
  365. controller = FCUController(fcu, Season(params.season))
  366. await controller.run()
  367. regulated_fcu = controller.get_results()
  368. instructions = ACATFCInstructions(
  369. switch_set=1 if regulated_fcu.running_status else 0,
  370. speed_set=regulated_fcu.air_valve_speed.value,
  371. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  372. mode_set=regulated_fcu.work_mode,
  373. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  374. )
  375. return instructions
  376. async def build_acatfc2_instructions_v2(params: ACATFC2InstructionsRequest) -> dict:
  377. controller = ATFC2Controller(
  378. params.space_temperature_target,
  379. params.space_realtime_temperature,
  380. Season(params.season),
  381. params.feedback,
  382. params.running_status,
  383. params.speed,
  384. )
  385. instructions = controller.run()
  386. return instructions
  387. async def build_acatfc4_instructions_v2(params: ACATFC4InstructionsRequest) -> dict:
  388. controller = ATFC4Controller(
  389. params.space_temperature_target,
  390. params.space_realtime_temperature,
  391. Season(params.season),
  392. params.feedback,
  393. params.running_status,
  394. params.speed,
  395. )
  396. instructions = controller.run()
  397. return instructions