basic.py 17 KB

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