You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AppCore.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #include "AppCore.h"
  2. #include "Logs.h"
  3. #define xstr(s) str(s)
  4. #define str(s) #s
  5. #define HIBERNATE_DELAY 5000
  6. #define SENSORS_CHECK_INTERVAL 2000
  7. #define SENSORS_REQUEST_DELAY 750
  8. #define TEMP_TRIGGER ((temp_t)(5 * 10.0f))
  9. #define TEMP_INTERVAL ((temp_t)(0.5 * 10.0f))
  10. #define LCD_CHAR_SENSOR 1
  11. #define PIN_ONEWIRE A0
  12. #define PIN_RELAY2 A1
  13. #define PIN_RELAY1 A2
  14. #define PIN_BTN_CANCEL 9
  15. #define PIN_BTN_OK 10
  16. #define PIN_BTN_MINUS 11
  17. #define PIN_BTN_PLUS 12
  18. #define PIN_LCD_LED 2
  19. #define PIN_LCD_RS 8
  20. #define PIN_LCD_ENABLE 7
  21. #define PIN_LCD_D4 6
  22. #define PIN_LCD_D5 5
  23. #define PIN_LCD_D6 4
  24. #define PIN_LCD_D7 3
  25. UiStateEnum AppCore::s_menuSequence[4]{MenuWaterSetting, MenuHeaterSetting, MenuTempTrigger, MenuVersion};
  26. AppCore::AppCore()
  27. : m_appCoreState(new AppCoreState{
  28. .appState = {
  29. .lastSensorRequestMs = 0,
  30. .hasReadSensors = true,
  31. .water = {
  32. .current = TEMP_T_INVALID,
  33. .setting = 0,
  34. .isActive = false,
  35. .pinNo = PIN_RELAY1
  36. },
  37. .heater = {
  38. .current = TEMP_T_INVALID,
  39. .setting = 0,
  40. .isActive = false,
  41. .pinNo = PIN_RELAY2
  42. },
  43. .tempTrigger = TEMP_TRIGGER,
  44. .pCurrentSettingEdit = nullptr,
  45. .currentSettingEditTmp = 0
  46. },
  47. .uiState = {
  48. .state = HomeLighting,
  49. .lastOpMs = 0,
  50. .isUpdateNeeded = true
  51. }
  52. })
  53. , m_pBtnCancel(new Button{PIN_BTN_CANCEL})
  54. , m_pBtnOk(new Button{PIN_BTN_OK})
  55. , m_pBtnMinus(new Button{PIN_BTN_MINUS})
  56. , m_pBtnPlus(new Button{PIN_BTN_PLUS})
  57. , m_pButtons{m_pBtnCancel, m_pBtnOk, m_pBtnMinus, m_pBtnPlus}
  58. , m_pLcd(new LiquidCrystal{PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7})
  59. , m_pOneWire(new OneWire{PIN_ONEWIRE})
  60. , m_pSensors(new DallasTemperature{m_pOneWire})
  61. , m_sensor1{0}
  62. , m_sensor2{0}
  63. {
  64. }
  65. void AppCore::setup()
  66. {
  67. Serial.begin(9600);
  68. LOG_FN_BEGIN(1);
  69. m_pSensors->begin();
  70. LOG(5, "Found %i sensors", m_pSensors->getDeviceCount());
  71. m_pSensors->getAddress(m_sensor1, 0);
  72. m_pSensors->getAddress(m_sensor2, 1);
  73. m_pSensors->setWaitForConversion(false);
  74. for (auto& g_button : m_pButtons)
  75. {
  76. g_button->begin();
  77. }
  78. pinMode(PIN_RELAY1, OUTPUT);
  79. digitalWrite(PIN_RELAY1, LOW);
  80. pinMode(PIN_RELAY2, OUTPUT);
  81. digitalWrite(PIN_RELAY2, LOW);
  82. pinMode(PIN_LCD_LED, OUTPUT);
  83. digitalWrite(PIN_LCD_LED, 1);
  84. m_pLcd->begin(16, 2);
  85. byte sensorChar[8] = {
  86. B00100,
  87. B01110,
  88. B01110,
  89. B01110,
  90. B01110,
  91. B11111,
  92. B11111,
  93. B01110,
  94. };
  95. m_pLcd->createChar(LCD_CHAR_SENSOR, sensorChar);
  96. bool allButtonsPressed = true;
  97. for (auto& g_button : m_pButtons)
  98. {
  99. g_button->read();
  100. allButtonsPressed = allButtonsPressed && g_button->isPressed();
  101. }
  102. if (!allButtonsPressed)
  103. {
  104. LOG(5, "%s: Loading settings", __FUNCTION__);
  105. m_storage.load(*m_appCoreState);
  106. }
  107. else
  108. {
  109. LOG(5, "%s: Resetting settings", __FUNCTION__);
  110. m_storage.save(*m_appCoreState);
  111. m_pLcd->clear();
  112. m_pLcd->setCursor(6, 0);
  113. m_pLcd->print("Reset");
  114. bool allButtonsPressed = true;
  115. while (allButtonsPressed)
  116. {
  117. for (auto& g_button : m_pButtons)
  118. {
  119. g_button->read();
  120. allButtonsPressed = allButtonsPressed && g_button->isPressed();
  121. }
  122. }
  123. }
  124. LOG_FN_END(1);
  125. }
  126. void AppCore::loop()
  127. {
  128. LOG_FN_BEGIN(50);
  129. const auto& currentMs = millis();
  130. if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL)
  131. {
  132. m_appCoreState->appState.lastSensorRequestMs = currentMs;
  133. m_appCoreState->appState.hasReadSensors = false;
  134. m_pSensors->requestTemperaturesByAddress(m_sensor1);
  135. m_pSensors->requestTemperaturesByAddress(m_sensor2);
  136. }
  137. if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY &&
  138. !m_appCoreState->appState.hasReadSensors)
  139. {
  140. m_appCoreState->appState.hasReadSensors = true;
  141. readAndUpdateSensors(&m_appCoreState->appState.water, m_sensor1);
  142. readAndUpdateSensors(&m_appCoreState->appState.heater, m_sensor2);
  143. checkBoilerItem(&m_appCoreState->appState.water);
  144. checkBoilerItem(&m_appCoreState->appState.heater);
  145. }
  146. for (auto& pButton : m_pButtons)
  147. {
  148. pButton->read();
  149. if (pButton->isPressed())
  150. {
  151. m_appCoreState->uiState.lastOpMs = currentMs;
  152. break;
  153. }
  154. }
  155. if (m_appCoreState->uiState.state == HomeHibernate)
  156. {
  157. for (auto& pButton : m_pButtons)
  158. {
  159. if (pButton->wasReleased())
  160. {
  161. setState(HomeLighting);
  162. break;
  163. }
  164. }
  165. }
  166. else
  167. {
  168. if (currentMs - m_appCoreState->uiState.lastOpMs >= HIBERNATE_DELAY)
  169. {
  170. setState(HomeHibernate);
  171. }
  172. else
  173. {
  174. if (m_appCoreState->uiState.state == HomeLighting)
  175. {
  176. if (m_pBtnCancel->wasReleased())
  177. {
  178. setState(HomeHibernate);
  179. }
  180. else if (m_pBtnOk->wasReleased())
  181. {
  182. setState(s_menuSequence[0]);
  183. }
  184. }
  185. else if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuTempTrigger || m_appCoreState->uiState.state == MenuVersion)
  186. {
  187. if (m_pBtnCancel->wasReleased())
  188. {
  189. setState(HomeLighting);
  190. }
  191. else if (m_pBtnOk->wasReleased())
  192. {
  193. UiStateEnum nextState = m_appCoreState->uiState.state;
  194. if (m_appCoreState->uiState.state == MenuWaterSetting)
  195. {
  196. nextState = MenuWaterSettingEdit;
  197. }
  198. else if (m_appCoreState->uiState.state == MenuHeaterSetting)
  199. {
  200. nextState = MenuHeaterSettingEdit;
  201. }
  202. else if (m_appCoreState->uiState.state == MenuTempTrigger)
  203. {
  204. nextState = MenuTempTriggerEdit;
  205. }
  206. setState(nextState);
  207. }
  208. else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased())
  209. {
  210. int idx = 0;
  211. while (m_appCoreState->uiState.state != s_menuSequence[idx])
  212. {
  213. ++idx;
  214. }
  215. auto shift = m_pBtnMinus->wasReleased() ? (-1) : 1;
  216. setState(s_menuSequence[(idx + shift) % ((sizeof(s_menuSequence) / sizeof(*s_menuSequence)))]);
  217. }
  218. }
  219. else if (m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit)
  220. {
  221. if (m_pBtnCancel->wasReleased() || m_pBtnOk->wasReleased())
  222. {
  223. UiStateEnum nextState = m_appCoreState->uiState.state;
  224. if (m_appCoreState->uiState.state == MenuWaterSettingEdit)
  225. {
  226. nextState = MenuWaterSetting;
  227. }
  228. else if (m_appCoreState->uiState.state == MenuHeaterSettingEdit)
  229. {
  230. nextState = MenuHeaterSetting;
  231. }
  232. else if (m_appCoreState->uiState.state == MenuTempTriggerEdit)
  233. {
  234. nextState = MenuTempTrigger;
  235. }
  236. if (m_pBtnOk->wasReleased())
  237. {
  238. *m_appCoreState->appState.pCurrentSettingEdit = m_appCoreState->appState.currentSettingEditTmp;
  239. }
  240. m_appCoreState->appState.currentSettingEditTmp = 0;
  241. m_appCoreState->appState.pCurrentSettingEdit = nullptr;
  242. setState(nextState);
  243. }
  244. else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased())
  245. {
  246. m_appCoreState->appState.currentSettingEditTmp += (m_pBtnMinus->wasReleased() ? -1 : 1) * TEMP_INTERVAL;
  247. m_appCoreState->uiState.isUpdateNeeded = true;
  248. }
  249. }
  250. }
  251. }
  252. if (m_appCoreState->uiState.isUpdateNeeded)
  253. {
  254. printState();
  255. }
  256. LOG_FN_END(50);
  257. }
  258. void AppCore::setState(
  259. UiStateEnum state
  260. )
  261. {
  262. LOG_FN_BEGIN(1);
  263. LOG(5, "Changing state %i => %i", m_appCoreState->uiState.state, state);
  264. if (state == HomeLighting)
  265. {
  266. digitalWrite(PIN_LCD_LED, 1);
  267. }
  268. else if (state == HomeHibernate)
  269. {
  270. digitalWrite(PIN_LCD_LED, 0);
  271. m_storage.save(*m_appCoreState);
  272. }
  273. else if (state == MenuWaterSettingEdit)
  274. {
  275. m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.water.setting;
  276. m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.water.setting;
  277. }
  278. else if (state == MenuHeaterSettingEdit)
  279. {
  280. m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.heater.setting;
  281. m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.heater.setting;
  282. }
  283. else if (state == MenuTempTriggerEdit)
  284. {
  285. m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.tempTrigger;
  286. m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.tempTrigger;
  287. }
  288. m_appCoreState->uiState.state = state;
  289. m_appCoreState->uiState.isUpdateNeeded = true;
  290. LOG_FN_END(1);
  291. }
  292. void AppCore::checkBoilerItem(
  293. BoilerItemState* boilerItemState
  294. )
  295. {
  296. LOG_FN_BEGIN(2);
  297. if (!boilerItemState->isActive && boilerItemState->current != TEMP_T_INVALID &&
  298. boilerItemState->current <= boilerItemState->setting - m_appCoreState->appState.tempTrigger)
  299. {
  300. boilerItemState->isActive = true;
  301. digitalWrite(boilerItemState->pinNo, HIGH);
  302. m_appCoreState->uiState.isUpdateNeeded = true;
  303. }
  304. else if (boilerItemState->isActive &&
  305. (boilerItemState->current == TEMP_T_INVALID || boilerItemState->current >= boilerItemState->setting))
  306. {
  307. boilerItemState->isActive = false;
  308. digitalWrite(boilerItemState->pinNo, LOW);
  309. m_appCoreState->uiState.isUpdateNeeded = true;
  310. }
  311. LOG_FN_END(2);
  312. }
  313. void AppCore::readAndUpdateSensors(
  314. BoilerItemState* boilerItemState
  315. , const uint8_t* sensor
  316. )
  317. {
  318. LOG_FN_BEGIN(2);
  319. auto raw = m_pSensors->getTempC(sensor);
  320. temp_t temp = TEMP_T_INVALID;
  321. if (raw != DEVICE_DISCONNECTED_C)
  322. {
  323. temp = (temp_t) (raw * 10);
  324. }
  325. if (temp != boilerItemState->current)
  326. {
  327. boilerItemState->current = temp;
  328. m_appCoreState->uiState.isUpdateNeeded = true;
  329. }
  330. LOG_FN_END(2);
  331. }
  332. void AppCore::printState()
  333. {
  334. LOG_FN_BEGIN(2);
  335. if (m_appCoreState->uiState.state == HomeLighting || m_appCoreState->uiState.state == HomeHibernate)
  336. {
  337. m_pLcd->setCursor(0, 0);
  338. printStateLine('S', &m_appCoreState->appState.water);
  339. m_pLcd->setCursor(0, 1);
  340. printStateLine('C', &m_appCoreState->appState.heater);
  341. }
  342. else if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuTempTrigger ||
  343. m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit)
  344. {
  345. const auto isEdit = m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit;
  346. const char* pTitle = nullptr;
  347. temp_t* pTemp = nullptr;
  348. if (isEdit)
  349. {
  350. pTemp = &m_appCoreState->appState.currentSettingEditTmp;
  351. }
  352. else
  353. {
  354. if (m_appCoreState->uiState.state == MenuWaterSetting ||
  355. m_appCoreState->uiState.state == MenuWaterSettingEdit)
  356. {
  357. pTitle = "Sanitaire";
  358. pTemp = &m_appCoreState->appState.water.setting;
  359. }
  360. else if (m_appCoreState->uiState.state == MenuHeaterSetting ||
  361. m_appCoreState->uiState.state == MenuHeaterSettingEdit)
  362. {
  363. pTitle = "Chauffage";
  364. pTemp = &m_appCoreState->appState.heater.setting;
  365. }
  366. else if (m_appCoreState->uiState.state == MenuTempTrigger ||
  367. m_appCoreState->uiState.state == MenuTempTriggerEdit)
  368. {
  369. pTitle = "Delta";
  370. pTemp = &m_appCoreState->appState.tempTrigger;
  371. }
  372. }
  373. char tmpStr[17], tmpTemp[7];
  374. m_pLcd->setCursor(0, 0);
  375. snprintf(tmpStr, sizeof(tmpStr), "%s : ", pTitle);
  376. m_pLcd->print(tmpStr);
  377. m_pLcd->setCursor(0, 1);
  378. tempToStr(tmpTemp, *pTemp, 4);
  379. snprintf(tmpStr, sizeof(tmpStr), "%s%s C ", isEdit ? "> " : " ", tmpTemp);
  380. m_pLcd->print(tmpStr);
  381. }
  382. else if (m_appCoreState->uiState.state == MenuVersion)
  383. {
  384. char tmpStr[17];
  385. m_pLcd->setCursor(0, 0);
  386. snprintf(tmpStr, sizeof(tmpStr), "%s : ", "Version");
  387. m_pLcd->print(tmpStr);
  388. m_pLcd->setCursor(0, 1);
  389. snprintf(tmpStr, sizeof(tmpStr), "%s - %s ", xstr(APP_CORE_VERSION), xstr(APP_CORE_COMMIT));
  390. m_pLcd->print(tmpStr);
  391. }
  392. m_appCoreState->uiState.isUpdateNeeded = false;
  393. LOG_FN_END(2);
  394. }
  395. void
  396. AppCore::printStateLine(
  397. char prefix
  398. , const BoilerItemState* boilerItemState
  399. )
  400. {
  401. LOG_FN_BEGIN(2);
  402. char curTmp[7], setTmp[7], tmp[17];
  403. tempToStr(curTmp, boilerItemState->current, 5);
  404. tempToStr(setTmp, boilerItemState->setting, 4);
  405. int count = snprintf(tmp, sizeof(tmp), "%c:%s [%s] %c", prefix, curTmp, setTmp, boilerItemState->isActive ? LCD_CHAR_SENSOR : ' ');
  406. for (; count < 17; ++count)
  407. {
  408. tmp[count] = ' ';
  409. }
  410. tmp[count] = 0;
  411. m_pLcd->print(tmp);
  412. LOG_FN_END(2);
  413. }
  414. void AppCore::tempToStr(
  415. char* out
  416. , temp_t temp
  417. , signed char width
  418. )
  419. {
  420. LOG_FN_BEGIN(2);
  421. LOG(5, "%s: temp=%i", __FUNCTION__, (int)temp);
  422. if (temp == TEMP_T_INVALID)
  423. {
  424. strcpy(out, " --.-");
  425. }
  426. else
  427. {
  428. dtostrf(temp / 10.0f, width, 1, out);
  429. }
  430. LOG_FN_END(2);
  431. }