basic.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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.space_realtime and self.space_target:
  198. if self.season == Season.cooling:
  199. diff = self.space_realtime - self.space_target
  200. elif self.season == Season.heating:
  201. diff = self.space_target - self.space_realtime
  202. else:
  203. diff = 0.0
  204. if diff <= -0.2:
  205. speed = Speed.off
  206. elif diff <= 0.2:
  207. speed = Speed.hold
  208. elif diff < 0.5:
  209. speed = Speed.low
  210. elif diff < 1.5:
  211. speed = Speed.medium
  212. else:
  213. speed = Speed.high
  214. else:
  215. speed = Speed.hold
  216. if not self.space_target:
  217. speed = Speed.off
  218. else:
  219. if self.feedback == FeedbackValue.switch_on:
  220. speed = Speed.medium
  221. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  222. if self.season == Season.cooling:
  223. speed = speed_controller.turn_up()
  224. elif self.season == Season.heating:
  225. speed = speed_controller.turn_down()
  226. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  227. if self.season == Season.cooling:
  228. speed = speed_controller.turn_down()
  229. elif self.season == Season.heating:
  230. speed = speed_controller.turn_up()
  231. return speed
  232. @staticmethod
  233. def build_switch_set(new_speed_set: Speed) -> str:
  234. if new_speed_set == Speed.off:
  235. switch_set = "off"
  236. elif new_speed_set == Speed.hold:
  237. switch_set = "hold"
  238. else:
  239. switch_set = "on"
  240. return switch_set
  241. def build_water_valve_set(self, new_switch_set: str) -> str:
  242. return new_switch_set
  243. def run(self) -> dict:
  244. try:
  245. speed = self.build_speed_set()
  246. switch_set = self.build_switch_set(speed)
  247. water_switch_set = self.build_water_valve_set(switch_set)
  248. except TypeError:
  249. raise MissingIOTDataError
  250. res = {
  251. "speed_set": speed,
  252. "switch_set": switch_set,
  253. "water_valve_set": water_switch_set,
  254. }
  255. return res
  256. class ATFC4Controller(ATFC2Controller):
  257. """Four-wipe ATFC logic"""
  258. def __init__(
  259. self,
  260. space_target: float,
  261. space_realtime: float,
  262. season: Season,
  263. feedback: FeedbackValue,
  264. running_status: bool,
  265. speed: Speed,
  266. ):
  267. super().__init__(
  268. space_target, space_realtime, season, feedback, running_status, speed
  269. )
  270. def build_speed_set(self) -> Speed:
  271. speed = super().build_speed_set()
  272. if self.season == Season.heating:
  273. speed_controller = SpeedController(self.speed, self.running_status)
  274. if self.space_target and self.space_realtime:
  275. diff = abs(self.space_realtime - self.space_target)
  276. if diff < 0.2:
  277. speed = Speed.hold
  278. elif diff < 0.5:
  279. speed = Speed.low
  280. elif diff < 1.5:
  281. speed = Speed.medium
  282. else:
  283. speed = Speed.high
  284. else:
  285. if not self.space_target:
  286. speed = Speed.off
  287. else:
  288. if self.feedback == FeedbackValue.switch_on:
  289. speed = Speed.medium
  290. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  291. speed = speed_controller.turn_up()
  292. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  293. speed = speed_controller.turn_up()
  294. return speed
  295. def build_water_valve_set(self, new_switch_set: str) -> tuple[str, str]:
  296. if new_switch_set == "off":
  297. chill_water_valve_set = "off"
  298. hot_water_valve_set = "off"
  299. else:
  300. if self.season == Season.heating:
  301. if self.space_realtime and self.space_target:
  302. if self.space_realtime - self.space_target >= 0.2:
  303. chill_water_valve_set = "on"
  304. hot_water_valve_set = "off"
  305. elif self.space_target - self.space_realtime >= 0.2:
  306. chill_water_valve_set = "off"
  307. hot_water_valve_set = "on"
  308. else:
  309. chill_water_valve_set = "hold"
  310. hot_water_valve_set = "hold"
  311. else:
  312. chill_water_valve_set = "hold"
  313. hot_water_valve_set = "hold"
  314. if self.feedback == FeedbackValue.switch_on:
  315. chill_water_valve_set = "off"
  316. hot_water_valve_set = "on"
  317. elif self.feedback == FeedbackValue.so_hot or self.feedback == FeedbackValue.a_little_hot:
  318. chill_water_valve_set = "on"
  319. hot_water_valve_set = "off"
  320. elif self.feedback == FeedbackValue.so_cold or self.feedback == FeedbackValue.a_little_cold:
  321. chill_water_valve_set = "off"
  322. hot_water_valve_set = "on"
  323. else:
  324. chill_water_valve_set = "on"
  325. hot_water_valve_set = "off"
  326. return chill_water_valve_set, hot_water_valve_set
  327. def run(self) -> dict:
  328. try:
  329. speed = self.build_speed_set()
  330. switch_set = self.build_switch_set(speed)
  331. chill_water_valve_set, hot_water_valve_set = self.build_water_valve_set(switch_set)
  332. except TypeError:
  333. raise MissingIOTDataError
  334. res = {
  335. "speed_set": speed,
  336. "switch_set": switch_set,
  337. "chill_water_valve_set": chill_water_valve_set,
  338. "hot_water_valve_set": hot_water_valve_set,
  339. }
  340. return res
  341. async def build_acatfc2_instructions(params: ACATFC2InstructionsRequest) -> ACATFCInstructions:
  342. space = Space(
  343. temperature_target=params.space_temperature_target,
  344. realtime_temperature=params.space_realtime_temperature,
  345. )
  346. fcu = FCU(space=space, supply_air_temperature=-1.0, water_in_temperature=-1.0)
  347. controller = FCUControllerV2(fcu, Season(params.season))
  348. await controller.run()
  349. regulated_fcu = controller.get_results()
  350. instructions = ACATFCInstructions(
  351. switch_set=1 if regulated_fcu.running_status else 0,
  352. speed_set=regulated_fcu.air_valve_speed.value,
  353. temperature_set=float(round_half_up(regulated_fcu.setting_temperature, 1)),
  354. mode_set=regulated_fcu.work_mode,
  355. water_valve_switch_set=1 if regulated_fcu.running_status else 0,
  356. )
  357. return instructions
  358. async def build_acatfc4_instructions(params: ACATFC4InstructionsRequest) -> 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 = FCUController(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_acatfc2_instructions_v2(params: ACATFC2InstructionsRequest) -> dict:
  376. controller = ATFC2Controller(
  377. params.space_temperature_target,
  378. params.space_realtime_temperature,
  379. Season(params.season),
  380. params.feedback,
  381. params.running_status,
  382. params.speed,
  383. )
  384. instructions = controller.run()
  385. return instructions
  386. async def build_acatfc4_instructions_v2(params: ACATFC4InstructionsRequest) -> dict:
  387. controller = ATFC4Controller(
  388. params.space_temperature_target,
  389. params.space_realtime_temperature,
  390. Season(params.season),
  391. params.feedback,
  392. params.running_status,
  393. params.speed,
  394. )
  395. instructions = controller.run()
  396. return instructions