basic.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. # -*- coding: utf-8 -*-
  2. from app.api.errors.iot import MissingIOTDataError
  3. from app.controllers.equipment.controller import EquipmentController
  4. from app.models.domain.devices import ACATFC2InstructionsRequest, ACATFC4InstructionsRequest, Speed
  5. from app.models.domain.feedback import FeedbackValue
  6. from app.schemas.equipment import AirValveSpeed, FCU
  7. from app.schemas.instructions import ACATFCInstructions
  8. from app.schemas.space import Space
  9. from app.services.transfer import Season
  10. from app.utils.math import round_half_up
  11. from loguru import logger
  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. logger.debug(mode)
  110. if mode == 2:
  111. diff = target - self.equipment.space.realtime_temperature
  112. elif mode == 1:
  113. diff = self.equipment.space.realtime_temperature - target
  114. else:
  115. diff = 0
  116. logger.debug(diff)
  117. if diff <= 0:
  118. speed = AirValveSpeed.off
  119. elif diff < 0.5:
  120. speed = AirValveSpeed.low
  121. elif diff < 1.5:
  122. speed = AirValveSpeed.medium
  123. else:
  124. speed = AirValveSpeed.high
  125. self.equipment.air_valve_speed = speed
  126. else:
  127. self.equipment.air_valve_speed = AirValveSpeed.medium
  128. else:
  129. self.equipment.air_valve_speed = AirValveSpeed.off
  130. def build_running_status(self):
  131. if self.equipment.air_valve_speed == AirValveSpeed.off:
  132. self.equipment.running_status = False
  133. else:
  134. self.equipment.running_status = True
  135. def build_temperature_set(self):
  136. if self.equipment.space.temperature_target:
  137. self.equipment.setting_temperature = self.equipment.space.temperature_target
  138. else:
  139. self.equipment.setting_temperature = 0.0
  140. async def run(self):
  141. try:
  142. self.build_air_valve_speed()
  143. self.build_running_status()
  144. self.get_mode()
  145. self.build_temperature_set()
  146. except TypeError:
  147. raise MissingIOTDataError
  148. def get_results(self):
  149. return self.equipment
  150. class SpeedController:
  151. def __init__(self, current_speed: Speed, running_status: bool) -> None:
  152. if running_status:
  153. self.current_speed = current_speed
  154. else:
  155. self.current_speed = Speed.off
  156. self.next_speed = current_speed
  157. def turn_up(self) -> Speed:
  158. if self.current_speed == Speed.off:
  159. self.next_speed = Speed.low
  160. elif self.current_speed == Speed.low:
  161. self.next_speed = Speed.medium
  162. elif self.current_speed == Speed.medium:
  163. self.next_speed = Speed.high
  164. return self.next_speed
  165. def turn_down(self) -> Speed:
  166. if self.current_speed == Speed.high:
  167. self.next_speed = Speed.medium
  168. elif self.current_speed == Speed.medium:
  169. self.next_speed = Speed.low
  170. elif self.current_speed == Speed.low:
  171. self.next_speed = Speed.off
  172. return self.next_speed
  173. def turn_on(self) -> Speed:
  174. self.next_speed = Speed.medium
  175. return self.next_speed
  176. def turn_off(self) -> Speed:
  177. self.next_speed = Speed.off
  178. return self.next_speed
  179. def get_next_speed(self) -> Speed:
  180. return self.next_speed
  181. class ATFC2Controller:
  182. """Two-wipe ATFC logic"""
  183. def __init__(
  184. self,
  185. space_target: float,
  186. space_realtime: float,
  187. season: Season,
  188. feedback: FeedbackValue,
  189. running_status: bool,
  190. speed: Speed,
  191. ):
  192. self.space_target = space_target
  193. self.space_realtime = space_realtime
  194. self.season = season
  195. self.feedback = feedback
  196. self.running_status = running_status
  197. self.speed = speed
  198. def build_speed_set(self) -> Speed:
  199. speed_controller = SpeedController(self.speed, self.running_status)
  200. if self.feedback != FeedbackValue.null:
  201. speed = Speed.hold
  202. if not self.space_target:
  203. speed = Speed.off
  204. else:
  205. if self.feedback == FeedbackValue.switch_on:
  206. speed = Speed.medium
  207. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  208. if self.season == Season.cooling:
  209. speed = speed_controller.turn_up()
  210. elif self.season == Season.heating:
  211. speed = speed_controller.turn_down()
  212. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  213. if self.season == Season.cooling:
  214. speed = speed_controller.turn_down()
  215. elif self.season == Season.heating:
  216. speed = speed_controller.turn_up()
  217. else:
  218. if not self.space_target:
  219. speed = Speed.off
  220. else:
  221. if self.space_realtime:
  222. if self.season == Season.cooling:
  223. diff = self.space_realtime - self.space_target
  224. elif self.season == Season.heating:
  225. diff = self.space_target - self.space_realtime
  226. else:
  227. diff = -0.3
  228. if diff <= -0.2:
  229. speed = Speed.off
  230. elif diff <= 0.2:
  231. speed = Speed.hold
  232. elif diff < 0.5:
  233. speed = Speed.low
  234. elif diff < 1.5:
  235. speed = Speed.medium
  236. else:
  237. speed = Speed.high
  238. else:
  239. speed = Speed.hold
  240. return speed
  241. @staticmethod
  242. def build_switch_set(new_speed_set: Speed) -> str:
  243. if new_speed_set == Speed.off:
  244. switch_set = "off"
  245. elif new_speed_set == Speed.hold:
  246. switch_set = "hold"
  247. else:
  248. switch_set = "on"
  249. return switch_set
  250. def build_water_valve_set(self, new_switch_set: str) -> str:
  251. return new_switch_set
  252. def run(self) -> dict:
  253. try:
  254. speed = self.build_speed_set()
  255. switch_set = self.build_switch_set(speed)
  256. water_switch_set = self.build_water_valve_set(switch_set)
  257. except TypeError:
  258. raise MissingIOTDataError
  259. res = {
  260. "speed_set": speed,
  261. "switch_set": switch_set,
  262. "water_valve_set": water_switch_set,
  263. }
  264. return res
  265. class ATFC4Controller(ATFC2Controller):
  266. """Four-wipe ATFC logic"""
  267. def __init__(
  268. self,
  269. space_target: float,
  270. space_realtime: float,
  271. season: Season,
  272. feedback: FeedbackValue,
  273. running_status: bool,
  274. speed: Speed,
  275. ):
  276. super().__init__(
  277. space_target, space_realtime, season, feedback, running_status, speed
  278. )
  279. def build_speed_set(self) -> Speed:
  280. speed = super().build_speed_set()
  281. if self.season == Season.heating:
  282. speed_controller = SpeedController(self.speed, self.running_status)
  283. if self.feedback != FeedbackValue.null:
  284. if not self.space_target:
  285. speed = Speed.off
  286. else:
  287. if self.feedback == FeedbackValue.switch_on:
  288. speed = Speed.medium
  289. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  290. speed = speed_controller.turn_up()
  291. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  292. speed = speed_controller.turn_up()
  293. else:
  294. speed = Speed.hold
  295. else:
  296. if not self.space_target:
  297. speed = Speed.off
  298. else:
  299. if self.space_realtime:
  300. diff = abs(self.space_realtime - self.space_target)
  301. if diff < 0.2:
  302. speed = Speed.hold
  303. elif diff < 0.5:
  304. speed = Speed.low
  305. elif diff < 1.5:
  306. speed = Speed.medium
  307. else:
  308. speed = Speed.high
  309. else:
  310. speed = Speed.hold
  311. return speed
  312. def build_water_valve_set(self, new_switch_set: str) -> tuple[str, str]:
  313. if new_switch_set == "off":
  314. chill_water_valve_set = "off"
  315. hot_water_valve_set = "off"
  316. else:
  317. if self.season == Season.heating:
  318. if self.space_realtime and self.space_target:
  319. if self.space_realtime - self.space_target >= 0.2:
  320. chill_water_valve_set = "on"
  321. hot_water_valve_set = "off"
  322. elif self.space_target - self.space_realtime >= 0.2:
  323. chill_water_valve_set = "off"
  324. hot_water_valve_set = "on"
  325. else:
  326. chill_water_valve_set = "hold"
  327. hot_water_valve_set = "hold"
  328. else:
  329. chill_water_valve_set = "hold"
  330. hot_water_valve_set = "hold"
  331. if self.feedback == FeedbackValue.switch_on:
  332. chill_water_valve_set = "off"
  333. hot_water_valve_set = "on"
  334. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  335. chill_water_valve_set = "on"
  336. hot_water_valve_set = "off"
  337. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  338. chill_water_valve_set = "off"
  339. hot_water_valve_set = "on"
  340. else:
  341. chill_water_valve_set = "on"
  342. hot_water_valve_set = "off"
  343. return chill_water_valve_set, hot_water_valve_set
  344. def run(self) -> dict:
  345. try:
  346. speed = self.build_speed_set()
  347. switch_set = self.build_switch_set(speed)
  348. chill_water_valve_set, hot_water_valve_set = self.build_water_valve_set(switch_set)
  349. except TypeError:
  350. raise MissingIOTDataError
  351. res = {
  352. "speed_set": speed,
  353. "switch_set": switch_set,
  354. "chill_water_valve_set": chill_water_valve_set,
  355. "hot_water_valve_set": hot_water_valve_set,
  356. }
  357. return res
  358. async def build_acatfc2_instructions(params: ACATFC2InstructionsRequest) -> ACATFCInstructions:
  359. space = Space(
  360. temperature_target=params.space_temperature_target,
  361. realtime_temperature=params.space_realtime_temperature,
  362. )
  363. fcu = FCU(space=space)
  364. controller = FCUControllerV2(fcu, Season(params.season))
  365. await controller.run()
  366. regulated_fcu = controller.get_results()
  367. instructions = ACATFCInstructions(
  368. switch_set=1 if regulated_fcu.running_status else 0,
  369. speed_set=regulated_fcu.air_valve_speed.value,
  370. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  371. mode_set=regulated_fcu.work_mode,
  372. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  373. )
  374. return instructions
  375. async def build_acatfc4_instructions(params: ACATFC4InstructionsRequest) -> ACATFCInstructions:
  376. space = Space(
  377. temperature_target=params.space_temperature_target,
  378. realtime_temperature=params.space_realtime_temperature,
  379. )
  380. fcu = FCU(space=space)
  381. controller = FCUController(fcu, Season(params.season))
  382. await controller.run()
  383. regulated_fcu = controller.get_results()
  384. instructions = ACATFCInstructions(
  385. switch_set=1 if regulated_fcu.running_status else 0,
  386. speed_set=regulated_fcu.air_valve_speed.value,
  387. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  388. mode_set=regulated_fcu.work_mode,
  389. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  390. )
  391. return instructions
  392. async def build_acatfc2_instructions_v2(params: ACATFC2InstructionsRequest) -> dict:
  393. controller = ATFC2Controller(
  394. params.space_temperature_target,
  395. params.space_realtime_temperature,
  396. Season(params.season),
  397. params.feedback,
  398. params.running_status,
  399. params.speed,
  400. )
  401. instructions = controller.run()
  402. return instructions
  403. async def build_acatfc4_instructions_v2(params: ACATFC4InstructionsRequest) -> dict:
  404. controller = ATFC4Controller(
  405. params.space_temperature_target,
  406. params.space_realtime_temperature,
  407. Season(params.season),
  408. params.feedback,
  409. params.running_status,
  410. params.speed,
  411. )
  412. instructions = controller.run()
  413. return instructions