# HG changeset patch # User Michiel Broek # Date 1572949919 -3600 # Node ID 043ae27633f8da4da744dfa39b76671dbfa5eff6 # Parent 7c1dacafed038efdc70cc8c54f19a7fb83e24f8e Moved the user interface into a separate task. Added a real seconds timer to this task. diff -r 7c1dacafed03 -r 043ae27633f8 main/CMakeLists.txt --- a/main/CMakeLists.txt Mon Nov 04 19:35:05 2019 +0100 +++ b/main/CMakeLists.txt Tue Nov 05 11:31:59 2019 +0100 @@ -1,3 +1,3 @@ -idf_component_register(SRCS config.c task_mqtt.c task_ds18b20.c updates.c task_adc.c xutil.c co2meter.c u8g2_esp32_hal.c task_wifi.c +idf_component_register(SRCS config.c task_user.c task_mqtt.c task_ds18b20.c updates.c task_adc.c xutil.c co2meter.c u8g2_esp32_hal.c task_wifi.c INCLUDE_DIRS ".") diff -r 7c1dacafed03 -r 043ae27633f8 main/co2meter.c --- a/main/co2meter.c Mon Nov 04 19:35:05 2019 +0100 +++ b/main/co2meter.c Tue Nov 05 11:31:59 2019 +0100 @@ -7,21 +7,9 @@ static const char *TAG = "co2meter"; -#define PIN_SDA (CONFIG_I2C_MASTER_SDA) -#define PIN_SCL (CONFIG_I2C_MASTER_SCL) -#define ROT_ENC_A_GPIO (CONFIG_ROT_ENC_A_GPIO) -#define ROT_ENC_B_GPIO (CONFIG_ROT_ENC_B_GPIO) -#define ROT_ENC_SW_GPIO (CONFIG_ROT_ENC_SW_GPIO) -#define INACTIVITY 480 ///< Time in 250 mSec units. -#define EDIT_TYPE_TEXT 0 ///< Editor type is text -#define EDIT_TYPE_INT 1 ///< Editor type is integer -#define EDIT_TYPE_FLOAT 2 ///< Editor type is float - - +const esp_app_desc_t *app_desc = NULL; ///< Application description int Main_Loop1 = ML1_INIT; ///< Loop 1 init -int Main_Loop2 = -1; ///< Loop 2 invalid -int New_Loop2 = ML2_DONE; ///< Loop 2 new state bool System_TimeOk = false; ///< System time status time_t now; ///< Current time struct tm timeinfo; ///< Current time structure @@ -31,12 +19,7 @@ static TaskHandle_t xTaskADC = NULL; static TaskHandle_t xTaskWifi = NULL; static TaskHandle_t xTaskMQTT = NULL; -const esp_app_desc_t *app_desc = NULL; ///< Application description -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 +static TaskHandle_t xTaskUser = NULL; extern unit_t units[3]; ///< Pressure test units extern SemaphoreHandle_t xSemaphoreUnits; ///< Units lock semaphore @@ -45,457 +28,9 @@ extern ADC_State *adc_state; ///< ADC state extern SemaphoreHandle_t xSemaphoreADC; ///< ADC lock semaphore extern WIFI_State *wifi_state; ///< WiFi state +extern EventGroupHandle_t xEventGroupUser; extern int count_pub; ///< Published MQTT messages in transit -static xQueueHandle gpio_evt_queue = NULL; ///< Rotary pushbutton queue -static int usertimer = 0; ///< User inactive timeout - - - -/** - * @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; -} @@ -505,31 +40,16 @@ gettimeofday(&now, NULL); int sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000; esp_err_t ret; - char txt[65]; Main_Loop1 = ML1_INIT; - 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, - app_desc = esp_ota_get_app_description(); + /* event handler and event group for the user interface */ + xEventGroupUser = xEventGroupCreate(); switch (esp_sleep_get_wakeup_cause()) { case ESP_SLEEP_WAKEUP_EXT1: { ESP_LOGI(TAG, "Starting from deep sleep, Rotary switch pressed"); - New_Loop2 = ML2_INIT; - screen_splash(); + user_wakeup(); break; } case ESP_SLEEP_WAKEUP_TIMER: { @@ -539,7 +59,7 @@ case ESP_SLEEP_WAKEUP_UNDEFINED: default: ESP_LOGI(TAG, "Starting from hard reset"); - screen_splash(); + user_cold(); } const int wakeup_time_sec = 55; @@ -590,7 +110,6 @@ } else { ESP_LOGE(TAG, "Failed to initialize SPIFFS (%d)", ret); } - screen_fatal("SPIFFS:", "init error"); return; // Stop application. } @@ -598,7 +117,6 @@ ret = esp_spiffs_info(NULL, &total, &used); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to get SPIFFS partition information"); - screen_fatal("SPIFFS:", "partition error"); return; // Stop application. } else { ESP_LOGI(TAG, "Partition size: %d, used: %d - %d%%", total, used, (used * 100) / total); @@ -637,6 +155,7 @@ xSemaphoreADC = xSemaphoreCreateMutex(); xSemaphoreUnits = xSemaphoreCreateMutex(); + xTaskCreate(&task_user, "task_user", 2048, NULL,10, &xTaskUser); xTaskCreate(&task_ds18b20, "task_ds18b20", 2560, NULL, 8, &xTaskDS18B20); xTaskCreate(&task_adc, "task_adc", 2560, NULL, 8, &xTaskADC); esp_log_level_set("wifi", ESP_LOG_ERROR); @@ -646,41 +165,13 @@ xTaskCreate(&task_mqtt, "task_mqtt", 4096, NULL, 5, &xTaskMQTT); /* - * 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)); - - /* * Main application loop. */ while (1) { ESP_LOGI(TAG, "Entered app loop"); - //event = { 0 }; - int sub = 0; - u8g2_SetPowerSave(&u8g2, 1); - /* Measure process or user input via rotary switch */ + /* Measure process */ while (1) { switch (Main_Loop1) { case ML1_INIT: @@ -695,8 +186,8 @@ case ML1_CONNECT: if (ready_WiFi()) { Main_Loop1 = ML1_MQTT_CONNECT; - if (Main_Loop2 == ML2_WIFI) - screen_wifi(); +// if (Main_Loop2 == ML2_WIFI) +// screen_wifi(); } break; @@ -747,12 +238,13 @@ } write_units(); xSemaphoreGive(xSemaphoreUnits); - 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; - } + 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; +// } } } break; @@ -791,10 +283,10 @@ case ML1_WIFI_OFF: if (! ready_WiFi()) { - ESP_LOGI(TAG, "Loop timer: Done"); + ESP_LOGI(TAG, "Loop timer: Done %s", user_busy() ? "true":"false"); Main_Loop1 = ML1_DONE; - if (Main_Loop2 == ML2_WIFI) - screen_wifi(); +// if (Main_Loop2 == ML2_WIFI) +// screen_wifi(); } break; @@ -802,222 +294,7 @@ break; } - /* - * One time actions - */ - 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; - usertimer = INACTIVITY; - 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); - sub = 0; - screen_unit_setup(Main_Loop2 - ML2_SETUP_UNIT1, sub); - 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); - sub = 0; - screen_unit_zero(Main_Loop2 - ML2_ZERO_UNIT1, sub); - break; - - case ML2_INACTIVE: - ESP_LOGI(TAG, "Loop user: Inactive"); - u8g2_SetPowerSave(&u8g2, 1); // powersave display - New_Loop2 = ML2_DONE; - break; - - default: - break; - } - } - - /* - * Handle rotationg the rotary encoder. - */ - if (Main_Loop2 < ML2_INACTIVE) { - // 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 - - if (xQueueReceive(event_queue, &event, 250 / portTICK_PERIOD_MS) == pdTRUE) { - usertimer = INACTIVITY; - 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, &sub)) - screen_unit_setup(Main_Loop2 - ML2_SETUP_UNIT1, sub); - break; - case ML2_ZERO_UNIT1: - case ML2_ZERO_UNIT2: - case ML2_ZERO_UNIT3: if (rotate_to_sub(event.state.position, 0, 1, &sub)) - screen_unit_zero(Main_Loop2 - ML2_ZERO_UNIT1, sub); - 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"); - } - } else { - // Poll current position and direction -// rotary_encoder_state_t state = { 0 }; -// ESP_ERROR_CHECK(rotary_encoder_get_state(&rinfo, &state)); - -// ESP_LOGI(TAG, "Poll: position %d, direction %s timer %d", state.position, -// state.direction ? (state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET", usertimer); - if (usertimer) { - usertimer--; - if ((usertimer % 240) == 0) { // Each minute - ESP_LOGI(TAG, "usertimer %d", usertimer); - if (Main_Loop1 == ML1_DONE) - Main_Loop1 = ML1_INIT; - } - } else - New_Loop2 = ML2_INACTIVE; - } - } - - /* - * Handle pressed rotary button. - */ - if (PushDuration) { - 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 (sub == 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, sub); - if (Main_Loop1 == ML1_DONE) - Main_Loop1 = ML1_INIT; - } - if (sub == 1) - New_Loop2 = ML2_ZERO_UNIT1 + idx; - if (sub == 3) - New_Loop2 = ML2_UNIT1 + idx; - printf("unit setup sub %d new %d idx %d\n", sub, New_Loop2, idx); - break; - - case ML2_ZERO_UNIT1: - case ML2_ZERO_UNIT2: - case ML2_ZERO_UNIT3: - idx = Main_Loop2 - ML2_ZERO_UNIT1; - if (sub == 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, sub); - if (Main_Loop1 == ML1_DONE) - Main_Loop1 = ML1_INIT; - } - } - if (sub == 1) { - New_Loop2 = ML2_SETUP_UNIT1 + idx; - sub = 1; - } - printf("unit zero sub %d new %d idx %d\n", sub, New_Loop2, idx); - break; - - case ML2_MQTT: - New_Loop2 = ML2_SETUP_MQTT; - break; - - default: - break; - } - PushDuration = 0; - } - - if (Main_Loop1 == ML1_DONE && Main_Loop2 == ML2_DONE) + if (Main_Loop1 == ML1_DONE && ! user_busy()) break; vTaskDelay(10 / portTICK_PERIOD_MS); @@ -1027,9 +304,10 @@ gettimeofday(&sleep_enter_time, NULL); esp_deep_sleep_start(); +// ESP_LOGI(TAG, "Do nothing loop"); +// vTaskDelay(55000 / portTICK_PERIOD_MS); + Main_Loop1 = ML1_INIT; - New_Loop2 = ML2_INIT; } - } diff -r 7c1dacafed03 -r 043ae27633f8 main/config.c --- a/main/config.c Mon Nov 04 19:35:05 2019 +0100 +++ b/main/config.c Tue Nov 05 11:31:59 2019 +0100 @@ -98,6 +98,8 @@ size_t bytes = fread(dst, 1, sizeof(units), f); fclose(f); ESP_LOGI(TAG, "/spiffs/etc/units.conf read %d bytes", bytes); + for (int i = 0; i < 3; i++) + ESP_LOGI(TAG, "%d %s %d %4d %3d", i, units[i].alias, units[i].pressure_channel, units[i].pressure_voltage, units[i].pressure_zero); } } diff -r 7c1dacafed03 -r 043ae27633f8 main/config.h --- a/main/config.h Mon Nov 04 19:35:05 2019 +0100 +++ b/main/config.h Tue Nov 05 11:31:59 2019 +0100 @@ -58,6 +58,7 @@ #include "task_adc.h" #include "task_wifi.h" #include "task_mqtt.h" +#include "task_user.h" #include "xutil.h" @@ -180,7 +181,7 @@ int pressure_state; ///< Reading status uint8_t pressure_channel; ///< ADC channel uint32_t pressure_voltage; ///< Measured voltage in mV. - uint32_t pressure_zero; ///< Zero offset in mV. + uint32_t pressure_zero; ///< Zero offset promille of the full scale. uint32_t pressure; ///< Pressure in bar * 1000; int mode; ///< Unit mode uint32_t alarm; ///< Alarm bits 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 +} + diff -r 7c1dacafed03 -r 043ae27633f8 main/task_user.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main/task_user.h Tue Nov 05 11:31:59 2019 +0100 @@ -0,0 +1,36 @@ +/** + * @file task_user.h + * @brief The FreeRTOS task to maintain MQTT connections. + */ + +#ifndef _TASK_USER_H +#define _TASK_USER_H + + +#define PIN_SDA (CONFIG_I2C_MASTER_SDA) +#define PIN_SCL (CONFIG_I2C_MASTER_SCL) +#define ROT_ENC_A_GPIO (CONFIG_ROT_ENC_A_GPIO) +#define ROT_ENC_B_GPIO (CONFIG_ROT_ENC_B_GPIO) +#define ROT_ENC_SW_GPIO (CONFIG_ROT_ENC_SW_GPIO) +#define INACTIVITY 120 ///< User inactivity time + +#define EDIT_TYPE_TEXT 0 ///< Editor type is text +#define EDIT_TYPE_INT 1 ///< Editor type is integer +#define EDIT_TYPE_FLOAT 2 ///< Editor type is float + + + +void user_cold(void); +void user_wakeup(void); +void user_refresh(void); +bool user_busy(void); + + +/** + * @brief The FreeRTOS task to run the user interface. + */ +void task_user(void *); + + +#endif +