|
1 /* |
|
2 * co2meter project. |
|
3 */ |
|
4 |
|
5 #include "config.h" |
|
6 |
|
7 static const char *TAG = "co2meter"; |
|
8 |
|
9 #define PIN_SDA (CONFIG_I2C_MASTER_SDA) |
|
10 #define PIN_SCL (CONFIG_I2C_MASTER_SCL) |
|
11 #define ROT_ENC_A_GPIO (CONFIG_ROT_ENC_A_GPIO) |
|
12 #define ROT_ENC_B_GPIO (CONFIG_ROT_ENC_B_GPIO) |
|
13 #define ROT_ENC_SW_GPIO (CONFIG_ROT_ENC_SW_GPIO) |
|
14 |
|
15 |
|
16 #define ENABLE_HALF_STEPS false ///< Set to true to enable tracking of rotary encoder at half step resolution |
|
17 #define RESET_AT 0 ///< Set to a positive non-zero number to reset the position if this value is exceeded |
|
18 #define FLIP_DIRECTION false ///< Set to true to reverse the clockwise/counterclockwise sense |
|
19 |
|
20 |
|
21 int Main_Loop1 = MAIN_LOOP1_INIT; ///< Loop 1 init |
|
22 int Main_Loop2 = -1; ///< Loop 2 invalid |
|
23 bool System_TimeOk = false; ///< System time status |
|
24 time_t now; ///< Current time |
|
25 struct tm timeinfo; ///< Current time structure |
|
26 char strftime_buf[64]; ///< Time buffer |
|
27 static RTC_DATA_ATTR struct timeval sleep_enter_time; |
|
28 static TaskHandle_t xTaskDS18B20 = NULL; |
|
29 static TaskHandle_t xTaskADC = NULL; |
|
30 static TaskHandle_t xTaskWifi = NULL; |
|
31 static TaskHandle_t xTaskMQTT = NULL; |
|
32 const esp_app_desc_t *app_desc = NULL; |
|
33 unit_t units[3]; ///< Pressure test units |
|
34 |
|
35 |
|
36 extern DS18B20_State *ds18b20_state; ///< DS18B20 state |
|
37 extern SemaphoreHandle_t xSemaphoreDS18B20; ///< DS18B20 lock semaphore |
|
38 extern ADC_State *adc_state; ///< ADC state |
|
39 extern SemaphoreHandle_t xSemaphoreADC; ///< ADC lock semaphore |
|
40 |
|
41 |
|
42 void app_main() |
|
43 { |
|
44 struct timeval now; |
|
45 gettimeofday(&now, NULL); |
|
46 int sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000; |
|
47 int New_Loop2 = MAIN_LOOP2_INIT; |
|
48 esp_err_t ret; |
|
49 |
|
50 Main_Loop1 = MAIN_LOOP1_INIT; |
|
51 Main_Loop2 = -1; |
|
52 |
|
53 switch (esp_sleep_get_wakeup_cause()) { |
|
54 case ESP_SLEEP_WAKEUP_EXT1: { |
|
55 uint64_t wakeup_pin_mask = esp_sleep_get_ext1_wakeup_status(); |
|
56 if (wakeup_pin_mask != 0) { |
|
57 int pin = __builtin_ffsll(wakeup_pin_mask) - 1; |
|
58 printf("Wake up from GPIO %d\n", pin); |
|
59 } else { |
|
60 printf("Wake up from GPIO\n"); |
|
61 } |
|
62 break; |
|
63 } |
|
64 case ESP_SLEEP_WAKEUP_TIMER: { |
|
65 ESP_LOGI(TAG, "Starting from deep sleep, timer wakeup after %dms", sleep_time_ms); |
|
66 break; |
|
67 } |
|
68 case ESP_SLEEP_WAKEUP_UNDEFINED: |
|
69 default: |
|
70 ESP_LOGI(TAG, "Starting from hard reset"); |
|
71 } |
|
72 |
|
73 const int wakeup_time_sec = 20; |
|
74 ESP_LOGI(TAG, "Enabling timer wakeup, %ds", wakeup_time_sec); |
|
75 esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000); |
|
76 |
|
77 // const int ext_wakeup_pin_1 = ROT_ENC_SW_GPIO; // 25 in example, redefine to rotary name. |
|
78 // const uint64_t ext_wakeup_pin_1_mask = 1ULL << ext_wakeup_pin_1; |
|
79 |
|
80 // printf("Enabling EXT1 wakeup on pins GPIO%d\n", ext_wakeup_pin_1); |
|
81 // esp_sleep_enable_ext1_wakeup(ext_wakeup_pin_1_mask, ESP_EXT1_WAKEUP_ANY_HIGH); // TODO: what is the logic of the rotary button. |
|
82 |
|
83 // Isolate GPIO12 pin from external circuits. This is needed for modules |
|
84 // which have an external pull-up resistor on GPIO12 (such as ESP32-WROVER) |
|
85 // to minimize current consumption. |
|
86 // rtc_gpio_isolate(GPIO_NUM_12); |
|
87 |
|
88 app_desc = esp_ota_get_app_description(); |
|
89 |
|
90 /* |
|
91 * Initialize NVS |
|
92 */ |
|
93 ret = nvs_flash_init(); |
|
94 if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { |
|
95 ESP_ERROR_CHECK(nvs_flash_erase()); |
|
96 ret = nvs_flash_init(); |
|
97 } |
|
98 ESP_ERROR_CHECK(ret); |
|
99 |
|
100 /* |
|
101 * Setup the OLED display. |
|
102 * See: https://github.com/nkolban/esp32-snippets/blob/master/hardware/displays/U8G2/ |
|
103 */ |
|
104 // u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT; |
|
105 // u8g2_esp32_hal.sda = PIN_SDA; |
|
106 // u8g2_esp32_hal.scl = PIN_SCL; |
|
107 // u8g2_esp32_hal_init(u8g2_esp32_hal); |
|
108 |
|
109 // u8g2_t u8g2; // a structure which will contain all the data for one display |
|
110 //// u8g2_Setup_ssd1306_i2c_128x32_univision_f( |
|
111 // u8g2_Setup_sh1106_i2c_128x64_noname_f( |
|
112 // &u8g2, |
|
113 // U8G2_R0, |
|
114 // //u8x8_byte_sw_i2c, |
|
115 // u8g2_esp32_i2c_byte_cb, |
|
116 // u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure |
|
117 // u8x8_SetI2CAddress(&u8g2.u8x8,0x78); |
|
118 |
|
119 // ESP_LOGI(TAG, "u8g2_InitDisplay"); |
|
120 // u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this, |
|
121 |
|
122 // ESP_LOGI(TAG, "u8g2_SetPowerSave"); |
|
123 // u8g2_SetPowerSave(&u8g2, 0); // wake up display |
|
124 // ESP_LOGI(TAG, "u8g2_ClearBuffer"); |
|
125 // u8g2_ClearBuffer(&u8g2); |
|
126 // ESP_LOGI(TAG, "u8g2_DrawBox"); |
|
127 // u8g2_DrawBox(&u8g2, 0, 26, 80,6); |
|
128 // u8g2_DrawFrame(&u8g2, 0,26,100,6); |
|
129 |
|
130 // ESP_LOGI(TAG, "u8g2_SetFont"); |
|
131 // u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr); |
|
132 // ESP_LOGI(TAG, "u8g2_DrawStr"); |
|
133 // u8g2_DrawStr(&u8g2, 2,17,"Hi nkolban!"); |
|
134 // ESP_LOGI(TAG, "u8g2_SendBuffer"); |
|
135 // u8g2_SendBuffer(&u8g2); |
|
136 |
|
137 |
|
138 ESP_LOGI(TAG, "Initializing SPIFFS"); |
|
139 |
|
140 esp_vfs_spiffs_conf_t conf = { |
|
141 .base_path = "/spiffs", |
|
142 .partition_label = NULL, |
|
143 .max_files = 5, |
|
144 .format_if_mount_failed = true |
|
145 }; |
|
146 |
|
147 // Use settings defined above to initialize and mount SPIFFS filesystem. |
|
148 // Note: esp_vfs_spiffs_register is an all-in-one convenience function. |
|
149 ret = esp_vfs_spiffs_register(&conf); |
|
150 |
|
151 if (ret != ESP_OK) { |
|
152 if (ret == ESP_FAIL) { |
|
153 ESP_LOGE(TAG, "Failed to mount or format filesystem"); |
|
154 } else if (ret == ESP_ERR_NOT_FOUND) { |
|
155 ESP_LOGE(TAG, "Failed to find SPIFFS partition"); |
|
156 } else { |
|
157 ESP_LOGE(TAG, "Failed to initialize SPIFFS (%d)", ret); |
|
158 } |
|
159 // _fg = TFT_RED; |
|
160 // TFT_print((char *)"error\r\n", LASTX, LASTY); |
|
161 return; // Stop application. |
|
162 } |
|
163 |
|
164 size_t total = 0, used = 0; |
|
165 ret = esp_spiffs_info(NULL, &total, &used); |
|
166 if (ret != ESP_OK) { |
|
167 ESP_LOGE(TAG, "Failed to get SPIFFS partition information"); |
|
168 // _fg = TFT_RED; |
|
169 // TFT_print((char *)"error\r\n", LASTX, LASTY); |
|
170 return; // Stop application. |
|
171 } else { |
|
172 ESP_LOGI(TAG, "Partition size: %d, used: %d - %d%%", total, used, (used * 100) / total); |
|
173 } |
|
174 |
|
175 // Just to debug, list the /spiffs filesystem. |
|
176 #if 1 |
|
177 DIR *dir = opendir("/spiffs"); |
|
178 struct dirent* de = readdir(dir); |
|
179 while (de) { |
|
180 if (de->d_type == DT_REG) { |
|
181 printf("F "); |
|
182 } |
|
183 if (de->d_type == DT_DIR) { |
|
184 printf("D "); |
|
185 } |
|
186 printf("%s\n", de->d_name); |
|
187 de = readdir(dir); |
|
188 } |
|
189 closedir(dir); |
|
190 #endif |
|
191 |
|
192 /* |
|
193 * Read or create configuration |
|
194 */ |
|
195 // TFT_print((char *)"Ophalen configuratie ", LASTX, LASTY); |
|
196 read_config(); |
|
197 read_units(); |
|
198 |
|
199 //add_station((uint8_t *)"MBSE_WLR", (uint8_t *)"abcjkltuv"); |
|
200 //remove_station((uint8_t *)"MBSE_WLP"); |
|
201 //sprintf(config.lastSSID, "%s", "BREWER"); |
|
202 //write_config(); |
|
203 |
|
204 xSemaphoreDS18B20 = xSemaphoreCreateMutex(); |
|
205 xSemaphoreADC = xSemaphoreCreateMutex(); |
|
206 |
|
207 xTaskCreate(&task_ds18b20, "task_ds18b20", 2560, NULL, 8, &xTaskDS18B20); |
|
208 xTaskCreate(&task_adc, "task_adc", 2560, NULL, 8, &xTaskADC); |
|
209 esp_log_level_set("wifi", ESP_LOG_ERROR); |
|
210 xTaskCreate(&task_wifi, "task_wifi", 4096, NULL, 3, &xTaskWifi); |
|
211 vTaskDelay( (TickType_t)10); |
|
212 xTaskCreate(&task_mqtt, "task_mqtt", 4096, NULL, 5, &xTaskMQTT); |
|
213 |
|
214 // esp32-rotary-encoder requires that the GPIO ISR service is installed before calling rotary_encoder_register() |
|
215 // ESP_ERROR_CHECK(gpio_install_isr_service(0)); |
|
216 |
|
217 // Initialise the rotary encoder device with the GPIOs for A and B signals |
|
218 // rotary_encoder_info_t info = { 0 }; |
|
219 // ESP_ERROR_CHECK(rotary_encoder_init(&info, ROT_ENC_A_GPIO, ROT_ENC_B_GPIO)); |
|
220 // ESP_ERROR_CHECK(rotary_encoder_enable_half_steps(&info, ENABLE_HALF_STEPS)); |
|
221 #ifdef FLIP_DIRECTION |
|
222 // ESP_ERROR_CHECK(rotary_encoder_flip_direction(&info)); |
|
223 #endif |
|
224 |
|
225 // Create a queue for events from the rotary encoder driver. |
|
226 // Tasks can read from this queue to receive up to date position information. |
|
227 // QueueHandle_t event_queue = rotary_encoder_create_queue(); |
|
228 // ESP_ERROR_CHECK(rotary_encoder_set_queue(&info, event_queue)); |
|
229 |
|
230 |
|
231 /* Print chip information */ |
|
232 // esp_chip_info_t chip_info; |
|
233 // esp_chip_info(&chip_info); |
|
234 // printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ", |
|
235 // chip_info.cores, |
|
236 // (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", |
|
237 // (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); |
|
238 |
|
239 // printf("silicon revision %d, ", chip_info.revision); |
|
240 |
|
241 // printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024), |
|
242 // (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); |
|
243 |
|
244 // esp_err_t status = adc2_vref_to_gpio(GPIO_NUM_26); |
|
245 // if (status == ESP_OK) { |
|
246 // printf("v_ref routed to GPIO\n"); |
|
247 // } else { |
|
248 // printf("failed to route v_ref\n"); |
|
249 // } |
|
250 // vTaskDelay(1000 * wakeup_time_sec / portTICK_PERIOD_MS); |
|
251 |
|
252 /* |
|
253 * Main application loop. |
|
254 */ |
|
255 while (1) { |
|
256 |
|
257 ESP_LOGI(TAG, "Entered app loop"); |
|
258 |
|
259 /* Measure process or user input via rotary switch */ |
|
260 while (1) { |
|
261 switch (Main_Loop1) { |
|
262 case MAIN_LOOP1_INIT: |
|
263 ESP_LOGI(TAG, "Loop timer: Init"); |
|
264 // If configured do MAIN_LOOP1_CONNECT |
|
265 Main_Loop1 = MAIN_LOOP1_CONNECT; |
|
266 requestWiFi_system(true); |
|
267 request_ds18b20(); |
|
268 request_adc(); |
|
269 break; |
|
270 |
|
271 case MAIN_LOOP1_CONNECT: |
|
272 if (ready_WiFi()) |
|
273 Main_Loop1 = MAIN_LOOP1_MQTT_CONNECT; |
|
274 break; |
|
275 |
|
276 case MAIN_LOOP1_MQTT_CONNECT: |
|
277 if (ready_ds18b20() && ready_adc()) { |
|
278 connect_mqtt(true); |
|
279 Main_Loop1 = MAIN_LOOP1_WAITCON; |
|
280 ESP_LOGI(TAG, "Loop timer: Wait MQTT"); |
|
281 |
|
282 /* Get global temperature, use for all units. */ |
|
283 uint32_t temp = 0; |
|
284 int state = 0; |
|
285 if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { |
|
286 temp = (ds18b20_state->bottle_temperature * 1000); |
|
287 state = (ds18b20_state->bottle_error == 0) ? 0:1; |
|
288 xSemaphoreGive(xSemaphoreDS18B20); |
|
289 } |
|
290 |
|
291 /* Copy measured data and calculate results */ |
|
292 for (int i = 0; i < 3; i++) { |
|
293 units[i].temperature = temp; |
|
294 units[i].temperature_state = state; |
|
295 if (xSemaphoreTake(xSemaphoreADC, 10) == pdTRUE) { |
|
296 units[i].pressure_state = adc_state->Pressure[i].error; |
|
297 units[i].pressure_channel = adc_state->Pressure[i].channel; |
|
298 units[i].pressure_voltage = adc_state->Pressure[i].voltage; |
|
299 units[i].pressure_zero = 110; |
|
300 int P = (units[i].pressure_voltage / (adc_state->Batt_voltage / 1000) - units[i].pressure_zero) * 14; // in bar |
|
301 if (P < 0) |
|
302 P = 0; |
|
303 units[i].pressure = P; |
|
304 printf("%d volt: %d batt: %d scale: %d bar: %d\n", i, units[i].pressure_voltage, adc_state->Batt_voltage, |
|
305 units[i].pressure_voltage / (adc_state->Batt_voltage / 1000) - units[i].pressure_zero, P); |
|
306 // Moet die echt op 5 volt? |
|
307 // Verbruik 10 mA |
|
308 // Setup tijd max 2 mS |
|
309 xSemaphoreGive(xSemaphoreADC); |
|
310 } |
|
311 } |
|
312 write_units(); |
|
313 } |
|
314 break; |
|
315 |
|
316 // calculate stap en data copy |
|
317 |
|
318 case MAIN_LOOP1_WAITCON: |
|
319 if (ready_mqtt()) |
|
320 Main_Loop1 = MAIN_LOOP1_SEND; |
|
321 break; |
|
322 |
|
323 case MAIN_LOOP1_SEND: |
|
324 ESP_LOGI(TAG, "Loop timer: Send MQTT"); |
|
325 publishNode(); |
|
326 publishUnits(); |
|
327 |
|
328 Main_Loop1 = MAIN_LOOP1_MQTT_DISCONNECT; |
|
329 break; |
|
330 |
|
331 case MAIN_LOOP1_WAITACK: |
|
332 break; |
|
333 |
|
334 case MAIN_LOOP1_MQTT_DISCONNECT: |
|
335 ESP_LOGI(TAG, "Loop timer: Disconnect MQTT"); |
|
336 connect_mqtt(false); |
|
337 Main_Loop1 = MAIN_LOOP1_DISCONNECT; |
|
338 break; |
|
339 |
|
340 case MAIN_LOOP1_DISCONNECT: |
|
341 if (! ready_mqtt()) { |
|
342 ESP_LOGI(TAG, "Loop timer: WiFi off"); |
|
343 requestWiFi_system(false); |
|
344 Main_Loop1 = MAIN_LOOP1_WIFI_OFF; |
|
345 } |
|
346 break; |
|
347 |
|
348 case MAIN_LOOP1_WIFI_OFF: |
|
349 if (! ready_WiFi()) { |
|
350 ESP_LOGI(TAG, "Loop timer: Done"); |
|
351 Main_Loop1 = MAIN_LOOP1_DONE; |
|
352 } |
|
353 break; |
|
354 |
|
355 case MAIN_LOOP1_DONE: |
|
356 break; |
|
357 } |
|
358 |
|
359 /* |
|
360 * One time actions |
|
361 */ |
|
362 if (New_Loop2 != Main_Loop2) { |
|
363 |
|
364 Main_Loop2 = New_Loop2; |
|
365 |
|
366 switch (Main_Loop2) { |
|
367 case MAIN_LOOP2_INIT: |
|
368 ESP_LOGI(TAG, "Loop user: Init"); |
|
369 // u8g2_SetPowerSave(&u8g2, 0); // wake up display |
|
370 // u8g2_ClearBuffer(&u8g2); |
|
371 // New_Loop2 = MAIN_LOOP2_INACTIVE; |
|
372 New_Loop2 = MAIN_LOOP2_DONE; |
|
373 break; |
|
374 |
|
375 case MAIN_LOOP2_INACTIVE: |
|
376 // u8g2_SetPowerSave(&u8g2, 1); // powersave display |
|
377 New_Loop2 = MAIN_LOOP2_DONE; |
|
378 break; |
|
379 |
|
380 default: |
|
381 break; |
|
382 } |
|
383 } |
|
384 |
|
385 /* |
|
386 * Action process. |
|
387 */ |
|
388 switch (Main_Loop2) { |
|
389 // If wakeup from GPIO -- state machine 2 |
|
390 // Init OLED |
|
391 // If not configured, start configure |
|
392 // If configured select first unit |
|
393 // New rotate position, set screen, reset waittimer |
|
394 // Handle screen (first is show measured values) |
|
395 // Count inactivity |
|
396 // flag if inactive and OLED lowpower. |
|
397 |
|
398 // Break if all done and inactive. |
|
399 default: |
|
400 break; |
|
401 } |
|
402 |
|
403 if (Main_Loop1 == MAIN_LOOP1_DONE && Main_Loop2 == MAIN_LOOP2_DONE) |
|
404 break; |
|
405 |
|
406 vTaskDelay(10 / portTICK_PERIOD_MS); |
|
407 } |
|
408 |
|
409 // printf("Simulate deep sleep\n"); |
|
410 // vTaskDelay(1000 * wakeup_time_sec / portTICK_PERIOD_MS); |
|
411 |
|
412 printf("Entering deep sleep\n"); |
|
413 gettimeofday(&sleep_enter_time, NULL); |
|
414 esp_deep_sleep_start(); |
|
415 |
|
416 Main_Loop1 = MAIN_LOOP1_INIT; |
|
417 New_Loop2 = MAIN_LOOP2_INIT; |
|
418 } |
|
419 |
|
420 } |
|
421 |