basic.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. from datetime import datetime
  2. from typing import Dict, Optional, Tuple
  3. import arrow
  4. import numpy as np
  5. from loguru import logger
  6. from sqlalchemy.orm import Session
  7. from sqlalchemy.exc import SQLAlchemyError
  8. from app.controllers.equipment.controller import EquipmentController
  9. from app.crud.device.device import device
  10. from app.crud.device.status_timestamp import blowy_feedback_time, high_speed_time
  11. from app.models.domain.devices import ACATVIInstructionsRequest
  12. from app.models.domain.feedback import FeedbackValue
  13. from app.schemas.device.device import DeviceCreate
  14. from app.schemas.equipment import VRF
  15. from app.schemas.season import Season
  16. from app.schemas.device.status_timestamp import (
  17. BlowyFeedbackTimeCreate,
  18. BlowyFeedbackTimeUpdate,
  19. HighSpeedTimeCreate,
  20. HighSpeedTimeUpdate,
  21. )
  22. from app.utils.math import round_half_up
  23. class VRFController(EquipmentController):
  24. def __init__(self, device: VRF, target: float, realtime: float):
  25. super(VRFController, self).__init__()
  26. self.device = device
  27. self.target = target
  28. self.realtime = realtime
  29. def get_switch_set(self) -> str:
  30. if np.isnan(self.target):
  31. if self.device.running_status:
  32. switch_set = "off"
  33. else:
  34. switch_set = "hold"
  35. else:
  36. if self.device.running_status:
  37. switch_set = "hold"
  38. else:
  39. switch_set = "on"
  40. self.device.equip_switch_set = switch_set
  41. return switch_set
  42. def get_mode_set(self) -> str:
  43. if self.target > self.realtime:
  44. mode_set = "cooling"
  45. else:
  46. mode_set = "cooling"
  47. self.device.mode_set = mode_set
  48. return mode_set
  49. def get_temperature_set(self) -> float:
  50. if self.realtime >= 24.0:
  51. diff = self.device.return_air_temp - self.realtime
  52. if diff > 0:
  53. new_temperature_set = self.target + diff
  54. elif diff <= 0:
  55. new_temperature_set = self.target
  56. else:
  57. if np.isnan(self.device.current_temperature_set):
  58. new_temperature_set = self.realtime
  59. else:
  60. new_temperature_set = self.device.current_temperature_set
  61. elif self.realtime <= 22.0:
  62. new_temperature_set = 28.0
  63. else:
  64. if np.isnan(self.device.current_temperature_set):
  65. new_temperature_set = 25.0
  66. else:
  67. new_temperature_set = self.device.current_temperature_set
  68. new_temperature_set = round_half_up(new_temperature_set)
  69. self.device.temperature_set = new_temperature_set
  70. return self.device.temperature_set
  71. def get_speed_set(self) -> str:
  72. if self.realtime >= 24.0:
  73. speed = "M"
  74. elif self.realtime <= 22.0:
  75. speed = "LL"
  76. elif self.device.return_air_temp - self.target >= 3.0:
  77. speed = "HH"
  78. elif self.device.return_air_temp - self.target >= 1.0:
  79. speed = "M"
  80. else:
  81. if self.device.speed == "missing":
  82. speed = "M"
  83. else:
  84. speed = self.device.speed
  85. self.device.speed_set = speed
  86. return speed
  87. async def run(self):
  88. if self.get_switch_set() == "on":
  89. self.get_speed_set()
  90. self.get_mode_set()
  91. self.get_temperature_set()
  92. if self.device.temperature_set == self.device.current_temperature_set:
  93. self.device.temperature_set = 0.0
  94. if self.device.speed_set == self.device.speed:
  95. self.device.speed_set = "hold"
  96. self.device.equip_switch_set = "hold"
  97. if not self.device.running_status:
  98. self.device.equip_switch_set = "on"
  99. self.get_speed_set()
  100. else:
  101. if not self.device.running_status:
  102. self.device.equip_switch_set = "hold"
  103. def get_results(self):
  104. return self.device
  105. class VRFController1(VRFController):
  106. def __init__(
  107. self,
  108. device: VRF,
  109. target: float,
  110. realtime: float,
  111. feedback: FeedbackValue,
  112. blow_feedback_time: datetime,
  113. high_speed_time: datetime,
  114. season: Season,
  115. ):
  116. super(VRFController1, self).__init__(device, target, realtime)
  117. self.feedback = feedback
  118. self.blow_feedback_time = blow_feedback_time
  119. self.high_speed_time = high_speed_time
  120. self.season = season
  121. def get_temperature_set(self) -> float:
  122. if self.season == Season.transition:
  123. new_temperature_set = np.NAN
  124. elif self.season == Season.cooling:
  125. new_temperature_set = np.NAN
  126. if np.isnan(self.target):
  127. return new_temperature_set
  128. if self.device.work_mode == 4.0 or self.device.work_mode == 8.0:
  129. return new_temperature_set
  130. # Default temperature set.
  131. if not self.device.running_status:
  132. new_temperature_set = self.target
  133. # lower limit.
  134. if self.realtime <= 22.0:
  135. new_temperature_set = 28.0
  136. # recovery
  137. # if self.device.temperature_set == 28.0 and self.realtime > 25.0:
  138. # new_temperature_set = self.target
  139. # feedback
  140. if self.feedback.value != "null" and self.device.current_temperature_set:
  141. if (
  142. self.feedback == FeedbackValue.a_little_cold
  143. or self.feedback == FeedbackValue.so_cold
  144. ):
  145. if self.feedback == FeedbackValue.so_cold:
  146. if self.device.speed == "LL":
  147. new_temperature_set = (
  148. self.device.current_temperature_set + 2.0
  149. )
  150. else:
  151. new_temperature_set = (
  152. self.device.current_temperature_set + 1.0
  153. )
  154. else:
  155. new_temperature_set = self.device.current_temperature_set + 1.0
  156. elif (
  157. self.feedback == FeedbackValue.a_little_hot
  158. or self.feedback == FeedbackValue.so_hot
  159. ):
  160. if (
  161. self.feedback == FeedbackValue.so_hot
  162. and self.device.speed == "HH"
  163. ):
  164. if self.device.speed == "HH":
  165. new_temperature_set = (
  166. self.device.current_temperature_set - 2.0
  167. )
  168. else:
  169. new_temperature_set = (
  170. self.device.current_temperature_set - 1.0
  171. )
  172. else:
  173. new_temperature_set = self.device.current_temperature_set - 1.0
  174. if not np.isnan(new_temperature_set):
  175. new_temperature_set = max(24.0, min(28.0, new_temperature_set))
  176. else:
  177. new_temperature_set = np.NAN
  178. if np.isnan(self.target):
  179. return new_temperature_set
  180. if self.device.work_mode == 4.0 or self.device.work_mode == 8.0:
  181. return new_temperature_set
  182. if not self.device.running_status:
  183. new_temperature_set = self.target
  184. if self.realtime >= 27.0:
  185. new_temperature_set = 18.0
  186. if self.feedback.value != "null" and self.device.current_temperature_set:
  187. if (
  188. self.feedback == FeedbackValue.a_little_cold
  189. or self.feedback == FeedbackValue.so_cold
  190. ):
  191. if self.feedback == FeedbackValue.so_cold:
  192. if self.device.speed == "M":
  193. new_temperature_set = (
  194. self.device.current_temperature_set + 1.0
  195. )
  196. else:
  197. new_temperature_set = (
  198. self.device.current_temperature_set + 2.0
  199. )
  200. else:
  201. new_temperature_set = self.device.current_temperature_set + 1.0
  202. elif (
  203. self.feedback == FeedbackValue.a_little_hot
  204. or self.feedback == FeedbackValue.so_hot
  205. ):
  206. if self.feedback == FeedbackValue.so_hot:
  207. if self.device.speed == "M":
  208. new_temperature_set = (
  209. self.device.current_temperature_set - 1.0
  210. )
  211. else:
  212. new_temperature_set = (
  213. self.device.current_temperature_set - 2.0
  214. )
  215. else:
  216. new_temperature_set = self.device.current_temperature_set - 1.0
  217. if not np.isnan(new_temperature_set):
  218. new_temperature_set = max(18.0, min(24.0, new_temperature_set))
  219. new_temperature_set = round_half_up(new_temperature_set)
  220. self.device.temperature_set = new_temperature_set
  221. return new_temperature_set
  222. def get_speed_set(self) -> str:
  223. if self.season == Season.transition:
  224. if np.isnan(self.target):
  225. new_speed = "hold"
  226. else:
  227. if self.device.running_status:
  228. new_speed = "hold"
  229. if self.feedback == FeedbackValue.noisy_or_blowy:
  230. if self.device.speed == "HH":
  231. new_speed = "M"
  232. elif self.device.speed == "M":
  233. new_speed = "LL"
  234. else:
  235. new_speed = "LL"
  236. else:
  237. new_speed = "M"
  238. elif self.season == Season.cooling:
  239. new_speed = "hold"
  240. if np.isnan(self.target):
  241. return new_speed
  242. # Default speed set:
  243. if not self.device.running_status:
  244. new_speed = "M"
  245. # Lower limit.
  246. if self.realtime <= 22.0:
  247. new_speed = "LL"
  248. # Feedback.
  249. if self.feedback == FeedbackValue.so_cold:
  250. if self.device.return_air_temp and self.device.current_temperature_set:
  251. if (
  252. self.device.return_air_temp
  253. > self.device.current_temperature_set
  254. ):
  255. if self.device.speed == "HH":
  256. new_speed = "M"
  257. elif self.device.speed == "M":
  258. new_speed = "LL"
  259. elif self.feedback == FeedbackValue.so_hot:
  260. if self.device.speed == "LL":
  261. new_speed = "M"
  262. elif self.device.speed == "M":
  263. new_speed = "HH"
  264. elif self.feedback == FeedbackValue.noisy_or_blowy:
  265. if self.device.speed == "HH":
  266. new_speed = "M"
  267. elif self.device.speed == "M":
  268. new_speed = "LL"
  269. else:
  270. new_speed = "LL"
  271. else:
  272. new_speed = "hold"
  273. if np.isnan(self.target):
  274. return new_speed
  275. # Default speed set:
  276. if not self.device.running_status:
  277. new_speed = "M"
  278. # Lower limit.
  279. if self.realtime >= 28.0:
  280. new_speed = "LL"
  281. # Feedback.
  282. if self.feedback == FeedbackValue.so_hot:
  283. if self.device.return_air_temp and self.device.current_temperature_set:
  284. if (
  285. self.device.return_air_temp
  286. < self.device.current_temperature_set
  287. ):
  288. if self.device.speed == "HH":
  289. new_speed = "M"
  290. elif self.device.speed == "M":
  291. new_speed = "LL"
  292. elif self.feedback == FeedbackValue.so_cold:
  293. if self.device.speed == "LL":
  294. new_speed = "M"
  295. elif self.device.speed == "M":
  296. new_speed = "HH"
  297. elif self.feedback == FeedbackValue.noisy_or_blowy:
  298. if self.device.speed == "HH":
  299. new_speed = "M"
  300. elif self.device.speed == "M":
  301. new_speed = "LL"
  302. else:
  303. new_speed = "LL"
  304. a_now = arrow.utcnow()
  305. # Restriction.
  306. logger.debug(self.blow_feedback_time)
  307. logger.debug(a_now.date())
  308. if a_now.date() == arrow.get(self.blow_feedback_time).date():
  309. if new_speed == "HH":
  310. new_speed = "M"
  311. # Recovery.
  312. if (a_now - arrow.get(self.high_speed_time)).seconds > 60 * 60:
  313. if new_speed == "HH":
  314. new_speed = "M"
  315. self.device.speed_set = new_speed
  316. return new_speed
  317. def ventilation_mode(self) -> str:
  318. new_speed = "hold"
  319. if np.isnan(self.target):
  320. return new_speed
  321. else:
  322. if not self.device.running_status:
  323. new_speed = "HH"
  324. else:
  325. if self.realtime > 26.0:
  326. new_speed = "HH"
  327. elif self.realtime < 22.0:
  328. new_speed = "LL"
  329. if (
  330. self.feedback == FeedbackValue.a_little_cold
  331. or self.feedback == FeedbackValue.so_cold
  332. or self.feedback.noisy_or_blowy
  333. ):
  334. if self.device.speed == "HH":
  335. new_speed = "M"
  336. elif self.device.speed == "M":
  337. new_speed = "LL"
  338. if (
  339. self.feedback == FeedbackValue.a_little_hot
  340. or self.feedback == FeedbackValue.so_hot
  341. ):
  342. if self.device.speed == "LL":
  343. new_speed = "M"
  344. elif self.device.speed == "M":
  345. new_speed = "HH"
  346. self.device.speed_set = new_speed
  347. return new_speed
  348. async def run(self):
  349. self.get_switch_set()
  350. if self.device.work_mode == 4.0 or self.device.work_mode == 8.0:
  351. self.ventilation_mode()
  352. else:
  353. self.get_speed_set()
  354. self.get_temperature_set()
  355. async def query_status_time(db: Session, device_id: str) -> Tuple[datetime, datetime]:
  356. feedback_time_in_db = blowy_feedback_time.get_time_by_device(db, device_id)
  357. if feedback_time_in_db:
  358. feedback_time = feedback_time_in_db.timestamp
  359. else:
  360. past = arrow.utcnow().shift(hours=-24)
  361. feedback_time = past.naive
  362. if not device.get(db, device_id):
  363. device.create(db=db, obj_in=DeviceCreate(id=device_id))
  364. blowy_feedback_time.create(
  365. db=db,
  366. obj_in=BlowyFeedbackTimeCreate(
  367. timestamp=feedback_time, device_id=device_id
  368. ),
  369. )
  370. high_speed_time_in_db = high_speed_time.get_time_by_device(db, device_id)
  371. if high_speed_time_in_db:
  372. high_time = high_speed_time_in_db.timestamp
  373. else:
  374. past = arrow.utcnow().shift(hours=-24)
  375. high_time = past.naive
  376. if not device.get(db, device_id):
  377. device.create(db=db, obj_in=DeviceCreate(id=device_id))
  378. high_speed_time.create(
  379. db=db, obj_in=HighSpeedTimeCreate(timestamp=high_time, device_id=device_id)
  380. )
  381. return feedback_time, high_time
  382. @logger.catch()
  383. async def build_acatvi_instructions(
  384. params: ACATVIInstructionsRequest, db: Session
  385. ) -> Dict:
  386. target = (
  387. np.NAN
  388. if params.space_temperature_target == -1.0
  389. else params.space_temperature_target
  390. )
  391. realtime = (
  392. np.NAN
  393. if params.space_realtime_temperature == -1.0
  394. else params.space_realtime_temperature
  395. )
  396. return_air_temp = (
  397. np.NAN
  398. if params.return_air_temperature == -1.0
  399. else params.return_air_temperature
  400. )
  401. current_temperature_set = (
  402. np.NAN
  403. if params.current_temperature_set == -1.0
  404. else round_half_up(params.current_temperature_set)
  405. )
  406. vrf = VRF(
  407. return_air_temp=return_air_temp,
  408. current_temperature_set=current_temperature_set,
  409. speed=params.current_speed,
  410. running_status=params.running_status,
  411. work_mode=params.work_mode,
  412. )
  413. act_blowy_feedback_time, act_high_speed_time = await query_status_time(
  414. db, params.device_id
  415. )
  416. controller = VRFController1(
  417. vrf,
  418. target,
  419. realtime,
  420. params.feedback,
  421. act_blowy_feedback_time,
  422. act_high_speed_time,
  423. params.season,
  424. )
  425. await controller.run()
  426. regulated_vrf = controller.get_results()
  427. if params.feedback == FeedbackValue.noisy_or_blowy:
  428. blowy_feedback_time_in_db = blowy_feedback_time.get_time_by_device(
  429. db=db, device_id=params.device_id
  430. )
  431. if blowy_feedback_time_in_db:
  432. blowy_feedback_time.update(
  433. db=db,
  434. db_obj=blowy_feedback_time_in_db,
  435. obj_in=BlowyFeedbackTimeUpdate(
  436. timestamp=datetime.utcnow(), device_id=params.device_id
  437. ),
  438. )
  439. else:
  440. blowy_feedback_time.create(
  441. db=db,
  442. obj_in=BlowyFeedbackTimeCreate(
  443. timestamp=datetime.utcnow(), device_id=params.device_id
  444. ),
  445. )
  446. if regulated_vrf.speed_set == "HH":
  447. high_speed_time_in_db = high_speed_time.get_time_by_device(
  448. db=db, device_id=params.device_id
  449. )
  450. if high_speed_time_in_db:
  451. high_speed_time.update(
  452. db=db,
  453. db_obj=high_speed_time_in_db,
  454. obj_in=HighSpeedTimeUpdate(
  455. timestamp=datetime.utcnow(), device_id=params.device_id
  456. ),
  457. )
  458. else:
  459. high_speed_time.create(
  460. db=db,
  461. obj_in=HighSpeedTimeCreate(
  462. timestamp=datetime.utcnow(), device_id=params.device_id
  463. ),
  464. )
  465. instructions = dict()
  466. instructions.update({"switch_set": regulated_vrf.equip_switch_set})
  467. instructions.update({"speed_set": regulated_vrf.speed_set})
  468. if regulated_vrf.temperature_set and not np.isnan(regulated_vrf.temperature_set):
  469. instructions.update({"temperature_set": regulated_vrf.temperature_set})
  470. return instructions