main/task_user.c

Fri, 08 Nov 2019 22:40:15 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Fri, 08 Nov 2019 22:40:15 +0100
changeset 26
8a3696620c0a
parent 25
cc7c423f03fb
child 27
8bb63daa7b46
permissions
-rw-r--r--

Increaded stacksize for the user process. Implemented the network update using the proven brewboard code. Reverted the lock release and display sendbuffer lines to the previous code. The networks status screen uses the wifi lock.

/**
 * @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 xQueueHandle                     gpio_evt_queue = NULL;          ///< Rotary pushbutton 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 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

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.



/**
 * @brief Seconds timer callback.
 */
void TimerCallback(void *arg);


/***************************************************************************/



void TimerCallback(void *arg)
{
    SecsCount++;
    if ((SecsCount % 60) == 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--;
    }
}



void user_cold()
{
    xEventGroupSetBits(xEventGroupUser, TASK_USER_COLD);
}



void user_wakeup()
{
    xEventGroupSetBits(xEventGroupUser, TASK_USER_WAKEUP);
}



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.
 * @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);
    } else {
	ESP_LOGE(TAG, "screen_main() lock error");
    }
    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, 35) == 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);
	u8g2_SendBuffer(&u8g2);

	xSemaphoreGive(xSemaphoreUnits);
    } else {
	ESP_LOGE(TAG, "screen_unit(%d) lock error", no);
    }
}



/**
 * @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 zeroset", 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");
    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("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    %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);
}



void screen_wifi()
{
    char	buf[65];

    if (xSemaphoreTake(xSemaphoreWiFi, 25) == pdTRUE) {
    	screen_top("WiFi Status");
    	snprintf(buf, 65, "SSID %s", wifi_state->STA_ssid);
    	u8g2_DrawStr(&u8g2, 1, 28, buf);
    	snprintf(buf, 65, "RSSI %d", wifi_state->STA_rssi);
    	u8g2_DrawStr(&u8g2, 1, 43, buf);
    	snprintf(buf, 65, "Online %s", wifi_state->STA_online ? "Yes":"No");
    	u8g2_DrawStr(&u8g2, 1, 59, buf);
	u8g2_SendBuffer(&u8g2);
	xSemaphoreGive(xSemaphoreWiFi);
    } else {
	ESP_LOGE(TAG, "screen_wifi() lock error");
    }
}



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);
}



void screen_network()
{
    if (xSemaphoreTake(xSemaphoreWiFi, 25) == pdTRUE) {
    	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);
        xSemaphoreGive(xSemaphoreWiFi);
    } else {
        ESP_LOGE(TAG, "screen_network() lock error");
    }
}



void screen_network_setup(int sub)
{
    screen_top("Network setup");
    menu_line(sub == 0, 2, 25, "Name %s", config.hostname);
    menu_line(sub == 1, 2, 37, "NTP  %s", config.ntp_server);
    menu_line(sub == 2, 2, 49, "Return");
    u8g2_SendBuffer(&u8g2);
}



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);
}



void screen_mqtt_setup(int sub)
{
    screen_top("MQTT Setup");
    menu_line(sub == 0, 2, 25, "serv %s", config.mqtt_server);
    menu_line(sub == 1, 2, 37, "port %d", config.mqtt_port);
    menu_line(sub == 2, 2, 49, "user %s", config.mqtt_user);
    menu_line(sub == 3, 2, 61, "Return");
    u8g2_SendBuffer(&u8g2);
}



void screen_update()
{
    screen_top("Update firmware");
    menu_line(0, 1, 43, "Push to update");
    u8g2_SendBuffer(&u8g2);
}



void screen_updating(char *m1, char *m2)
{
    screen_top("Updating ...");
    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 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)
{
    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_SETUP_NETWORK:
		ESP_LOGI(TAG, "Loop user: Network setup");
		screen_network_setup(SubMenu);
		break;

            case ML2_MQTT:
                ESP_LOGI(TAG, "Loop user: MQTT");
                screen_mqtt();
                break;

            case ML2_SETUP_MQTT:
                ESP_LOGI(TAG, "Loop user: MQTT setup");
		screen_mqtt_setup(SubMenu);
		break;

            case ML2_UPDATE:
                ESP_LOGI(TAG, "Loop user: Update");
                screen_update();
                break;

	    case ML2_DO_UPDATE:
		ESP_LOGI(TAG, "Loop user: Do 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;
	case ML2_SETUP_MQTT:	if (rotate_to_sub(event.state.position, 0, 3, &SubMenu))
				    screen_mqtt_setup(SubMenu);
				break;
	case ML2_SETUP_NETWORK:	if (rotate_to_sub(event.state.position, 0, 2, &SubMenu))
				    screen_network_setup(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");
    }
}



/*
 * Refresh screens that are in focus. Called by the main measurement loop.
 */
void user_refresh(void)
{
    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;
    }
}



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;
		}
		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);
			ESP_LOGI(TAG, "Zero set unit %d, %d mV set %d", idx, adc_state->Pressure[idx].voltage, units[idx].pressure_zero);
			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_NETWORK:
		New_Loop2 = ML2_SETUP_NETWORK;
		break;

	case ML2_SETUP_NETWORK:
		if (SubMenu == 0) {
                    rotary_editer("Hostname", config.hostname, "", 16, EDIT_TYPE_TEXT);
                    screen_network_setup(SubMenu);
                }
		if (SubMenu == 1) {
                    rotary_editer("NTP server", config.ntp_server, "", 16, EDIT_TYPE_TEXT);
                    screen_network_setup(SubMenu);
                }
		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);
		}
		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);
		}
		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);
		}
		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:
		New_Loop2 = ML2_DO_UPDATE;
		break;

	case ML2_DO_UPDATE:
		bin_update();
		New_Loop2 = ML2_UPDATE;
		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;

    /*
     * 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);
	    screen_main();
	    UserTimer = INACTIVITY;
	    New_Loop2 = ML2_INIT;
	    Main_Loop2 = -1;
	    SubMenu = 0;

	    while (UserTimer) {

		menu_change();
		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);
	}
    }

// 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
}

mercurial