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 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. #include "AppCore.h"
  2. #include "Logs.h"
  3. #define HIBERNATE_DELAY 5000
  4. #define SENSORS_CHECK_INTERVAL 2000
  5. #define SENSORS_REQUEST_DELAY 750
  6. #define TEMP_TRIGGER ((temp_t)(5 * 10.0f))
  7. #define TEMP_INTERVAL ((temp_t)(0.5 * 10.0f))
  8. #define LCD_CHAR_SENSOR 1
  9. #define PIN_ONEWIRE A0
  10. #define PIN_RELAY1 A1
  11. #define PIN_RELAY2 A2
  12. #define PIN_BTN_CANCEL 9
  13. #define PIN_BTN_OK 10
  14. #define PIN_BTN_MINUS 11
  15. #define PIN_BTN_PLUS 12
  16. #define PIN_LCD_LED 2
  17. #define PIN_LCD_RS 8
  18. #define PIN_LCD_ENABLE 7
  19. #define PIN_LCD_D4 6
  20. #define PIN_LCD_D5 5
  21. #define PIN_LCD_D6 4
  22. #define PIN_LCD_D7 3
  23. #define BUTTONS_COUNT (sizeof(g_buttons) / sizeof(*g_buttons))
  24. #define MODE_SEQUENCE_COUNT (sizeof(m_modeSequence) / sizeof(*m_modeSequence))
  25. AppCore::AppCore()
  26. : m_appCoreState(new AppCoreState{
  27. .appState = {
  28. .lastSensorRequestMs = 0,
  29. .hasReadSensors = true,
  30. .water = {
  31. .current = TEMP_T_INVALID,
  32. .setting = 0,
  33. .isActive = false,
  34. .pinNo = PIN_RELAY1
  35. },
  36. .heater = {
  37. .current = TEMP_T_INVALID,
  38. .setting = 0,
  39. .isActive = false,
  40. .pinNo = PIN_RELAY2
  41. }
  42. },
  43. .uiState = {
  44. .state = Lighting,
  45. .lastOpMs = 0,
  46. .modeSequenceIndex = MODE_SEQUENCE_COUNT - 1,
  47. .isUpdateNeeded = true
  48. }
  49. })
  50. , m_modeSequence{WaterSetting, HeaterSetting, Lighting}
  51. , m_pBtnCancel(new Button{PIN_BTN_CANCEL})
  52. , m_pBtnOk(new Button{PIN_BTN_OK})
  53. , m_pBtnMinus(new Button{PIN_BTN_MINUS})
  54. , m_pBtnPlus(new Button{PIN_BTN_PLUS})
  55. , m_pButtons{m_pBtnOk, m_pBtnMinus, m_pBtnPlus}
  56. , m_pLcd(new LiquidCrystal{PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7})
  57. , m_pOneWire(new OneWire{PIN_ONEWIRE})
  58. , m_pSensors(new DallasTemperature{m_pOneWire})
  59. , m_sensor1{0}
  60. , m_sensor2{0}
  61. {
  62. }
  63. void AppCore::setup()
  64. {
  65. Serial.begin(9600);
  66. LOG_FN_BEGIN(1);
  67. m_pSensors->begin();
  68. LOG(5, "Found %i sensors", m_pSensors->getDeviceCount());
  69. m_pSensors->getAddress(m_sensor1, 0);
  70. m_pSensors->getAddress(m_sensor2, 1);
  71. m_pSensors->setWaitForConversion(false);
  72. for (auto& g_button : m_pButtons)
  73. {
  74. g_button->begin();
  75. }
  76. pinMode(PIN_RELAY1, OUTPUT);
  77. digitalWrite(PIN_RELAY1, LOW);
  78. pinMode(PIN_RELAY2, OUTPUT);
  79. digitalWrite(PIN_RELAY2, LOW);
  80. pinMode(PIN_LCD_LED, OUTPUT);
  81. digitalWrite(PIN_LCD_LED, 1);
  82. m_pLcd->begin(16, 2);
  83. byte sensorChar[8] = {
  84. B00100,
  85. B01110,
  86. B01110,
  87. B01110,
  88. B01110,
  89. B11111,
  90. B11111,
  91. B01110,
  92. };
  93. m_pLcd->createChar(LCD_CHAR_SENSOR, sensorChar);
  94. bool allButtonsPressed = true;
  95. for (auto& g_button : m_pButtons)
  96. {
  97. g_button->read();
  98. allButtonsPressed = allButtonsPressed && g_button->isPressed();
  99. }
  100. if (!allButtonsPressed)
  101. {
  102. LOG(5, "%s: Loading settings", __FUNCTION__);
  103. m_storage.load(*m_appCoreState);
  104. }
  105. else
  106. {
  107. LOG(5, "%s: Resetting settings", __FUNCTION__);
  108. m_storage.save(*m_appCoreState);
  109. m_pLcd->clear();
  110. m_pLcd->setCursor(6, 0);
  111. m_pLcd->print("Reset");
  112. bool allButtonsPressed = true;
  113. while (allButtonsPressed)
  114. {
  115. for (auto& g_button : m_pButtons)
  116. {
  117. g_button->read();
  118. allButtonsPressed = allButtonsPressed && g_button->isPressed();
  119. }
  120. }
  121. }
  122. LOG_FN_END(1);
  123. }
  124. void AppCore::loop()
  125. {
  126. LOG_FN_BEGIN(50);
  127. const auto& currentMs = millis();
  128. if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL)
  129. {
  130. m_appCoreState->appState.lastSensorRequestMs = currentMs;
  131. m_appCoreState->appState.hasReadSensors = false;
  132. m_pSensors->requestTemperaturesByAddress(m_sensor1);
  133. m_pSensors->requestTemperaturesByAddress(m_sensor2);
  134. }
  135. if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY &&
  136. !m_appCoreState->appState.hasReadSensors)
  137. {
  138. m_appCoreState->appState.hasReadSensors = true;
  139. readAndUpdateSensors(&m_appCoreState->appState.water, m_sensor1);
  140. readAndUpdateSensors(&m_appCoreState->appState.heater, m_sensor2);
  141. checkBoilerItem(&m_appCoreState->appState.water);
  142. checkBoilerItem(&m_appCoreState->appState.heater);
  143. }
  144. for (auto& pButton : m_pButtons)
  145. {
  146. pButton->read();
  147. if (pButton->isPressed())
  148. {
  149. m_appCoreState->uiState.lastOpMs = currentMs;
  150. break;
  151. }
  152. }
  153. if (m_appCoreState->uiState.state == Hibernate)
  154. {
  155. for (auto& pButton : m_pButtons)
  156. {
  157. if (pButton->wasReleased())
  158. {
  159. setState(Lighting);
  160. break;
  161. }
  162. }
  163. }
  164. else
  165. {
  166. if (currentMs - m_appCoreState->uiState.lastOpMs >= HIBERNATE_DELAY)
  167. {
  168. setState(Hibernate);
  169. }
  170. else
  171. {
  172. if (m_pBtnOk->wasReleased())
  173. {
  174. m_appCoreState->uiState.modeSequenceIndex =
  175. (m_appCoreState->uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT;
  176. setState(m_modeSequence[m_appCoreState->uiState.modeSequenceIndex]);
  177. }
  178. else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased())
  179. {
  180. BoilerItemState* itemState = nullptr;
  181. if (m_appCoreState->uiState.state == WaterSetting)
  182. {
  183. itemState = &m_appCoreState->appState.water;
  184. }
  185. else if (m_appCoreState->uiState.state == HeaterSetting)
  186. {
  187. itemState = &m_appCoreState->appState.heater;
  188. }
  189. if (itemState)
  190. {
  191. if (m_pBtnMinus->wasReleased())
  192. {
  193. itemState->setting -= TEMP_INTERVAL;
  194. }
  195. else if (m_pBtnPlus->wasReleased())
  196. {
  197. itemState->setting += TEMP_INTERVAL;
  198. }
  199. LOG(1, "Setting temp to %i (%i)", itemState->setting, TEMP_INTERVAL);
  200. m_appCoreState->uiState.isUpdateNeeded = true;
  201. }
  202. }
  203. }
  204. }
  205. if (m_appCoreState->uiState.isUpdateNeeded)
  206. {
  207. printState();
  208. }
  209. LOG_FN_END(50);
  210. }
  211. void AppCore::setState(
  212. UiStateEnum state
  213. )
  214. {
  215. LOG_FN_BEGIN(1);
  216. LOG(5, "Changing state %i => %i", m_appCoreState->uiState.state, state);
  217. if (state == Lighting)
  218. {
  219. digitalWrite(PIN_LCD_LED, 1);
  220. }
  221. else if (state == Hibernate)
  222. {
  223. digitalWrite(PIN_LCD_LED, 0);
  224. m_appCoreState->uiState.modeSequenceIndex = MODE_SEQUENCE_COUNT - 1;
  225. m_storage.save(*m_appCoreState);
  226. }
  227. m_appCoreState->uiState.state = state;
  228. m_appCoreState->uiState.isUpdateNeeded = true;
  229. LOG_FN_END(1);
  230. }
  231. void AppCore::checkBoilerItem(
  232. BoilerItemState* boilerItemState
  233. )
  234. {
  235. LOG_FN_BEGIN(2);
  236. if (!boilerItemState->isActive && boilerItemState->current != TEMP_T_INVALID &&
  237. boilerItemState->current <= boilerItemState->setting - TEMP_TRIGGER)
  238. {
  239. boilerItemState->isActive = true;
  240. digitalWrite(boilerItemState->pinNo, HIGH);
  241. m_appCoreState->uiState.isUpdateNeeded = true;
  242. }
  243. else if (boilerItemState->isActive &&
  244. (boilerItemState->current == TEMP_T_INVALID || boilerItemState->current >= boilerItemState->setting))
  245. {
  246. boilerItemState->isActive = false;
  247. digitalWrite(boilerItemState->pinNo, LOW);
  248. m_appCoreState->uiState.isUpdateNeeded = true;
  249. }
  250. LOG_FN_END(2);
  251. }
  252. void AppCore::readAndUpdateSensors(
  253. BoilerItemState* boilerItemState
  254. , const uint8_t* sensor
  255. )
  256. {
  257. LOG_FN_BEGIN(2);
  258. auto raw = m_pSensors->getTempC(sensor);
  259. temp_t temp = TEMP_T_INVALID;
  260. if (raw != DEVICE_DISCONNECTED_C)
  261. {
  262. temp = (temp_t) (raw * 10);
  263. }
  264. if (temp != boilerItemState->current)
  265. {
  266. boilerItemState->current = temp;
  267. m_appCoreState->uiState.isUpdateNeeded = true;
  268. }
  269. LOG_FN_END(2);
  270. }
  271. void AppCore::printState()
  272. {
  273. LOG_FN_BEGIN(2);
  274. m_pLcd->setCursor(0, 0);
  275. printStateLine('S', &m_appCoreState->appState.water, m_appCoreState->uiState.state == WaterSetting,
  276. m_appCoreState->appState.water.isActive);
  277. m_pLcd->setCursor(0, 1);
  278. printStateLine('C', &m_appCoreState->appState.heater, m_appCoreState->uiState.state == HeaterSetting,
  279. m_appCoreState->appState.heater.isActive);
  280. m_appCoreState->uiState.isUpdateNeeded = false;
  281. LOG_FN_END(2);
  282. }
  283. void
  284. AppCore::printStateLine(
  285. char prefix
  286. , const BoilerItemState* boilerItemState
  287. , bool isModifying
  288. , bool isActive
  289. )
  290. {
  291. LOG_FN_BEGIN(2);
  292. char curTmp[7], setTmp[7], tmp[17];
  293. tempToStr(curTmp, boilerItemState->current, 5);
  294. tempToStr(setTmp, boilerItemState->setting, 4);
  295. int count = snprintf(tmp, sizeof(tmp), "%c:%s [%s]%c%c", prefix, curTmp, setTmp, isModifying ? '<' : ' ',
  296. isActive ? LCD_CHAR_SENSOR : ' ');
  297. for (; count < 17; ++count)
  298. {
  299. tmp[count] = ' ';
  300. }
  301. tmp[count] = 0;
  302. m_pLcd->print(tmp);
  303. LOG_FN_END(2);
  304. }
  305. void AppCore::tempToStr(
  306. char* out
  307. , temp_t temp
  308. , signed char width
  309. )
  310. {
  311. LOG_FN_BEGIN(2);
  312. LOG(5, "%s: temp=%i", __FUNCTION__, (int)temp);
  313. if (temp == TEMP_T_INVALID)
  314. {
  315. strcpy(out, " --.-");
  316. }
  317. else
  318. {
  319. dtostrf(temp / 10.0f, width, 1, out);
  320. }
  321. LOG_FN_END(2);
  322. }