diff -r 7c1dacafed03 -r 043ae27633f8 main/task_user.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/task_user.c Tue Nov 05 11:31:59 2019 +0100 @@ -0,0 +1,868 @@ +/** + * @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 +int Main_Loop2 = -1; ///< Effective menu +int New_Loop2 = ML2_INIT; ///< New menu +int SubMenu = 0; ///< Submenu number +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 }; +QueueHandle_t event_queue; +static int PushDuration = 0; ///< Duration of the pushed button + +extern const esp_app_desc_t *app_desc; +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 int count_pub; ///< Published MQTT messages in transit +static xQueueHandle gpio_evt_queue = NULL; ///< Rotary pushbutton queue +extern int Main_Loop1; ///< Main measure loop + + + +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 measurement results + + + +/** + * @brief Seconds timer callback. + */ +void TimerCallback(void *arg); + + +/***************************************************************************/ + + + +void TimerCallback(void *arg) +{ + SecsCount++; + if ((SecsCount % 60) == 0) { + if (Main_Loop1 == ML1_DONE) + 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--; + } +} + + + +void user_cold() +{ + xEventGroupSetBits(xEventGroupUser, TASK_USER_COLD); +} + + + +void user_wakeup() +{ + xEventGroupSetBits(xEventGroupUser, TASK_USER_WAKEUP); +} + + + +void user_refresh() +{ + xEventGroupSetBits(xEventGroupUser, TASK_USER_REFRESH); +} + + + +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. NOTE, start at 0 for a new char?? + * @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; + + u8g2_DrawHLine(&u8g2, x, y+3, 12); + u8g2_SendBuffer(&u8g2); + + for (;;) { + if (xQueueReceive(event_queue, &event, 100 / portTICK_PERIOD_MS) == pdTRUE) { + UserTimer = INACTIVITY; + if (event.state.position != 0) { + + u8g2_SetDrawColor(&u8g2, 0); + u8g2_DrawGlyph(&u8g2, x, y, key); + u8g2_SetDrawColor(&u8g2, 1); + u8g2_SendBuffer(&u8g2); + + if (event.state.position > 0) { + if (key == 126) + key = 171; + else if (key < 126) + key++; + } else if (event.state.position < 0) { + if (key == 171) + key = 126; + else if (key > 32) + key--; + } + + ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); + u8g2_DrawGlyph(&u8g2, x, y, key); + u8g2_SendBuffer(&u8g2); + } + } else { + if (PushDuration) { + if (PushDuration > 500) + rc = 2; + else + rc = 1; + PushDuration = 0; + break; + } + } + } + u8g2_SetDrawColor(&u8g2, 0); + u8g2_DrawHLine(&u8g2, x, y+3, 12); + u8g2_SetDrawColor(&u8g2, 1); + u8g2_SendBuffer(&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_t0_12_tf); + y = 36; + u8g2_DrawStr(&u8g2, 0, y, txt); + u8g2_SendBuffer(&u8g2); + + for (;;) { + x = u8g2_GetUTF8Width(&u8g2, txt); + key = 'a'; + 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 == 171 && strlen(txt)) { + // delete key + txt[strlen(txt) - 1] = '\0'; + } +printf("strlen %d x %d key %d\n", strlen(txt), x, key); + } else if (rc == 2) { + break; + } + } +} + + + +/** + * @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. + * @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, "START"); + u8g2_DrawUTF8(&u8g2, (128 - w) / 2,50, "START"); + + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); // wake up display +} + + + +/** + * @brief The main overview screen. + */ +void screen_main() +{ + char buf[65]; + int i; + + screen_top("CO2 meter %s", app_desc->version); + + if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) { + + u8g2_SetFont(&u8g2, u8g2_font_t0_22b_tf); + sprintf(buf, "%.1f °C", units[0].temperature / 1000.0); + u8g2_uint_t w = u8g2_GetUTF8Width(&u8g2, buf); + u8g2_DrawUTF8(&u8g2, (128 - w) / 2,40, buf); + u8g2_SetFont(&u8g2, u8g2_font_t0_18b_tf); + + for (i = 0; i < 3; i++) { + sprintf(buf, "%.1f", units[i].pressure / 1000.0); + w = u8g2_GetUTF8Width(&u8g2, buf); + u8g2_DrawUTF8(&u8g2, ((42 - w) / 2) + i * 43,63, buf); + } + xSemaphoreGive(xSemaphoreUnits); + } + 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]; + + if (xSemaphoreTake(xSemaphoreUnits, 25) == pdTRUE) { + + screen_top("Unit %d %s", no + 1, units[no].mode ? "On":"Off"); + + u8g2_SetFont(&u8g2, u8g2_font_t0_22b_tf); + sprintf(buf, "%.1f °C", units[no].temperature / 1000.0); + u8g2_uint_t w = u8g2_GetUTF8Width(&u8g2, buf); + u8g2_DrawUTF8(&u8g2, (128 - w) / 2,40, buf); + + sprintf(buf, "%.2f bar", units[no].pressure / 1000.0); + w = u8g2_GetUTF8Width(&u8g2, buf); + u8g2_DrawUTF8(&u8g2, (128 - w) / 2,63, buf); + + xSemaphoreGive(xSemaphoreUnits); + } + 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("Unit %d zero mV", no + 1); + menu_line( 0, 2, 25, "Current %d", units[no].pressure_zero); + menu_line(sub == 0, 2, 37, "New value %d", units[no].pressure_voltage / (adc_state->Batt_voltage / 1000)); + menu_line(sub == 1, 2, 49, "Return"); +printf("current %d p_voltage %d batt %d\n", units[no].pressure_zero, units[no].pressure_voltage, adc_state->Batt_voltage); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +/** + * @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("Unit %d setup", no + 1); + menu_line(sub == 0, 2, 25, "Mode %s", units[no].mode ? "ON":"OFF"); + menu_line(sub == 1, 2, 37, "Zero mV %d", units[no].pressure_zero); + menu_line(sub == 2, 2, 49, "DS18B20 %s", units[no].temperature_rom_code); + menu_line(sub == 3, 2, 61, "Return"); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +void screen_wifi() +{ + char buf[65]; + + screen_top("WiFi Status"); + snprintf(buf, 65, "SSID %s", wifi_state->STA_ssid); + u8g2_DrawStr(&u8g2, 1, 28, buf); + snprintf(buf, 65, "Online %s", wifi_state->STA_online ? "Yes":"No"); + u8g2_DrawStr(&u8g2, 1, 43, buf); + snprintf(buf, 65, "RSSI %d", wifi_state->STA_rssi); + u8g2_DrawStr(&u8g2, 1, 59, buf); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +void screen_wifi_setup(int sub) +{ + screen_top("WiFi Setup"); + menu_line(sub == 0, 2, 25, "Connect"); + menu_line(sub == 1, 2, 37, "New"); + menu_line(sub == 2, 2, 49, "Delete"); + menu_line(sub == 3, 2, 61, "Return"); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +void screen_network() +{ + screen_top("Network Status"); + menu_line(0, 1, 25, "IP %s", wifi_state->STA_ip); + menu_line(0, 1, 37, "Mask %s", wifi_state->STA_nm); + menu_line(0, 1, 49, "GW %s", wifi_state->STA_gw); + menu_line(0, 1, 61, "Name %s", config.hostname); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +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); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +void screen_update() +{ + screen_top("Update firmware"); + menu_line(0, 1, 43, "Push to update"); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +/** + * @brief Fatal messages on the screen. + * @param e1 The first line. + * @param e2 The second line. + */ +void screen_fatal(char *e1, char *e2) +{ + u8g2_SetFont(&u8g2, u8g2_font_t0_15_tr); + u8g2_DrawStr(&u8g2,2,12,e1); + u8g2_DrawStr(&u8g2,2,24,e2); + u8g2_SendBuffer(&u8g2); + u8g2_SetPowerSave(&u8g2, 0); +} + + + +/** + * @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_LOGI(TAG, "GPIO rotary button intr, val: %d time: %d", gpio_get_level(io_num), PushDuration); + } + } else { + ESP_LOGE(TAG, "GPIO[%d] 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 min The lowest number. If already at the lowest, select the highest. + * @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. + * @return Returns true if a new number is selected, false if nothing changed. + */ +bool rotate_to_sub(rotary_encoder_position_t pos, int min, int max, int *cursub) +{ + int sub = *cursub; + bool rc = false; + + if (pos > 0) { + if (sub < max) + sub++; + else + sub = min; + ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); + rc = true; + } else if (pos < 0) { + if (sub > min) + sub--; + else + sub = max; + ESP_ERROR_CHECK(rotary_encoder_reset(&rinfo)); + rc = true; + } + + *cursub = sub; + return rc; +} + + + +void menu_change(void) +{ + char txt[65]; + + if (New_Loop2 != Main_Loop2) { + + Main_Loop2 = New_Loop2; + + switch (Main_Loop2) { + case ML2_INIT: + ESP_LOGI(TAG, "Loop user: Init"); + New_Loop2 = ML2_USER; + break; + + case ML2_USER: + ESP_LOGI(TAG, "Loop user: User mainmenu"); + screen_main(); + break; + + case ML2_UNIT1: + case ML2_UNIT2: + case ML2_UNIT3: + ESP_LOGI(TAG, "Loop user: Unit %d", Main_Loop2 - ML2_UNIT1); + screen_unit(Main_Loop2 - ML2_UNIT1); + break; + + case ML2_WIFI: + ESP_LOGI(TAG, "Loop user: WiFi"); + screen_wifi(); + break; + + case ML2_NETWORK: + ESP_LOGI(TAG, "Loop user: Network"); + screen_network(); + break; + + case ML2_MQTT: + ESP_LOGI(TAG, "Loop user: MQTT"); + screen_mqtt(); + break; + + case ML2_SETUP_MQTT: + ESP_LOGI(TAG, "Loop user: MQTT setup"); + sprintf(txt, "EDtXt"); + rotary_editer("MQTT demo", txt, "", 16, EDIT_TYPE_TEXT); + New_Loop2 = ML2_MQTT; + break; + + case ML2_UPDATE: + ESP_LOGI(TAG, "Loop user: Update"); + screen_update(); + break; + + case ML2_SETUP_UNIT1: + case ML2_SETUP_UNIT2: + case ML2_SETUP_UNIT3: + ESP_LOGI(TAG, "Loop user: Setup Unit %d", Main_Loop2 - ML2_SETUP_UNIT1); + SubMenu = 0; + screen_unit_setup(Main_Loop2 - ML2_SETUP_UNIT1, SubMenu); + break; + + case ML2_ZERO_UNIT1: + case ML2_ZERO_UNIT2: + case ML2_ZERO_UNIT3: + ESP_LOGI(TAG, "Loop user: Zero Unit %d", Main_Loop2 - ML2_ZERO_UNIT1); + SubMenu = 0; + screen_unit_zero(Main_Loop2 - ML2_ZERO_UNIT1, SubMenu); + break; + + case ML2_INACTIVE: + ESP_LOGI(TAG, "Loop user: Inactive"); + u8g2_SetPowerSave(&u8g2, 1); // powersave display + New_Loop2 = ML2_DONE; + break; + + default: + break; + } + } +} + + + +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_UPDATE, ML2_MQTT); break; + case ML2_SETUP_UNIT1: + case ML2_SETUP_UNIT2: + case ML2_SETUP_UNIT3: if (rotate_to_sub(event.state.position, 0, 3, &SubMenu)) + 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, 0, 1, &SubMenu)) + screen_unit_zero(Main_Loop2 - ML2_ZERO_UNIT1, SubMenu); + break; + default: + ESP_LOGI(TAG, "Event: position %d, direction %s", event.state.position, + event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW":"CCW"):"NOT_SET"); + } +} + + + +void menu_loop(void) +{ + int idx = 0; + + 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); + } + screen_unit_setup(idx, SubMenu); + if (Main_Loop1 == ML1_DONE) + Main_Loop1 = ML1_INIT; + } + if (SubMenu == 1) + New_Loop2 = ML2_ZERO_UNIT1 + idx; + if (SubMenu == 3) + New_Loop2 = ML2_UNIT1 + idx; + printf("unit setup sub %d new %d idx %d\n", SubMenu, New_Loop2, 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 && + 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(); + xSemaphoreGive(xSemaphoreADC); + xSemaphoreGive(xSemaphoreUnits); + screen_unit_zero(idx, SubMenu); + if (Main_Loop1 == ML1_DONE) + Main_Loop1 = ML1_INIT; + } + } + if (SubMenu == 1) { + New_Loop2 = ML2_SETUP_UNIT1 + idx; + SubMenu = 1; + } + printf("unit zero sub %d new %d idx %d\n", SubMenu, New_Loop2, idx); + break; + + case ML2_MQTT: + New_Loop2 = ML2_SETUP_MQTT; + break; + + default: + break; + } +} + + + +void task_user(void *pvParameter) +{ + esp_err_t ret; + + 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_PIN_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)); + + esp_timer_create_args_t timerSecond = { + .callback = &TimerCallback, + .name = "SecondsTimer" + }; + + /* + * Create a one second periodic timer. + */ + ESP_ERROR_CHECK(esp_timer_create(&timerSecond, &timerHandle)); + ret = esp_timer_start_periodic(timerHandle, 1000000); + assert(ret == ESP_OK); + + EventBits_t uxBits; + ESP_LOGI(TAG, "User task loop enter"); + + /* + * 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); + xEventGroupClearBits(xEventGroupUser, TASK_USER_REFRESH); + screen_main(); + UserTimer = INACTIVITY; + New_Loop2 = ML2_INIT; + Main_Loop2 = -1; + SubMenu = 0; + + while (UserTimer) { + + menu_change(); + if (xQueueReceive(event_queue, &event, 250 / portTICK_PERIOD_MS) == pdTRUE) { + UserTimer = INACTIVITY; + menu_rotary(); + } + + if (PushDuration) { + PushDuration = 0; + menu_loop(); + } + + if (xEventGroupGetBits(xEventGroupUser) & TASK_USER_REFRESH) { + ESP_LOGI(TAG, "User task 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; + } + xEventGroupClearBits(xEventGroupUser, TASK_USER_REFRESH); + } + + vTaskDelay(10 / portTICK_PERIOD_MS); + } + + xEventGroupClearBits(xEventGroupUser, TASK_USER_WAKEUP); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } + +// If not configured, start configure +// If configured select first unit +// Handle screen (first is show measured values) +// Display per unit. Temp + Pressure + state. Press is setup this sensor. +// Setup menu: Sensors +// WiFi +// MQTT +// System (timers etc) +// Update OTA +// Return + +// Sensors menu: Assignments, turn to choose one. +// Sensors setup menu: DS18B20 addr Press is assign +// DS18B20 addr +} +