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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #include <LiquidCrystal.h>
  2. #include <JC_Button.h>
  3. #include <OneWire.h>
  4. #include <DallasTemperature.h>
  5. #include "Boiler.h"
  6. #include "Storage.h"
  7. #include "AppCore.h"
  8. #define HIBERNATE_DELAY 5000
  9. #define SENSORS_CHECK_INTERVAL 2000
  10. #define SENSORS_REQUEST_DELAY 750
  11. #define TEMP_TRIGGER 10
  12. #define LCD_CHAR_SENSOR 1
  13. #define PIN_ONEWIRE 12
  14. #define PIN_BTN_MODE 11
  15. #define PIN_BTN_MINUS 10
  16. #define PIN_BTN_PLUS 9
  17. #define PIN_LCD_LED 8
  18. #define PIN_LCD_RS 7
  19. #define PIN_LCD_ENABLE 6
  20. #define PIN_LCD_D0 5
  21. #define PIN_LCD_D1 4
  22. #define PIN_LCD_D2 3
  23. #define PIN_LCD_D3 2
  24. byte g_sensorChar[8] = {
  25. B00100,
  26. B01110,
  27. B01110,
  28. B01110,
  29. B01110,
  30. B11111,
  31. B11111,
  32. B01110,
  33. };
  34. Storage g_storage;
  35. OneWire g_oneWire(PIN_ONEWIRE);
  36. DallasTemperature g_sensors(&g_oneWire);
  37. DeviceAddress g_sensor1;
  38. Button g_btnMode(PIN_BTN_MODE);
  39. Button g_btnMinus(PIN_BTN_MINUS);
  40. Button g_btnPlus(PIN_BTN_PLUS);
  41. Button* g_buttons[3] = {&g_btnMode, &g_btnMinus, &g_btnPlus};
  42. #define BUTTONS_COUNT (sizeof(g_buttons) / sizeof(*g_buttons))
  43. LiquidCrystal g_lcd(PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
  44. UiStateEnum g_modeSequence[3] = {WaterSetting, HeaterSetting, Lighting};
  45. #define MODE_SEQUENCE_COUNT (sizeof(g_modeSequence) / sizeof(*g_modeSequence))
  46. AppCoreState g_appCoreState = {
  47. .appState = {
  48. .lastSensorRequestMs = 0,
  49. .hasReadSensors = true,
  50. .water = {
  51. .current = TEMP_T_INVALID,
  52. .setting = 0,
  53. .isActive = false
  54. },
  55. .heater = {
  56. .current = TEMP_T_INVALID,
  57. .setting = 0,
  58. .isActive = false
  59. }
  60. },
  61. .uiState = {
  62. .state = Lighting,
  63. .lastOpMs = 0,
  64. .modeSequenceIndex = MODE_SEQUENCE_COUNT - 1,
  65. .isUpdateNeeded = true
  66. }
  67. };
  68. void tmpToStr(char* out, temp_t temp)
  69. {
  70. if (temp == TEMP_T_INVALID)
  71. {
  72. strcpy(out, " -- ");
  73. }
  74. else
  75. {
  76. char tmp[7];
  77. dtostrf(temp / 2.0f, 2, 1, tmp);
  78. auto len = 4 - strlen(tmp);
  79. for (auto i = 0; i < len; ++i)
  80. {
  81. out[i] = ' ';
  82. }
  83. strcpy(&out[len], tmp);
  84. }
  85. }
  86. void printStateLine(char prefix, const BoilerItemState& boilerItemState, bool isModifying, bool isActive, LiquidCrystal lcd)
  87. {
  88. char curTmp[7], setTmp[7], tmp[17];
  89. tmpToStr(curTmp, boilerItemState.current);
  90. tmpToStr(setTmp, boilerItemState.setting);
  91. int count = snprintf(tmp, sizeof(tmp), "%c: %s [%s]%c%c", prefix, curTmp, setTmp, isModifying ? '<' : ' ', isActive ? LCD_CHAR_SENSOR : ' ');
  92. for (; count < 17; ++count)
  93. {
  94. tmp[count] = ' ';
  95. }
  96. tmp[count] = 0;
  97. lcd.print(tmp);
  98. }
  99. void printState(const AppCoreState& appCoreState, LiquidCrystal lcd)
  100. {
  101. Serial.println("Updating display");
  102. lcd.setCursor(0, 0);
  103. printStateLine('S', appCoreState.appState.water, appCoreState.uiState.state == WaterSetting, appCoreState.appState.water.isActive, lcd);
  104. lcd.setCursor(0, 1);
  105. printStateLine('C', appCoreState.appState.heater, appCoreState.uiState.state == HeaterSetting, appCoreState.appState.heater.isActive, lcd);
  106. }
  107. void setState(AppCoreState& boilerState, UiStateEnum state)
  108. {
  109. char tmp[50];
  110. snprintf(tmp, sizeof(tmp), "Changing state %i => %i", g_appCoreState.uiState.state, state);
  111. Serial.println(tmp);
  112. if (state == Lighting)
  113. {
  114. digitalWrite(PIN_LCD_LED, 1);
  115. }
  116. else if (state == Hibernate)
  117. {
  118. digitalWrite(PIN_LCD_LED, 0);
  119. g_appCoreState.uiState.modeSequenceIndex = MODE_SEQUENCE_COUNT - 1;
  120. }
  121. g_appCoreState.uiState.state = state;
  122. g_appCoreState.uiState.isUpdateNeeded = true;
  123. }
  124. void checkBoilerItem(BoilerItemState& boilerItemState, UiState uiState)
  125. {
  126. if (!boilerItemState.isActive && boilerItemState.current != TEMP_T_INVALID && boilerItemState.current <= boilerItemState.setting - TEMP_TRIGGER)
  127. {
  128. boilerItemState.isActive = true;
  129. uiState.isUpdateNeeded = true;
  130. }
  131. else if (boilerItemState.isActive && (boilerItemState.current == TEMP_T_INVALID || boilerItemState.current >= boilerItemState.setting))
  132. {
  133. boilerItemState.isActive = false;
  134. uiState.isUpdateNeeded = true;
  135. }
  136. }
  137. void AppCore::setup()
  138. {
  139. g_sensors.begin();
  140. g_sensors.getAddress(g_sensor1, 0);
  141. g_sensors.setWaitForConversion(false);
  142. for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
  143. {
  144. g_buttons[i]->begin();
  145. }
  146. pinMode(PIN_LCD_LED, OUTPUT);
  147. digitalWrite(PIN_LCD_LED, 1);
  148. g_lcd.begin(16, 2);
  149. g_lcd.createChar(LCD_CHAR_SENSOR, g_sensorChar);
  150. Serial.begin(9600);
  151. bool allButtonsPressed = true;
  152. for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
  153. {
  154. allButtonsPressed = allButtonsPressed && g_buttons[i]->isPressed();
  155. }
  156. if (!allButtonsPressed)
  157. {
  158. g_storage.load(g_appCoreState);
  159. }
  160. else
  161. {
  162. g_storage.save(g_appCoreState);
  163. }
  164. }
  165. void AppCore::loop()
  166. {
  167. const auto& currentMs = millis();
  168. if (currentMs - g_appCoreState.appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL)
  169. {
  170. g_appCoreState.appState.lastSensorRequestMs = currentMs;
  171. g_appCoreState.appState.hasReadSensors = false;
  172. g_sensors.requestTemperaturesByAddress(g_sensor1);
  173. }
  174. if (currentMs - g_appCoreState.appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY && !g_appCoreState.appState.hasReadSensors)
  175. {
  176. g_appCoreState.appState.hasReadSensors = true;
  177. auto raw = g_sensors.getTempC(g_sensor1);
  178. temp_t temp = TEMP_T_INVALID;
  179. if (raw != DEVICE_DISCONNECTED_C)
  180. {
  181. temp = (temp_t) (raw * 2);
  182. }
  183. if (temp != g_appCoreState.appState.water.current)
  184. {
  185. g_appCoreState.appState.water.current = temp;
  186. g_appCoreState.uiState.isUpdateNeeded = true;
  187. }
  188. }
  189. for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
  190. {
  191. g_buttons[i]->read();
  192. if (g_buttons[i]->isPressed())
  193. {
  194. g_appCoreState.uiState.lastOpMs = currentMs;
  195. break;
  196. }
  197. }
  198. if (g_appCoreState.uiState.state == Hibernate)
  199. {
  200. for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
  201. {
  202. if (g_buttons[i]->wasReleased())
  203. {
  204. setState(g_appCoreState, Lighting);
  205. break;
  206. }
  207. }
  208. }
  209. else
  210. {
  211. if (currentMs - g_appCoreState.uiState.lastOpMs >= HIBERNATE_DELAY)
  212. {
  213. setState(g_appCoreState, Hibernate);
  214. }
  215. else
  216. {
  217. if (g_btnMode.wasReleased())
  218. {
  219. g_appCoreState.uiState.modeSequenceIndex = (g_appCoreState.uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT;
  220. setState(g_appCoreState, g_modeSequence[g_appCoreState.uiState.modeSequenceIndex]);
  221. }
  222. else if (g_btnMinus.wasReleased() || g_btnPlus.wasReleased())
  223. {
  224. BoilerItemState* itemState = NULL;
  225. if (g_appCoreState.uiState.state == WaterSetting)
  226. {
  227. itemState = &g_appCoreState.appState.water;
  228. }
  229. else if (g_appCoreState.uiState.state == HeaterSetting)
  230. {
  231. itemState = &g_appCoreState.appState.heater;
  232. }
  233. if (itemState)
  234. {
  235. if (g_btnMinus.wasReleased())
  236. {
  237. --itemState->setting;
  238. }
  239. else if (g_btnPlus.wasReleased())
  240. {
  241. ++itemState->setting;
  242. }
  243. g_appCoreState.uiState.isUpdateNeeded = true;
  244. }
  245. }
  246. }
  247. }
  248. checkBoilerItem(g_appCoreState.appState.water, g_appCoreState.uiState);
  249. checkBoilerItem(g_appCoreState.appState.heater, g_appCoreState.uiState);
  250. if (g_appCoreState.uiState.isUpdateNeeded)
  251. {
  252. printState(g_appCoreState, g_lcd);
  253. g_appCoreState.uiState.isUpdateNeeded = false;
  254. }
  255. }