Tue, 26 Sep 2023 14:56:04 +0200
Migrated ADC converter to isp-idf v5.1
/** * @file task_user.c * @brief co2meter project. */ #include "config.h" static const char *TAG = "task_user"; EventGroupHandle_t xEventGroupUser; ///< Events User task esp_timer_handle_t timerHandle; ///< Seconds timer uint32_t SecsCount = 0; ///< Seconds counter uint32_t UserTimer = 0; ///< User inactive timeout uint32_t AlarmTimer = 0; ///< Alarm timer int Main_Loop2 = -1; ///< Effective menu int New_Loop2 = ML2_INIT; ///< New menu int SubMenu = 0; ///< Submenu number int SubOffset = 0; ///< Submenu offset u8g2_t u8g2; ///< A structure which will contain all the data for one display rotary_encoder_info_t rinfo = { 0 }; ///< Rotary encoder record rotary_encoder_event_t event = { 0 }; ///< Rotary encoder events QueueHandle_t event_queue; ///< Events queue QueueHandle_t gpio_evt_queue = NULL; ///< Rotary pushbutton queue static int PushDuration = 0; ///< Duration of the pushed button wifiStation_t APs[10]; ///< List of APs we know int edit_ssid = 0; ///< SSID being edited int num_ssids = 0; ///< Number of SSIDs we know wifiStation_t editAP; ///< Data of station to edit char sensors[DS18B20_MAX][17]; ///< Sensors to select uint32_t err_connect = 0; ///< Connect error count uint32_t err_alarm = 0; ///< Alarm watchdog error count uint32_t err_temp = 0; ///< DS18B20 read errors extern const esp_app_desc_t *app_desc; ///< App description extern unit_t units[3]; ///< Pressure test units extern SemaphoreHandle_t xSemaphoreUnits; ///< Units lock semaphore extern DS18B20_State *ds18b20_state; ///< DS18B20 state extern SemaphoreHandle_t xSemaphoreDS18B20; ///< DS18B20 lock semaphore extern ADC_State *adc_state; ///< ADC state extern SemaphoreHandle_t xSemaphoreADC; ///< ADC lock semaphore extern WIFI_State *wifi_state; ///< WiFi state extern SemaphoreHandle_t xSemaphoreWiFi; ///< WiFi lock semaphore extern int count_pub; ///< Published MQTT messages in transit extern int Main_Loop1; ///< Main measure loop extern int update_running; ///< If update is running extern int num_sensors; ///< Detected DS18B20 sensors extern strConfig_t config; extern wifiStation_t wifiStation; const int TASK_USER_COLD = BIT0; ///< System cold start const int TASK_USER_WAKEUP = BIT1; ///< System wakeup from deepsleep const int TASK_USER_BUSY = BIT2; ///< User interface is busy doing something. const int TASK_USER_REFRESH = BIT3; ///< Refresh requested /** * @brief Seconds timer callback. * @param arg Arguments for the timer. */ void TimerCallback(void *arg); /***************************************************************************/ void TimerCallback(void *arg) { SecsCount++; if ((SecsCount % MAINLOOP_TIMER) == 0) { if (Main_Loop1 == ML1_DONE && update_running == 0) Main_Loop1 = ML1_INIT; } if (UserTimer == 1) { ESP_LOGI(TAG, "User inactivity timeout"); xEventGroupClearBits(xEventGroupUser, TASK_USER_BUSY); u8g2_SetPowerSave(&u8g2, 1); } if (UserTimer) { UserTimer--; } if (AlarmTimer == 1) { ESP_LOGI(TAG, "Alarm Timer timeout"); Main_Loop1 = ML1_INIT; err_alarm++; } if (AlarmTimer) { AlarmTimer--; } } void user_cold() { xEventGroupSetBits(xEventGroupUser, TASK_USER_COLD); } bool user_busy(void) { if (xEventGroupGetBits(xEventGroupUser) & TASK_USER_BUSY) return true; return false; } /** * @brief Get a keyboard character from the rotary encoder. * @param curkey The referenced value if the key being edited. * @param type The edittype, all values, integer or float. * @param x The x position on the screen. * @param y The y position on the screen. * @return 1 if short keypress, meaning enter key. 2 if long press, enter key and editing is ready. */ int getkey(int *curkey, int type, int x, int y) { int key = *curkey; int rc = 0; int8_t ascent = u8g2_GetAscent(&u8g2); int8_t descent = u8g2_GetDescent(&u8g2); int8_t charheight = u8g2_GetMaxCharHeight(&u8g2); int8_t charwidth = u8g2_GetMaxCharWidth(&u8g2); u8g2_DrawGlyph(&u8g2, x, y, key); u8g2_DrawHLine(&u8g2, x, y+3, 12); u8g2_UpdateDisplay(&u8g2); ESP_LOGI(TAG, "getkey(%c, %d, %d, %d) a %d d %d h %d w %d", key, type, x, y, ascent, descent, charheight, charwidth); for (;;) { if (xQueueReceive(event_queue, &event, 100 / portTICK_PERIOD_MS) == pdTRUE) { UserTimer = INACTIVITY; if (event.state.position != 0) { u8g2_SetDrawColor(&u8g2, 0); u8g2_DrawBox(&u8g2, x, y - 12, charwidth, charheight); u8g2_SetDrawColor(&u8g2, 1); u8g2_DrawHLine(&u8g2, x, y+3, charwidth); if (event.state.position > 0) { /* * If turned fast, the encoder registers multiple steps. * So follow these steps. */ for (int i = 0; i < event.state.position; i++) { if (type == EDIT_TYPE_CAPS) { if (key < 127) key++; if (key > 96 && key < 123) key = 123; } else if (type == EDIT_TYPE_INT) { if (key < 127) key++; if (key < 45) key = 45; if (key == 46 || key == 47) key = 48; if (key > 57) key = 127; } else if (type == EDIT_TYPE_FLOAT) { } else { // EDIT_TYPE_TEXT if (key < 127) key++; } } } else if (event.state.position < 0) { for (int i = 0; i > event.state.position; i--) { if (type == EDIT_TYPE_CAPS) { if (key > 32) key--; if (key > 96 && key < 123) key = 96; } else if (type == EDIT_TYPE_INT) { if (key > 45) key--; if (key > 57) key = 57; if (key == 46 || key == 47) key = 45; } else if (type == EDIT_TYPE_FLOAT) { } else { // EDIT_TYPE_TEXT if (key > 32) key--; } } } ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); u8g2_DrawGlyph(&u8g2, x, y, key); u8g2_SendBuffer(&u8g2); u8g2_UpdateDisplay(&u8g2); } } else { if (PushDuration) { UserTimer = INACTIVITY; if (PushDuration > 500) rc = 2; else rc = 1; PushDuration = 0; break; } } } u8g2_SetDrawColor(&u8g2, 0); if (key == 127) u8g2_DrawBox(&u8g2, x, y - 12, charwidth, charheight); // Erase DEL character u8g2_DrawHLine(&u8g2, x, y+3, charwidth); u8g2_SetDrawColor(&u8g2, 1); u8g2_UpdateDisplay(&u8g2); *curkey = key; return rc; } /** * @brief Editor using the rotary switch. * @param label The label of the edit field. * @param txt The string to edit. * @param errmsg The error message if needed. * @param len The maximum length for the string. * @param type The edit type. */ void rotary_editer(char *label, char *txt, char *errmsg, int len, int type) { char buf[65]; int key, x, y, rc; u8g2_ClearBuffer(&u8g2); u8g2_DrawHLine(&u8g2, 0, 14, 128); u8g2_DrawHLine(&u8g2, 0, 49, 128); u8g2_SetFont(&u8g2, u8g2_font_t0_15_tf); sprintf(buf, "Edit %s", label); u8g2_DrawStr(&u8g2,0,12,buf); if (strlen(errmsg)) { u8g2_SetFont(&u8g2, u8g2_font_t0_12b_tf); u8g2_DrawStr(&u8g2, 0, 61, errmsg); } u8g2_SetFont(&u8g2, u8g2_font_unifont_t_symbols); y = 36; u8g2_DrawStr(&u8g2, 0, y, txt); u8g2_SendBuffer(&u8g2); ESP_LOGI(TAG, "rotary_editer(%s, %s, %s, %d, %d)", label, txt, errmsg, len, type); /* * Choose initial edit key */ if (type == EDIT_TYPE_CAPS) key = 'A'; else if (type == EDIT_TYPE_INT || type == EDIT_TYPE_FLOAT) key = '0'; else key = 'a'; for (;;) { x = u8g2_GetUTF8Width(&u8g2, txt); rc = getkey(&key, type, x, y); if (rc == 1) { if (key >= 32 && key <= 126 && strlen(txt) < len) { txt[strlen(txt) + 1] = '\0'; txt[strlen(txt)] = key; } else if (key == 127 && strlen(txt)) { // delete key txt[strlen(txt) - 1] = '\0'; if (strlen(txt)) key = txt[strlen(txt) - 1]; } ESP_LOGI(TAG, " strlen %d x %d key %d `%s`", strlen(txt), x, key, txt); } else if (rc == 2) { break; } } ESP_LOGI(TAG, "rotary_editer() `%s`", txt); } /** * @brief Write a menu line on the display. * @param bright Display the line with a bold or normal font. * @param x The horizontal start position of the line. * @param y The vertical bottom of the line position. * @param format The formatted data to display. */ void menu_line(int bright, int x, int y, const char *format, ...) { char buf[65]; va_list va_ptr; if (bright) u8g2_SetFont(&u8g2, u8g2_font_t0_12b_tr); else u8g2_SetFont(&u8g2, u8g2_font_t0_12_tr); va_start(va_ptr, format); vsnprintf(buf, 65, format, va_ptr); va_end(va_ptr); u8g2_DrawStr(&u8g2, x, y, buf); } /** * @brief Clear the display and prepare the top of the display. * @param format The formatted data to display at the top. */ void screen_top(const char *format, ...) { char buf[65]; va_list va_ptr; va_start(va_ptr, format); vsnprintf(buf, 65, format, va_ptr); va_end(va_ptr); u8g2_ClearBuffer(&u8g2); u8g2_DrawHLine(&u8g2, 0, 14, 128); u8g2_SetFont(&u8g2, u8g2_font_t0_15_tr); u8g2_uint_t w = u8g2_GetStrWidth(&u8g2, buf); u8g2_DrawStr(&u8g2, (128 - w) / 2,12, buf); } /** * @brief The splash screen shown during cold boot or user wakeup. */ void screen_splash() { screen_top("CO2 meter %s", app_desc->version); u8g2_SetFont(&u8g2, u8g2_font_t0_22b_tf); u8g2_uint_t w = u8g2_GetUTF8Width(&u8g2, "Welkom"); u8g2_DrawUTF8(&u8g2, (128 - w) / 2,50, "Welkom"); u8g2_SendBuffer(&u8g2); u8g2_SetPowerSave(&u8g2, 0); // wake up display } /** * @brief The main overview screen. */ void screen_main() { char buf[65]; int i, mode[3]; uint32_t temperature = 0, pressure[3]; if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) { temperature = units[0].temperature; for (i = 0; i < 3; i++) { pressure[i] = units[i].pressure; mode[i] = units[i].mode; } xSemaphoreGive(xSemaphoreUnits); } else { ESP_LOGE(TAG, "screen_main() lock error"); } screen_top("CO2 meter %s", app_desc->version); u8g2_SetFont(&u8g2, u8g2_font_t0_22b_tf); sprintf(buf, "%.1f °C", temperature / 1000.0); u8g2_uint_t w = u8g2_GetUTF8Width(&u8g2, buf); u8g2_DrawUTF8(&u8g2, (128 - w) / 2,40, buf); for (i = 0; i < 3; i++) { if (mode[i]) u8g2_SetFont(&u8g2, u8g2_font_t0_18b_tf); else u8g2_SetFont(&u8g2, u8g2_font_t0_18_tf); sprintf(buf, "%.1f", pressure[i] / 1000.0); w = u8g2_GetUTF8Width(&u8g2, buf); u8g2_DrawUTF8(&u8g2, ((42 - w) / 2) + i * 43,63, buf); } u8g2_SendBuffer(&u8g2); u8g2_SetPowerSave(&u8g2, 0); // wake up display } /** * @brief The unit display screen. * @param no The unit index number. */ void screen_unit(int no) { char buf[65]; int mode = 0; uint32_t temperature = 0, pressure = 0; if (xSemaphoreTake(xSemaphoreUnits, 35) == pdTRUE) { mode = units[no].mode; temperature = units[no].temperature; pressure = units[no].pressure; xSemaphoreGive(xSemaphoreUnits); } else { ESP_LOGE(TAG, "screen_unit(%d) lock error", no); } screen_top("Meter %d %s", no + 1, mode ? "Aan":"Uit"); u8g2_SetFont(&u8g2, u8g2_font_t0_22b_tf); sprintf(buf, "%.1f °C", temperature / 1000.0); u8g2_uint_t w = u8g2_GetUTF8Width(&u8g2, buf); u8g2_DrawUTF8(&u8g2, (128 - w) / 2,40, buf); sprintf(buf, "%.2f bar", pressure / 1000.0); w = u8g2_GetUTF8Width(&u8g2, buf); u8g2_DrawUTF8(&u8g2, (128 - w) / 2,63, buf); u8g2_SendBuffer(&u8g2); u8g2_SetPowerSave(&u8g2, 0); // wake up display } /** * @brief The unit zero setup screen. * @param no The unit index number. * @param sub The submenu index number. */ void screen_unit_zero(int no, int sub) { screen_top("Meter %d nulpunt", no + 1); menu_line( 0, 1, 25, "Huidig %d", units[no].pressure_zero); menu_line(sub == 0, 1, 37, "Nieuw %d", units[no].pressure_voltage / (adc_state->Batt_voltage / 1000)); menu_line(sub == 1, 1, 49, "Terug"); u8g2_SendBuffer(&u8g2); } /** * @brief The list of detected DS18B20 sensors. * @param no The unit index number. * @param sub The submenu index number. * @param offset The offset in the list. */ void screen_list_sensors(int no, int sub, int offset) { int i; if (xSemaphoreTake(xSemaphoreDS18B20, 10) == pdTRUE) { for (i = 0; i < ds18b20_state->num_sensors; i++) { strncpy(sensors[i], ds18b20_state->sensor[i].rom_code, 17); sensors[i][16] = '\0'; } xSemaphoreGive(xSemaphoreDS18B20); } else { ESP_LOGE(TAG, "screen_list_sensors() DS18B20 lock error"); } screen_top("DS18B20 lijst"); if (num_sensors == 0) { menu_line(0, 1, 25, "Geen sensors"); } else { for (i = 0; i < num_sensors; i++) { menu_line(sub == i, 1, 25 + (i * 12), sensors[i + offset]); if (i == 3) break; } if ((i + offset) == num_sensors) menu_line(sub == i, 1, 25 + (i * 12), "Terug"); } u8g2_SendBuffer(&u8g2); } /** * @brief The unit setup screen. * @param no The unit index number. * @param sub The submenu index number. */ void screen_unit_setup(int no, int sub) { screen_top("Meter %d setup", no + 1); menu_line(sub == 0, 1, 25, "Werking %s", units[no].mode ? "Aan":"Uit"); menu_line(sub == 1, 1, 37, "Nulpunt %d", units[no].pressure_zero); menu_line(sub == 2, 1, 49, "DS18B20 %s", units[no].temperature_rom_code); menu_line(sub == 3, 1, 61, "Terug"); u8g2_SendBuffer(&u8g2); } /** * @brief The WiFi overview screen. */ void screen_wifi() { char buf[65]; int8_t rssi = 0; bool online = false; if (xSemaphoreTake(xSemaphoreWiFi, 25) == pdTRUE) { snprintf(buf, 65, "SSID %s", wifi_state->STA_ssid); rssi = wifi_state->STA_rssi; online = wifi_state->STA_online; xSemaphoreGive(xSemaphoreWiFi); } else { ESP_LOGE(TAG, "screen_wifi() lock error"); } screen_top("WiFi Status"); u8g2_DrawStr(&u8g2, 1, 28, buf); snprintf(buf, 65, "RSSI %d", rssi); u8g2_DrawStr(&u8g2, 1, 43, buf); snprintf(buf, 65, "Verbonden %s", online ? "Ja":"Nee"); u8g2_DrawStr(&u8g2, 1, 59, buf); u8g2_SendBuffer(&u8g2); } /** * @brief The WiFi setup menu. * @param sub The submenu entry to hilite. */ void screen_wifi_setup(int sub) { screen_top("WiFi AP setup"); menu_line(sub == 0, 1, 25, "Lijst AP"); menu_line(sub == 1, 1, 37, "Nieuw AP"); menu_line(sub == 2, 1, 49, "Terug"); u8g2_SendBuffer(&u8g2); } /** * @brief Show the list if WiFi Access Points. * @param sub The sub entry line. * @param offset The offset in the list. */ void screen_list_aps(int sub, int offset) { int i; wifiStation_t ap; uint8_t *dst = (uint8_t *)≈ FILE *f; size_t bytes; num_ssids = 0; memset(dst, 0, sizeof(ap)); f = fopen("/spiffs/stations.conf", "r"); if (f) { while (1) { bytes = fread(dst, 1, sizeof(ap), f); if (bytes < sizeof(ap)) { fclose(f); break; } memcpy(APs[num_ssids].SSID, ap.SSID, 32); memcpy(APs[num_ssids].Password, ap.Password, 64); num_ssids++; } } screen_top("WiFi AP lijst"); if (num_ssids == 0) { menu_line(0, 1, 25, "Geen AP's"); } else { for (i = 0; i < num_ssids; i++) { menu_line(sub == i, 1, 25 + (i * 12), APs[i + offset].SSID); if (i == 3) break; } if ((i + offset) == num_ssids) menu_line(sub == i, 1, 25 + (i * 12), "Terug"); } u8g2_SendBuffer(&u8g2); } /** * @brief Edit WiFi AP menu. * @param sub The menu entry to hilite. */ void screen_edit_ap(int sub) { screen_top("WiFi wijzig AP"); menu_line(sub == 0, 1, 25, "SSID %s", editAP.SSID); menu_line(sub == 1, 1, 37, "PSK %s", editAP.Password); menu_line(sub == 2, 1, 49, "Opslaan"); if (edit_ssid >= 0) { menu_line(sub == 3, 1, 61, "Verwijder"); } u8g2_SendBuffer(&u8g2); } /** * @brief The network status screen. */ void screen_network() { char ip[17], nm[17], gw[17]; if (xSemaphoreTake(xSemaphoreWiFi, 25) == pdTRUE) { strcpy(ip, wifi_state->STA_ip); strcpy(nm, wifi_state->STA_nm); strcpy(gw, wifi_state->STA_gw); xSemaphoreGive(xSemaphoreWiFi); } else { ESP_LOGE(TAG, "screen_network() lock error"); } screen_top("Netwerk Status"); menu_line(0, 1, 25, "IP %s", ip); menu_line(0, 1, 37, "Mask %s", nm); menu_line(0, 1, 49, "GW %s", gw); menu_line(0, 1, 61, "Naam %s", config.hostname); u8g2_SendBuffer(&u8g2); } /** * @brief The network setup menu. * @param sub The menu entry to hilite. */ void screen_network_setup(int sub) { screen_top("Netwerk setup"); menu_line(sub == 0, 1, 25, "Naam %s", config.hostname); menu_line(sub == 1, 1, 37, "NTP %s", config.ntp_server); menu_line(sub == 2, 1, 49, "Terug"); u8g2_SendBuffer(&u8g2); } /** * @brief MQTT status */ void screen_mqtt() { screen_top("MQTT Status"); menu_line(0, 1, 25, "serv %s", config.mqtt_server); menu_line(0, 1, 37, "port %d", config.mqtt_port); menu_line(0, 1, 49, "user %s", config.mqtt_user); u8g2_SendBuffer(&u8g2); } /** * @brief MQTT setup menu. * @param sub The submenu entry to hilite. */ void screen_mqtt_setup(int sub) { screen_top("MQTT Setup"); menu_line(sub == 0, 1, 25, "serv %s", config.mqtt_server); menu_line(sub == 1, 1, 37, "port %d", config.mqtt_port); menu_line(sub == 2, 1, 49, "user %s", config.mqtt_user); menu_line(sub == 3, 1, 61, "Terug"); u8g2_SendBuffer(&u8g2); } /** * @brief The OTA update menu. */ void screen_update() { screen_top("Update software"); menu_line(0, 1, 43, "Druk voor update"); u8g2_SendBuffer(&u8g2); } /** * @brief The update status screen. * @param m1 The first message line or NULL. * @param m2 The second message line or NULL. */ void screen_updating(char *m1, char *m2) { screen_top("Bijwerken ..."); u8g2_SetFont(&u8g2, u8g2_font_unifont_t_symbols); if (m1) { u8g2_DrawUTF8(&u8g2,2,30, m1); } if (m2) { u8g2_DrawUTF8(&u8g2,2,55, m2); } u8g2_SendBuffer(&u8g2); } /** * @brief The counters display screen. */ void screen_counters() { char buf[65]; screen_top("Software fouten"); snprintf(buf, 64, "Network %4lu", err_connect); u8g2_DrawStr(&u8g2, 1, 28, buf); snprintf(buf, 64, "Watchdog %4lu", err_alarm); u8g2_DrawStr(&u8g2, 1, 43, buf); snprintf(buf, 64, "DS18B20 %4lu", err_temp); u8g2_DrawStr(&u8g2, 1, 59, buf); u8g2_SendBuffer(&u8g2); } /** * @brief Interrupt service routine for the rotary pushbutton. */ static void IRAM_ATTR gpio_isr_handler(void* arg) { uint32_t gpio_num = (uint32_t) arg; xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); } /** * @brief GPIO queue task. See if there is a rotary pushbutton event on the queue. */ static void gpio_task(void* arg) { uint32_t io_num; static int64_t pushed = 0; for(;;) { if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { if (io_num == ROT_ENC_SW_GPIO) { if (gpio_get_level(io_num) == 0) { pushed = esp_timer_get_time(); PushDuration = 0; } else if (gpio_get_level(io_num) == 1) { PushDuration = (esp_timer_get_time() - pushed) / 1000; ESP_LOGD(TAG, "GPIO rotary button intr, val: %d time: %d", gpio_get_level(io_num), PushDuration); if (! user_busy()) { xEventGroupSetBits(xEventGroupUser, TASK_USER_WAKEUP); } } } else { ESP_LOGE(TAG, "GPIO[%lu] unknown intr, val: %d", io_num, gpio_get_level(io_num)); } UserTimer = INACTIVITY; } } } /** * @brief Select new menu number on a postitive or negative rotary position. * @param pos The new position, positive, negative or zero. * @param next_menu The selected menu if rotated clockwise. * @param prev_menu The selected menu if rotated counter-clockwise. */ static void rotate_to_menu(rotary_encoder_position_t pos, int next_menu, int prev_menu) { if (pos > 0) New_Loop2 = next_menu; else if (pos < 0) New_Loop2 = prev_menu; ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); } /** * @brief Rotate subscreens numbers. * @param pos The new position, positive, negative or zero. * @param max The highest number. If already at the highest, select the lowest. * @param cursub The subscreen number by reference. This is updated with the new number. * @param curoffset The offset in the list. This is updated with the new number. * @return Returns true if a new number is selected, false if nothing changed. */ bool rotate_to_sub(rotary_encoder_position_t pos, int max, int *cursub, int *curoffset) { int sub = *cursub; int offset = *curoffset; bool rc = false; if (pos > 0) { if ((sub + offset) < max) { if (sub < 3) { sub++; } else { offset++; } } else { sub = offset = 0; } ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); rc = true; } else if (pos < 0) { if ((sub + offset) > 0) { if (offset > 0) { offset--; } else { sub--; } } else { if (max > 3) { offset = max - 3; sub = 3; } else { sub = max; offset = 0; } } ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); rc = true; } *cursub = sub; *curoffset = offset; return rc; } /** * @brief Handle menu changes. */ void menu_change(void) { if (New_Loop2 != Main_Loop2) { Main_Loop2 = New_Loop2; switch (Main_Loop2) { case ML2_INIT: ESP_LOGI(TAG, "Loop user: Init"); New_Loop2 = -1; if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) { for (int i = 0; i < 3; i++) { if (units[i].mode) { if (New_Loop2 == -1) { // Not selected yet New_Loop2 = i; } else if (New_Loop2 >= 0) { // One selected New_Loop2 = -2; break; // Multiple units are active } } } xSemaphoreGive(xSemaphoreUnits); } else { ESP_LOGE(TAG, "menu_change() ML2_INIT lock"); } if (New_Loop2 < 0) New_Loop2 = ML2_USER; else New_Loop2 += ML2_UNIT1; break; case ML2_USER: screen_main(); break; case ML2_UNIT1: case ML2_UNIT2: case ML2_UNIT3: screen_unit(Main_Loop2 - ML2_UNIT1); break; case ML2_WIFI: screen_wifi(); break; case ML2_SETUP_WIFI: screen_wifi_setup(SubMenu); break; case ML2_LIST_APS: SubMenu = SubOffset = 0; screen_list_aps(SubMenu, SubOffset); break; case ML2_EDIT_AP: screen_edit_ap(0); break; case ML2_NETWORK: screen_network(); break; case ML2_SETUP_NETWORK: screen_network_setup(SubMenu); break; case ML2_MQTT: screen_mqtt(); break; case ML2_SETUP_MQTT: screen_mqtt_setup(SubMenu); break; case ML2_UPDATE: screen_update(); break; case ML2_COUNTERS: screen_counters(); break; case ML2_SETUP_UNIT1: case ML2_SETUP_UNIT2: case ML2_SETUP_UNIT3: SubMenu = SubOffset = 0; screen_unit_setup(Main_Loop2 - ML2_SETUP_UNIT1, SubMenu); break; case ML2_ZERO_UNIT1: case ML2_ZERO_UNIT2: case ML2_ZERO_UNIT3: SubMenu = SubOffset = 0; screen_unit_zero(Main_Loop2 - ML2_ZERO_UNIT1, SubMenu); break; case ML2_SEL_SENSOR1: case ML2_SEL_SENSOR2: case ML2_SEL_SENSOR3: SubMenu = SubOffset = 0; screen_list_sensors(Main_Loop2 - ML2_SEL_SENSOR1, SubMenu, SubOffset); break; case ML2_INACTIVE: ESP_LOGI(TAG, "Loop user: Inactive"); u8g2_SetPowerSave(&u8g2, 1); // powersave display New_Loop2 = ML2_DONE; break; default: break; } } } /** * @brief Handle rotary switch for menu navigation. */ void menu_rotary(void) { switch (Main_Loop2) { case ML2_USER: rotate_to_menu(event.state.position, ML2_UNIT1, ML2_USER); break; case ML2_UNIT1: rotate_to_menu(event.state.position, ML2_UNIT2, ML2_USER); break; case ML2_UNIT2: rotate_to_menu(event.state.position, ML2_UNIT3, ML2_UNIT1); break; case ML2_UNIT3: rotate_to_menu(event.state.position, ML2_WIFI, ML2_UNIT2); break; case ML2_WIFI: rotate_to_menu(event.state.position, ML2_NETWORK, ML2_UNIT3); break; case ML2_NETWORK: rotate_to_menu(event.state.position, ML2_MQTT, ML2_WIFI); break; case ML2_MQTT: rotate_to_menu(event.state.position, ML2_UPDATE, ML2_NETWORK); break; case ML2_UPDATE: rotate_to_menu(event.state.position, ML2_COUNTERS, ML2_MQTT); break; case ML2_COUNTERS: rotate_to_menu(event.state.position, ML2_COUNTERS, ML2_UPDATE); break; case ML2_SETUP_UNIT1: case ML2_SETUP_UNIT2: case ML2_SETUP_UNIT3: if (rotate_to_sub(event.state.position, 3, &SubMenu, &SubOffset)) screen_unit_setup(Main_Loop2 - ML2_SETUP_UNIT1, SubMenu); break; case ML2_ZERO_UNIT1: case ML2_ZERO_UNIT2: case ML2_ZERO_UNIT3: if (rotate_to_sub(event.state.position, 1, &SubMenu, &SubOffset)) screen_unit_zero(Main_Loop2 - ML2_ZERO_UNIT1, SubMenu); break; case ML2_SEL_SENSOR1: case ML2_SEL_SENSOR2: case ML2_SEL_SENSOR3: if (rotate_to_sub(event.state.position, num_sensors, &SubMenu, &SubOffset)) screen_list_sensors(Main_Loop2 - ML2_SEL_SENSOR1, SubMenu, SubOffset); break; case ML2_SETUP_WIFI: if (rotate_to_sub(event.state.position, 2, &SubMenu, &SubOffset)) screen_wifi_setup(SubMenu); break; case ML2_LIST_APS: if (rotate_to_sub(event.state.position, num_ssids, &SubMenu, &SubOffset)) screen_list_aps(SubMenu, SubOffset); break; case ML2_EDIT_AP: if (edit_ssid < 0) { if (rotate_to_sub(event.state.position, 2, &SubMenu, &SubOffset)) screen_edit_ap(SubMenu); } else { if (rotate_to_sub(event.state.position, 3, &SubMenu, &SubOffset)) screen_edit_ap(SubMenu); } break; case ML2_SETUP_MQTT: if (rotate_to_sub(event.state.position, 3, &SubMenu, &SubOffset)) screen_mqtt_setup(SubMenu); break; case ML2_SETUP_NETWORK: if (rotate_to_sub(event.state.position, 2, &SubMenu, &SubOffset)) screen_network_setup(SubMenu); break; default: ESP_LOGI(TAG, "Event: position %ld, direction %s", event.state.position, event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW":"CCW"):"NOT_SET"); } } /* * Refresh screens that are in focus. Called by the main measurement loop. */ void user_refresh(void) { if (UserTimer) xEventGroupSetBits(xEventGroupUser, TASK_USER_REFRESH); } /** * @brief Pressed keys actions */ void menu_loop(void) { int idx = 0; char txt[32]; switch (Main_Loop2) { case ML2_UNIT1: case ML2_UNIT2: case ML2_UNIT3: New_Loop2 = ML2_SETUP_UNIT1 + (Main_Loop2 - ML2_UNIT1); break; case ML2_SETUP_UNIT1: case ML2_SETUP_UNIT2: case ML2_SETUP_UNIT3: idx = Main_Loop2 - ML2_SETUP_UNIT1; if (SubMenu == 0) { if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) { if (units[idx].mode) units[idx].mode = 0; else units[idx].mode = 1; write_units(); xSemaphoreGive(xSemaphoreUnits); } else { ESP_LOGE(TAG, "menu_loop() ML2_SETUP_UNIT%d units lock", idx + 1); } screen_unit_setup(idx, SubMenu); if (Main_Loop1 == ML1_DONE) Main_Loop1 = ML1_INIT; } else if (SubMenu == 1) { New_Loop2 = ML2_ZERO_UNIT1 + idx; } else if (SubMenu == 2) { New_Loop2 = ML2_SEL_SENSOR1 + idx; } else if (SubMenu == 3) { New_Loop2 = ML2_UNIT1 + idx; } break; case ML2_ZERO_UNIT1: case ML2_ZERO_UNIT2: case ML2_ZERO_UNIT3: idx = Main_Loop2 - ML2_ZERO_UNIT1; if (SubMenu == 0) { if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE && xSemaphoreTake(xSemaphoreADC, 10) == pdTRUE) { if (adc_state->Pressure[idx].voltage > 165 && adc_state->Pressure[idx].voltage < 660) { units[idx].pressure_zero = adc_state->Pressure[idx].voltage / (adc_state->Batt_voltage / 1000); write_units(); ESP_LOGI(TAG, "Zero set unit %d, %lu mV set %lu", idx, adc_state->Pressure[idx].voltage, units[idx].pressure_zero); } xSemaphoreGive(xSemaphoreADC); xSemaphoreGive(xSemaphoreUnits); screen_unit_zero(idx, SubMenu); if (Main_Loop1 == ML1_DONE) Main_Loop1 = ML1_INIT; } } else if (SubMenu == 1) { New_Loop2 = ML2_SETUP_UNIT1 + idx; SubMenu = 1; } break; case ML2_SEL_SENSOR1: case ML2_SEL_SENSOR2: case ML2_SEL_SENSOR3: idx = Main_Loop2 - ML2_SEL_SENSOR1; if ((SubMenu + SubOffset) < num_sensors) { ESP_LOGI(TAG, "Select sensor %d %s for unit %d", SubMenu + SubOffset, sensors[SubMenu + SubOffset], idx + 1); if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) { strcpy(units[idx].temperature_rom_code, sensors[SubMenu + SubOffset]); write_units(); xSemaphoreGive(xSemaphoreUnits); New_Loop2 = ML2_SETUP_UNIT1 + idx; SubMenu = 2; SubOffset = 0; if (Main_Loop1 == ML1_DONE) Main_Loop1 = ML1_INIT; } else { ESP_LOGE(TAG, "Failed units lock for new romcode"); } } else { New_Loop2 = ML2_SETUP_UNIT1 + idx; SubMenu = 2; SubOffset = 0; } break; case ML2_WIFI: New_Loop2 = ML2_SETUP_WIFI; break; case ML2_SETUP_WIFI: if (SubMenu == 0) { New_Loop2 = ML2_LIST_APS; } else if (SubMenu == 1) { edit_ssid = -1; editAP.SSID[0] = '\0'; editAP.Password[0] = '\0'; SubMenu = SubOffset = 0; New_Loop2 = ML2_EDIT_AP; } else if (SubMenu == 2) { New_Loop2 = ML2_WIFI; } break; case ML2_LIST_APS: if ((SubMenu + SubOffset) < num_ssids) { edit_ssid = SubMenu + SubOffset; memcpy(editAP.SSID, APs[edit_ssid].SSID, 32); memcpy(editAP.Password, APs[edit_ssid].Password, 64); SubMenu = SubOffset = 0; New_Loop2 = ML2_EDIT_AP; } else { New_Loop2 = ML2_SETUP_WIFI; SubMenu = SubOffset = 0; } break; case ML2_EDIT_AP: ESP_LOGI(TAG, " ML2_EDIT_AP: SubMenu %d edit_ssid %d", SubMenu, edit_ssid); if (SubMenu == 0) { rotary_editer("SSID", editAP.SSID, "", 16, EDIT_TYPE_TEXT); screen_edit_ap(SubMenu); } else if (SubMenu == 1) { rotary_editer("PSK", editAP.Password, "", 16, EDIT_TYPE_TEXT); screen_edit_ap(SubMenu); } else if (SubMenu == 2 || SubMenu == 3) { update_running = 1; // Block measurements if (edit_ssid < 0) New_Loop2 = ML2_SETUP_WIFI; else New_Loop2 = ML2_LIST_APS; if (edit_ssid >= 0) { ESP_LOGI(TAG, "Remove %s", APs[edit_ssid].SSID); remove_station((uint8_t *)APs[edit_ssid].SSID); } if (SubMenu == 2) { ESP_LOGI(TAG, "Add %s %s", editAP.SSID, editAP.Password); add_station((uint8_t *)editAP.SSID, (uint8_t *)editAP.Password); } } update_running = 0; break; case ML2_NETWORK: New_Loop2 = ML2_SETUP_NETWORK; break; case ML2_SETUP_NETWORK: if (SubMenu == 0) { rotary_editer("Hostnaam", config.hostname, "", 16, EDIT_TYPE_TEXT); screen_network_setup(SubMenu); } else if (SubMenu == 1) { rotary_editer("NTP server", config.ntp_server, "", 16, EDIT_TYPE_TEXT); screen_network_setup(SubMenu); } else if (SubMenu == 2) { ESP_LOGI(TAG, "Hostname `%s`", config.hostname); ESP_LOGI(TAG, "NTP server `%s`", config.ntp_server); write_config(); New_Loop2 = ML2_NETWORK; } break; case ML2_MQTT: New_Loop2 = ML2_SETUP_MQTT; break; case ML2_SETUP_MQTT: if (SubMenu == 0) { rotary_editer("MQTT server", config.mqtt_server, "", 16, EDIT_TYPE_TEXT); screen_mqtt_setup(SubMenu); } else if (SubMenu == 1) { sprintf(txt, "%d", config.mqtt_port); rotary_editer("MQTT server poort", txt, "", 6, EDIT_TYPE_INT); config.mqtt_port = atoi(txt); screen_mqtt_setup(SubMenu); } else if (SubMenu == 2) { rotary_editer("MQTT user", config.mqtt_user, "", 16, EDIT_TYPE_TEXT); rotary_editer("MQTT password", config.mqtt_pwd, "", 16, EDIT_TYPE_TEXT); screen_mqtt_setup(SubMenu); } else if (SubMenu == 3) { ESP_LOGI(TAG, "mqtt_server %s:%d", config.mqtt_server, config.mqtt_port); ESP_LOGI(TAG, "mqtt_user/pass `%s/%s`", config.mqtt_user, config.mqtt_pwd); write_config(); New_Loop2 = ML2_MQTT; } break; case ML2_UPDATE: bin_update(); screen_update(); break; default: break; } } void task_user(void *pvParameter) { ESP_LOGI(TAG, "Starting User task"); Main_Loop2 = -1; /* * Setup the OLED display. * See: https://github.com/nkolban/esp32-snippets/blob/master/hardware/displays/U8G2/ */ u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT; u8g2_esp32_hal.sda = PIN_SDA; u8g2_esp32_hal.scl = PIN_SCL; u8g2_esp32_hal_init(u8g2_esp32_hal); u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure u8x8_SetI2CAddress(&u8g2.u8x8, 0x78); u8g2_InitDisplay(&u8g2); // send init sequence to the display, display is in sleep mode after this, /* * Setup the Rotary Encoder. * esp32-rotary-encoder requires that the GPIO ISR service is * installed before calling rotary_encoder_register() */ ESP_ERROR_CHECK(gpio_install_isr_service(0)); ESP_ERROR_CHECK(rotary_encoder_init(&rinfo, ROT_ENC_A_GPIO, ROT_ENC_B_GPIO)); ESP_ERROR_CHECK(rotary_encoder_enable_half_steps(&rinfo, false)); gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_ANYEDGE; io_conf.pin_bit_mask = (1ULL << ROT_ENC_SW_GPIO); io_conf.mode = GPIO_MODE_INPUT; gpio_config(&io_conf); gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL); gpio_isr_handler_add(ROT_ENC_SW_GPIO, gpio_isr_handler, (void*) ROT_ENC_SW_GPIO); /* * Create a queue for events from the rotary encoder driver. * Tasks can read from this queue to receive up to date position information. */ event_queue = rotary_encoder_create_queue(); ESP_ERROR_CHECK(rotary_encoder_set_queue(&rinfo, event_queue)); /* * Create a one second periodic timer. */ esp_timer_create_args_t timerSecond = { .callback = &TimerCallback, .name = "SecondsTimer" }; ESP_ERROR_CHECK(esp_timer_create(&timerSecond, &timerHandle)); ESP_ERROR_CHECK(esp_timer_start_periodic(timerHandle, 1000000)); EventBits_t uxBits; /* * Task loop forever. */ while (1) { uxBits = xEventGroupWaitBits(xEventGroupUser, TASK_USER_COLD | TASK_USER_WAKEUP, pdFALSE, pdFALSE, portMAX_DELAY ); if (uxBits & TASK_USER_COLD) { ESP_LOGI(TAG, "User task cold start"); screen_splash(); xEventGroupClearBits(xEventGroupUser, TASK_USER_COLD); UserTimer = 10; } if (uxBits & TASK_USER_WAKEUP) { ESP_LOGI(TAG, "User task wakeup"); xEventGroupSetBits(xEventGroupUser, TASK_USER_BUSY); UserTimer = INACTIVITY; New_Loop2 = ML2_INIT; Main_Loop2 = -1; SubMenu = 0; while (UserTimer) { menu_change(); if (xEventGroupGetBits(xEventGroupUser) & TASK_USER_REFRESH) { xEventGroupClearBits(xEventGroupUser, TASK_USER_REFRESH); switch (Main_Loop2) { case ML2_USER: screen_main(); break; case ML2_UNIT1: screen_unit(0); break; case ML2_UNIT2: screen_unit(1); break; case ML2_UNIT3: screen_unit(2); break; case ML2_WIFI: screen_wifi(); break; case ML2_COUNTERS: screen_counters(); break; } } if (xQueueReceive(event_queue, &event, 100 / portTICK_PERIOD_MS) == pdTRUE) { UserTimer = INACTIVITY; menu_rotary(); } if (PushDuration) { PushDuration = 0; menu_loop(); } } xEventGroupClearBits(xEventGroupUser, TASK_USER_WAKEUP); } } }