basic.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. if a_now.date() == arrow.get(self.blow_feedback_time).date():
  307. if new_speed == "HH":
  308. new_speed = "M"
  309. # Recovery.
  310. if (a_now - arrow.get(self.high_speed_time)).seconds > 60 * 60:
  311. if new_speed == "HH":
  312. new_speed = "M"
  313. self.device.speed_set = new_speed
  314. return new_speed
  315. def ventilation_mode(self) -> str:
  316. new_speed = "hold"
  317. if np.isnan(self.target):
  318. return new_speed
  319. else:
  320. if not self.device.running_status:
  321. new_speed = "HH"
  322. else:
  323. if self.realtime > 26.0:
  324. new_speed = "HH"
  325. elif self.realtime < 22.0:
  326. new_speed = "LL"
  327. if (
  328. self.feedback == FeedbackValue.a_little_cold
  329. or self.feedback == FeedbackValue.so_cold
  330. or self.feedback.noisy_or_blowy
  331. ):
  332. if self.device.speed == "HH":
  333. new_speed = "M"
  334. elif self.device.speed == "M":
  335. new_speed = "LL"
  336. elif (
  337. self.feedback == FeedbackValue.a_little_hot
  338. or self.feedback == FeedbackValue.so_hot
  339. ):
  340. if self.device.speed == "LL":
  341. new_speed = "M"
  342. elif self.device.speed == "M":
  343. new_speed = "HH"
  344. self.device.speed_set = new_speed
  345. return new_speed
  346. async def run(self):
  347. self.get_switch_set()
  348. if self.device.work_mode == 4.0 or self.device.work_mode == 8.0:
  349. self.ventilation_mode()
  350. else:
  351. self.get_speed_set()
  352. self.get_temperature_set()
  353. async def query_status_time(db: Session, device_id: str) -> Tuple[datetime, datetime]:
  354. feedback_time_in_db = blowy_feedback_time.get_time_by_device(db, device_id)
  355. if feedback_time_in_db:
  356. feedback_time = feedback_time_in_db.timestamp
  357. else:
  358. past = arrow.utcnow().shift(hours=-24)
  359. feedback_time = past.naive
  360. if not device.get(db, device_id):
  361. device.create(db=db, obj_in=DeviceCreate(id=device_id))
  362. blowy_feedback_time.create(
  363. db=db,
  364. obj_in=BlowyFeedbackTimeCreate(
  365. timestamp=feedback_time, device_id=device_id
  366. ),
  367. )
  368. high_speed_time_in_db = high_speed_time.get_time_by_device(db, device_id)
  369. if high_speed_time_in_db:
  370. high_time = high_speed_time_in_db.timestamp
  371. else:
  372. past = arrow.utcnow().shift(hours=-24)
  373. high_time = past.naive
  374. if not device.get(db, device_id):
  375. device.create(db=db, obj_in=DeviceCreate(id=device_id))
  376. high_speed_time.create(
  377. db=db, obj_in=HighSpeedTimeCreate(timestamp=high_time, device_id=device_id)
  378. )
  379. return feedback_time, high_time
  380. @logger.catch()
  381. async def build_acatvi_instructions(
  382. params: ACATVIInstructionsRequest, db: Session
  383. ) -> Dict:
  384. target = (
  385. np.NAN
  386. if params.space_temperature_target == -1.0
  387. else params.space_temperature_target
  388. )
  389. realtime = (
  390. np.NAN
  391. if params.space_realtime_temperature == -1.0
  392. else params.space_realtime_temperature
  393. )
  394. return_air_temp = (
  395. np.NAN
  396. if params.return_air_temperature == -1.0
  397. else params.return_air_temperature
  398. )
  399. current_temperature_set = (
  400. np.NAN
  401. if params.current_temperature_set == -1.0
  402. else round_half_up(params.current_temperature_set)
  403. )
  404. vrf = VRF(
  405. return_air_temp=return_air_temp,
  406. current_temperature_set=current_temperature_set,
  407. speed=params.current_speed,
  408. running_status=params.running_status,
  409. work_mode=params.work_mode,
  410. )
  411. act_blowy_feedback_time, act_high_speed_time = await query_status_time(
  412. db, params.device_id
  413. )
  414. controller = VRFController1(
  415. vrf,
  416. target,
  417. realtime,
  418. params.feedback,
  419. act_blowy_feedback_time,
  420. act_high_speed_time,
  421. params.season,
  422. )
  423. await controller.run()
  424. regulated_vrf = controller.get_results()
  425. if params.feedback == FeedbackValue.noisy_or_blowy:
  426. blowy_feedback_time_in_db = blowy_feedback_time.get_time_by_device(
  427. db=db, device_id=params.device_id
  428. )
  429. if blowy_feedback_time_in_db:
  430. blowy_feedback_time.update(
  431. db=db,
  432. db_obj=blowy_feedback_time_in_db,
  433. obj_in=BlowyFeedbackTimeUpdate(
  434. timestamp=datetime.utcnow(), device_id=params.device_id
  435. ),
  436. )
  437. else:
  438. blowy_feedback_time.create(
  439. db=db,
  440. obj_in=BlowyFeedbackTimeCreate(
  441. timestamp=datetime.utcnow(), device_id=params.device_id
  442. ),
  443. )
  444. if regulated_vrf.speed_set == "HH":
  445. high_speed_time_in_db = high_speed_time.get_time_by_device(
  446. db=db, device_id=params.device_id
  447. )
  448. if high_speed_time_in_db:
  449. high_speed_time.update(
  450. db=db,
  451. db_obj=high_speed_time_in_db,
  452. obj_in=HighSpeedTimeUpdate(
  453. timestamp=datetime.utcnow(), device_id=params.device_id
  454. ),
  455. )
  456. else:
  457. high_speed_time.create(
  458. db=db,
  459. obj_in=HighSpeedTimeCreate(
  460. timestamp=datetime.utcnow(), device_id=params.device_id
  461. ),
  462. )
  463. instructions = dict()
  464. instructions.update({"switch_set": regulated_vrf.equip_switch_set})
  465. instructions.update({"speed_set": regulated_vrf.speed_set})
  466. if regulated_vrf.temperature_set and not np.isnan(regulated_vrf.temperature_set):
  467. instructions.update({"temperature_set": regulated_vrf.temperature_set})
  468. return instructions