heatpump_controller.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /**
  2. * @Author: 李建
  3. * @Date: 2025/9/9 13:55
  4. * Description: 热泵控制器
  5. * Copyright: Copyright (©) 2025 永续绿建. All rights reserved.
  6. */
  7. #include "heatpump_controller.h"
  8. #include "system/miscellaneous_interface.h"
  9. #include "freertos/FreeRTOS.h"
  10. #include <freertos/task.h>
  11. #include <cJSON.h>
  12. #include <esp_log.h>
  13. #include "freertos/semphr.h"
  14. #include "sht30.h"
  15. #include "gateway/access.h"
  16. #include "sub_device/command.h"
  17. #include "lvgl_port.h"
  18. #include "modbus_master.h"
  19. hp_controller_t hp_controller = {0};
  20. static SemaphoreHandle_t report_task_sem; // 上报任务信号量,用于实现操作完成上立即上报当前状态
  21. static TaskHandle_t cloud_report_handle;
  22. static const char *TAG = "hp_controller";
  23. static TaskHandle_t report_trigger_handle;
  24. static TaskHandle_t status_task_handle;
  25. static bool need_report_error = false;
  26. void set_bit(uint16_t *num, uint8_t bit, bool value) {
  27. if (bit > 15) return;
  28. if (value) {
  29. *num |= (1 << bit);
  30. } else {
  31. *num &= ~(1 << bit);
  32. }
  33. }
  34. bool get_bit(uint16_t num, uint8_t bit) {
  35. if (bit > 15) return false;
  36. return (num >> bit) & 1;
  37. }
  38. esp_err_t set_relay_status(uint8_t no, uint8_t value) {
  39. esp_err_t err = ESP_OK;
  40. set_bit(&hp_controller.relay_status, no, value);
  41. nvs_set_uint16(hp_controller.relay_status, NVS_RELEY_STATUS_KEY);
  42. if (cloud_connected) {
  43. xSemaphoreGive(report_task_sem);
  44. }
  45. return err;
  46. }
  47. static void update_power_ui(bool on_off) {
  48. // 同步更新 UI
  49. if (guider_ui.screen) {
  50. lvgl_port_lock(-1);
  51. //lv_obj_set_state(guider_ui.screen_ib_power, LV_IMAGEBUTTON_STATE_CHECKED_RELEASED, on_off);
  52. if (on_off) {
  53. lv_obj_remove_flag(guider_ui.screen_cont_power_on, LV_OBJ_FLAG_HIDDEN);
  54. lv_obj_add_flag(guider_ui.screen_cont_power_off, LV_OBJ_FLAG_HIDDEN);
  55. } else {
  56. lv_obj_remove_flag(guider_ui.screen_cont_power_off, LV_OBJ_FLAG_HIDDEN);
  57. lv_obj_add_flag(guider_ui.screen_cont_power_on, LV_OBJ_FLAG_HIDDEN);
  58. }
  59. lvgl_port_unlock();
  60. }
  61. }
  62. /******************************************云端指令开始*************************************************************/
  63. // 云端指令:设置电源开关
  64. static void cloud_set_power(char *params) {
  65. cJSON *data = cJSON_Parse(params);
  66. if (data != NULL) {
  67. cJSON *value = cJSON_GetObjectItemCaseSensitive(data, "value");
  68. hp_controller.power = value->valueint;
  69. heatpump_set_power(true);
  70. update_power_ui(hp_controller.power);
  71. cJSON_Delete(data);
  72. }
  73. }
  74. // 云端指令:设置模式
  75. static void cloud_set_mode(char *params) {
  76. cJSON *data = cJSON_Parse(params);
  77. if (data != NULL) {
  78. cJSON *value = cJSON_GetObjectItemCaseSensitive(data, "value");
  79. hp_controller.mode = value->valueint;
  80. if (guider_ui.screen) {
  81. if (hp_controller.mode == 1) {
  82. lvgl_port_lock(0);
  83. lv_label_set_text_static(guider_ui.screen_lab_mode, "制冷");
  84. lv_obj_set_style_arc_color(guider_ui.screen_arc_temp, lv_color_hex(0x00d9ff),
  85. LV_PART_INDICATOR | LV_STATE_DEFAULT);
  86. lv_label_set_text_fmt(guider_ui.screen_lab_ac_temp, "%d", hp_controller.cool_set_temp);
  87. lv_obj_add_state(guider_ui.screen_ib_cool, LV_STATE_CHECKED);
  88. lv_obj_remove_state(guider_ui.screen_ib_heat, LV_STATE_CHECKED);
  89. lv_arc_set_range(guider_ui.screen_arc_temp, 7 * 2, 22 * 2);
  90. lv_arc_set_value(guider_ui.screen_arc_temp, hp_controller.cool_set_temp * 2);
  91. lvgl_port_unlock();
  92. } else if (hp_controller.mode == 2) {
  93. lvgl_port_lock(0);
  94. lv_obj_set_style_arc_color(guider_ui.screen_arc_temp, lv_color_hex(0xfa890a),
  95. LV_PART_INDICATOR | LV_STATE_DEFAULT);
  96. lv_label_set_text_static(guider_ui.screen_lab_mode, "制热");
  97. lv_label_set_text_fmt(guider_ui.screen_lab_ac_temp, "%d", hp_controller.heat_set_temp);
  98. lv_obj_remove_state(guider_ui.screen_ib_cool, LV_STATE_CHECKED);
  99. lv_obj_add_state(guider_ui.screen_ib_heat, LV_STATE_CHECKED);
  100. lv_arc_set_range(guider_ui.screen_arc_temp, 30 * 2, 60 * 2);
  101. lv_arc_set_value(guider_ui.screen_arc_temp, hp_controller.heat_set_temp * 2);
  102. lvgl_port_unlock();
  103. }
  104. }
  105. heatpump_set_mode(hp_controller.mode);
  106. cJSON_Delete(data);
  107. }
  108. }
  109. // 云端指令:设置水温
  110. static void cloud_set_water_temp(char *params) {
  111. cJSON *data = cJSON_Parse(params);
  112. if (data != NULL) {
  113. cJSON *value = cJSON_GetObjectItemCaseSensitive(data, "value");
  114. heatpump_set_temp(value->valueint);
  115. // 更新UI
  116. if (guider_ui.screen) {
  117. lvgl_port_lock(0);
  118. lv_label_set_text_fmt(guider_ui.screen_lab_ac_temp, "%d", value->valueint);
  119. lv_arc_set_value(guider_ui.screen_arc_temp, value->valueint * 2);
  120. lvgl_port_unlock();
  121. }
  122. cJSON_Delete(data);
  123. }
  124. }
  125. // 云端指令 :设置分集水器某一路开关
  126. static void cloud_set_IO(char *params) {
  127. cJSON *data = cJSON_Parse(params);
  128. if (data != NULL) {
  129. cJSON *value = cJSON_GetObjectItemCaseSensitive(data, "value");
  130. cJSON *no = cJSON_GetObjectItemCaseSensitive(data, "no");
  131. if (no->valueint > 8 || value->valueint > 1) return;
  132. uint8_t n = no->valueint - 1;
  133. uint8_t v = value->valueint;
  134. set_relay_status(n, v); // 要减1
  135. // 更新UI
  136. if (guider_ui.WaterValvePage) {
  137. lvgl_port_lock(0);
  138. if (n == 0) {
  139. v
  140. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_1, LV_STATE_CHECKED, true)
  141. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_1, LV_STATE_CHECKED);
  142. }
  143. if (n == 1) {
  144. v
  145. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_2, LV_STATE_CHECKED, true)
  146. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_2, LV_STATE_CHECKED);
  147. }
  148. if (n == 2) {
  149. v
  150. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_3, LV_STATE_CHECKED, true)
  151. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_3, LV_STATE_CHECKED);
  152. }
  153. if (n == 3) {
  154. v
  155. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_4, LV_STATE_CHECKED, true)
  156. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_4, LV_STATE_CHECKED);
  157. }
  158. if (n == 4) {
  159. v
  160. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_5, LV_STATE_CHECKED, true)
  161. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_5, LV_STATE_CHECKED);
  162. }
  163. if (n == 5) {
  164. v
  165. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_6, LV_STATE_CHECKED, true)
  166. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_6, LV_STATE_CHECKED);
  167. }
  168. if (n == 6) {
  169. v
  170. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_7, LV_STATE_CHECKED, true)
  171. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_7, LV_STATE_CHECKED);
  172. }
  173. if (n == 7) {
  174. v
  175. ? lv_obj_set_state(guider_ui.WaterValvePage_sw_8, LV_STATE_CHECKED, true)
  176. : lv_obj_clear_state(guider_ui.WaterValvePage_sw_8, LV_STATE_CHECKED);
  177. }
  178. lvgl_port_unlock();
  179. }
  180. cJSON_Delete(data);
  181. }
  182. }
  183. // 云端指令:联动开关
  184. static void cloud_set_link(char *params) {
  185. cJSON *data = cJSON_Parse(params);
  186. if (data != NULL) {
  187. cJSON *value = cJSON_GetObjectItemCaseSensitive(data, "value");
  188. hp_controller.linkage = value->valueint;
  189. heatpump_set_linkage();
  190. cJSON_Delete(data);
  191. }
  192. }
  193. /******************************************云端指令END*************************************************************/
  194. static void register_sparrow_commands() {
  195. sparrow_command set_power_cmd = {
  196. .name = "setPower",
  197. .unpack = &cloud_set_power
  198. };
  199. register_sparrow_command(set_power_cmd);
  200. // setMode
  201. sparrow_command set_mode_cmd = {
  202. .name = "setMode",
  203. .unpack = &cloud_set_mode
  204. };
  205. register_sparrow_command(set_mode_cmd);
  206. // setWaterTemp
  207. sparrow_command set_water_temp = {
  208. .name = "setWaterTemp",
  209. .unpack = &cloud_set_water_temp
  210. };
  211. register_sparrow_command(set_water_temp);
  212. // setIO
  213. sparrow_command set_io = {
  214. .name = "setIO",
  215. .unpack = &cloud_set_IO
  216. };
  217. register_sparrow_command(set_io);
  218. // setLinkage
  219. sparrow_command set_link = {
  220. .name = "setLinkage",
  221. .unpack = &cloud_set_link
  222. };
  223. register_sparrow_command(set_link);
  224. }
  225. _Noreturn static void report_trigger_task(void *arg) {
  226. for (;;) {
  227. if (cloud_connected) {
  228. xSemaphoreGive(report_task_sem);
  229. }
  230. vTaskDelay(pdTICKS_TO_MS(system_setting.report_data_duration * 1000));
  231. }
  232. }
  233. /**
  234. * 状态同步任务
  235. * @param arg
  236. */
  237. _Noreturn static void status_sync_task(void *arg) {
  238. esp_err_t err = ESP_OK;
  239. vTaskDelay(pdMS_TO_TICKS(5000));
  240. char in_temp_buf[12];
  241. char out_hum_buf[12];
  242. for (;;) {
  243. // 联动判断
  244. if (gpio_get_level(41) == 0 && system_setting.allow_linkage == 1) {
  245. hp_controller.power = 1;
  246. update_power_ui(true);
  247. }
  248. err = mm_set_param(CID_HP_POWER, &hp_controller.power);
  249. err = mm_set_param(CID_HP_SET_MODE, &hp_controller.mode);
  250. if (hp_controller.mode == MODE_COOL) mm_set_param(CID_HP_COOL_SET_TEMP, &hp_controller.cool_set_temp);
  251. if (hp_controller.mode == MODE_HEAT) mm_set_param(CID_HP_HEAT_SET_TEMP, &hp_controller.heat_set_temp);
  252. mm_get_param(CID_HP_OUTDOOR_TEMP, &hp_controller.outdoor_temp);
  253. mm_get_param(CID_HP_OUT_WATER_TEMP, &hp_controller.out_water_temp);
  254. err = mm_get_param(CID_HP_IN_WATER_TEMP, &hp_controller.in_water_temp);
  255. if (guider_ui.screen) {
  256. snprintf(in_temp_buf, sizeof(in_temp_buf), "%d°C", hp_controller.in_water_temp);
  257. snprintf(out_hum_buf, sizeof(out_hum_buf), "%d°C", hp_controller.out_water_temp);
  258. lvgl_port_lock(-1);
  259. lv_label_set_text_static(guider_ui.screen_lab_in_temp, in_temp_buf);
  260. lv_label_set_text_static(guider_ui.screen_lab_out_temp, out_hum_buf);
  261. if (err != ESP_OK) {
  262. lv_obj_remove_flag(guider_ui.screen_lab_icon_485, LV_OBJ_FLAG_HIDDEN);
  263. } else {
  264. lv_obj_add_flag(guider_ui.screen_lab_icon_485, LV_OBJ_FLAG_HIDDEN);
  265. }
  266. lvgl_port_unlock();
  267. }
  268. // 如果开启了分集水器控制
  269. if (system_setting.active_fjsq == 1) {
  270. uint8_t status = get_bit(hp_controller.relay_status, 0);
  271. err = mm_set_param(CID_RELAY_1_STATUS, &status);
  272. if (err == ESP_OK) hp_controller.fjsq_exist = 1;
  273. status = get_bit(hp_controller.relay_status, 1);
  274. mm_set_param(CID_RELAY_2_STATUS, &status);
  275. status = get_bit(hp_controller.relay_status, 2);
  276. mm_set_param(CID_RELAY_3_STATUS, &status);
  277. status = get_bit(hp_controller.relay_status, 3);
  278. mm_set_param(CID_RELAY_4_STATUS, &status);
  279. status = get_bit(hp_controller.relay_status, 4);
  280. mm_set_param(CID_RELAY_5_STATUS, &status);
  281. status = get_bit(hp_controller.relay_status, 5);
  282. mm_set_param(CID_RELAY_6_STATUS, &status);
  283. status = get_bit(hp_controller.relay_status, 6);
  284. mm_set_param(CID_RELAY_7_STATUS, &status);
  285. status = get_bit(hp_controller.relay_status, 7);
  286. err = mm_set_param(CID_RELAY_8_STATUS, &status);
  287. if (err != ESP_OK) hp_controller.fjsq_exist = 0;
  288. }
  289. // 读取故障
  290. mm_get_param(CID_HP_ERROR, &hp_controller.error);
  291. if (hp_controller.error == 1) {
  292. lvgl_port_lock(-1);
  293. lv_obj_remove_flag(guider_ui.screen_lab_icon_err, LV_OBJ_FLAG_HIDDEN);
  294. lvgl_port_unlock();
  295. need_report_error = true;
  296. xSemaphoreGive(report_task_sem);
  297. } else {
  298. lvgl_port_lock(-1);
  299. lv_obj_add_flag(guider_ui.screen_lab_icon_err, LV_OBJ_FLAG_HIDDEN);
  300. lvgl_port_unlock();
  301. }
  302. ESP_LOGD(TAG, "error:%d", hp_controller.error);
  303. vTaskDelay(5000 / portTICK_PERIOD_MS);
  304. }
  305. }
  306. /**
  307. * 云端上报任务
  308. * @param arg
  309. */
  310. _Noreturn static void cloud_report_task(void *arg) {
  311. for (;;) {
  312. if (xSemaphoreTake(report_task_sem, portMAX_DELAY) == pdTRUE) {
  313. if (need_report_error) {
  314. need_report_error = false;
  315. cJSON *data = cJSON_CreateObject();
  316. cJSON_AddNumberToObject(data, "error", hp_controller.error);
  317. cJSON *root = cJSON_CreateObject();
  318. cJSON_AddStringToObject(root, "cmd", "error");
  319. cJSON_AddItemToObject(root, "params", data);
  320. const publish_message_t msg = {
  321. .topic = "error",
  322. .data = root,
  323. };
  324. sparrow_publish_event(msg);
  325. }
  326. ESP_LOGD(TAG, "cloud report");
  327. cJSON *data = cJSON_CreateObject();
  328. cJSON_AddNumberToObject(data, "power", hp_controller.power);
  329. cJSON_AddNumberToObject(data, "cool_set_water_temp", hp_controller.cool_set_temp);
  330. cJSON_AddNumberToObject(data, "heat_set_water_temp", hp_controller.heat_set_temp);
  331. cJSON_AddNumberToObject(data, "mode", hp_controller.mode);
  332. cJSON_AddNumberToObject(data, "temperature", (int) sht30Data.temperature);
  333. cJSON_AddNumberToObject(data, "humidity", (int) sht30Data.humidity);
  334. cJSON_AddNumberToObject(data, "outdoor_temp", hp_controller.outdoor_temp);
  335. cJSON_AddNumberToObject(data, "out_water_temp", hp_controller.out_water_temp);
  336. cJSON_AddNumberToObject(data, "in_water_temp", hp_controller.in_water_temp);
  337. cJSON_AddNumberToObject(data, "fjsq_exist", hp_controller.fjsq_exist);
  338. cJSON_AddNumberToObject(data, "fjsq_status", hp_controller.relay_status);
  339. cJSON_AddNumberToObject(data, "linkage", hp_controller.linkage);
  340. cJSON *root = cJSON_CreateObject();
  341. cJSON_AddStringToObject(root, "cmd", "status");
  342. cJSON_AddItemToObject(root, "params", data);
  343. const publish_message_t msg = {
  344. .topic = "status",
  345. .data = root,
  346. };
  347. sparrow_publish_status(msg);
  348. }
  349. }
  350. }
  351. void heatpump_controller_init(system_setting_t *setting) {
  352. hp_controller.mode = setting->hp_mode;
  353. hp_controller.power = setting->hp_power;
  354. hp_controller.cool_set_temp = setting->hp_cool_set_temp;
  355. hp_controller.heat_set_temp = setting->hp_heat_set_temp;
  356. hp_controller.relay_status = setting->reley_status;
  357. hp_controller.linkage = setting->allow_linkage;
  358. register_sparrow_commands();
  359. report_task_sem = xSemaphoreCreateBinary();
  360. // 启动状态同步任务
  361. xTaskCreate(status_sync_task, "status_sync_task", 6 * 1024, NULL, 5, &status_task_handle);
  362. // 启动云端上报任务,受信号量控制,无信号时任务不作操作,只有本地有操作或定时上报任务触发时才会上报。
  363. xTaskCreate(cloud_report_task, "report_task", 6 * 1024, NULL, 5, &cloud_report_handle);
  364. // 启动云端上报任务
  365. xTaskCreate(report_trigger_task, "trigger_task", 2 * 1024, NULL, 5, &report_trigger_handle);
  366. // 初始化联动IO
  367. const gpio_config_t io_config = {
  368. .pin_bit_mask = (1ULL << 41),
  369. .mode = GPIO_MODE_DEF_INPUT
  370. };
  371. gpio_config(&io_config);
  372. }
  373. esp_err_t heatpump_set_power(bool saved) {
  374. esp_err_t err = ESP_OK;
  375. if (saved)
  376. nvs_set_uint16(hp_controller.power, NVS_HP_POWER_KEY);
  377. if (cloud_connected) {
  378. xSemaphoreGive(report_task_sem);
  379. }
  380. return err;
  381. }
  382. esp_err_t heatpump_set_mode(uint8_t mode) {
  383. esp_err_t err = ESP_OK;
  384. nvs_set_uint16(hp_controller.mode, NVS_HP_MODE_KEY);
  385. if (cloud_connected) {
  386. xSemaphoreGive(report_task_sem);
  387. }
  388. return err;
  389. }
  390. esp_err_t heatpump_set_temp(uint16_t temp) {
  391. esp_err_t err = ESP_OK;
  392. if (hp_controller.mode == MODE_COOL) {
  393. hp_controller.cool_set_temp = temp;
  394. nvs_set_uint16(hp_controller.cool_set_temp, NVS_HP_COOL_SET_TEMP_KEY);
  395. }
  396. if (hp_controller.mode == MODE_HEAT) {
  397. hp_controller.heat_set_temp = temp;
  398. nvs_set_uint16(hp_controller.heat_set_temp, NVS_HP_HEAT_SET_TEMP_KEY);
  399. }
  400. if (cloud_connected) {
  401. xSemaphoreGive(report_task_sem);
  402. }
  403. return err;
  404. }
  405. esp_err_t heatpump_set_linkage() {
  406. esp_err_t err = ESP_OK;
  407. nvs_set_uint16(hp_controller.linkage, NVS_ALLOW_LINKAGE_KEY);
  408. return err;
  409. }
  410. void stop_controller() {
  411. vTaskDelete(status_task_handle);
  412. vTaskDelete(cloud_report_handle);
  413. vTaskDelete(report_trigger_handle);
  414. // 停止modbus 协议 stack
  415. modbus_master_destroy();
  416. }