main/task_tft.c

Sat, 06 Jun 2020 13:28:46 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 06 Jun 2020 13:28:46 +0200
changeset 77
66c77497d86d
parent 76
3ff381bfa469
child 88
7f02dbee58d0
permissions
-rw-r--r--

Changed the recipe database so that it is expandable, version 2. More mash fields and allow 16 steps. Allow 20 Additions. Removed separate mash steps from the state machine, the steps are moved to the runtime data. There is no fixed step number for mashout anymore. There is no fixed step for mash-in anymore, just use the first step and heat to the infusion temperature. After malt add, switch to the normal step temperature. Implemented decoction steps.

/**
 * @file task_tft.c
 * @brief BrewBoard TFT and Touch screen driver for a 320x240 ILI9341 based display.
 *        But because the application is controlled using the touch screen, all the
 *        processing of menus is also found here.
 *        It's the first started task, but it does nothing until the Main_Screen 
 *        variable is set.
 */

#include "config.h"

spi_lobo_device_handle_t	spi;				///< TFT screen SPI handler
spi_lobo_device_handle_t	tsspi = NULL;			///< Touchscreen SPI handler
extern sButton			Buttons[MAXBUTTONS];		///< 40 buttons on a screen.
time_t				now;				///< Current time
time_t				last = 0;			///< Last time
struct tm			timeinfo;			///< Current time structure
char				s_timer[16];			///< Timer string buffer
char				s_top_msg[64];			///< Top message string buffer

extern float			stageTemp;
extern uint16_t			stageTime;
extern uint16_t			TimeWhirlPool;
extern uint32_t			TimeLeft;
extern uint32_t			TimeSpent;
extern uint32_t			SecsCount;
extern uint32_t			pumpTime;
extern uint32_t			TimeBrewing;
extern uint16_t			Steady;
esp_timer_handle_t		timerHandle;			///< Timer handler
extern bool			_NewMinute;
extern bool			_UseHLT;
extern bool			System_TimeOk;
extern const esp_app_desc_t	*app_desc;

static const char		*TAG = "task_tft";

#define SPI_BUS			TFT_HSPI_HOST			///< SPI bus for the TFT, TFT_VSPI_HOST or TFT_HSPI_HOST

extern int			Main_Screen;
extern int			Sub_Screen;
extern int			Old_Screen;
extern int			MLT_pin;
extern int			HLT_pin;
extern int			Pump_pin;
extern DS18B20_State		*ds18b20_state;
extern DRIVER_State		*driver_state;
extern JSON_log			*json_log;
extern SemaphoreHandle_t	xSemaphoreDS18B20;
extern SemaphoreHandle_t	xSemaphoreDriver;
extern SemaphoreHandle_t        xSemaphoreWiFi;
extern WIFI_State               *wifi_state;
extern double			Output;
extern sButton                  Buttons[MAXBUTTONS];

extern int			BoilPower, LastMashStep;
extern char			temp_buf[], logline[], strftime_buf[64];
extern bool			loop, CoolBeep, Resume, pumpRest, updateRuntime;
extern bool			NewMinute, TempReached;
extern uint8_t			MashState;
extern float			temp_MLT, MinMash, MaxMash;
extern uint32_t			power_MLT, power_HLT, counts;


#ifdef CONFIG_TEMP_SENSORS_SIMULATOR
extern float			Fake_MLT;
extern float			Fake_HLT;
#endif



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


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



int init_tft_display(void)
{
    esp_err_t ret;
    esp_timer_create_args_t timerSecond = {
	.callback = &TimerCallback,
	.name = "SecondsTimer"
    };

    ESP_LOGI(TAG, "Initialize TFT");

    max_rdclock = 8000000;
    TFT_PinsInit();

    spi_lobo_bus_config_t buscfg = {
	.miso_io_num=PIN_NUM_MISO,		// set SPI MISO pin
	.mosi_io_num=PIN_NUM_MOSI,		// set SPI MOSI pin
	.sclk_io_num=PIN_NUM_CLK,		// set SPI CLK pin
	.quadwp_io_num=-1,
	.quadhd_io_num=-1,
	.max_transfer_sz = 6*1024,
    };
    spi_lobo_device_interface_config_t devcfg={
	.clock_speed_hz=8000000,                // Initial clock out at 8 MHz
	.mode=0,                                // SPI mode 0
	.spics_io_num=-1,                       // we will use external CS pin
	.spics_ext_io_num=PIN_NUM_CS,           // external CS pin
	.flags=LB_SPI_DEVICE_HALFDUPLEX,        // ALWAYS SET  to HALF DUPLEX MODE!! for display spi
    };
    spi_lobo_device_interface_config_t tsdevcfg={
	.clock_speed_hz=2500000,                //Clock out at 2.5 MHz
	.mode=0,                                //SPI mode 0
	.spics_io_num=PIN_NUM_TCS,              //Touch CS pin
	.spics_ext_io_num=-1,                   //Not using the external CS
    };

    ESP_LOGI(TAG, "TFT pins: miso=%d, mosi=%d, sck=%d, cs=%d", PIN_NUM_MISO, PIN_NUM_MOSI, PIN_NUM_CLK, PIN_NUM_CS);

    ret = spi_lobo_bus_add_device(SPI_BUS, &buscfg, &devcfg, &spi);
    assert(ret == ESP_OK);
    disp_spi = spi;

    // ==== Test select/deselect ====
    ret = spi_lobo_device_select(spi, 1);
    assert(ret == ESP_OK);
    ret = spi_lobo_device_deselect(spi);
    assert(ret == ESP_OK);

    ESP_LOGI(TAG, "SPI: attached display, spi bus: %d, speed: %u, bus uses native pins: %s", 
		    SPI_BUS, spi_lobo_get_speed(spi), spi_lobo_uses_native_pins(spi) ? "true" : "false");

    ESP_LOGI(TAG, "TS pins : miso=%d, mosi=%d, sck=%d, cs=%d", PIN_NUM_MISO, PIN_NUM_MOSI, PIN_NUM_CLK, PIN_NUM_TCS);

    ret=spi_lobo_bus_add_device(SPI_BUS, &buscfg, &tsdevcfg, &tsspi);
    assert(ret == ESP_OK);
    ts_spi = tsspi;

    // ==== Test select/deselect ====
    ret = spi_lobo_device_select(tsspi, 1);
    assert(ret == ESP_OK);
    ret = spi_lobo_device_deselect(tsspi);
    assert(ret == ESP_OK);

    ESP_LOGI(TAG, "SPI: attached TS device, spi bus: %d, speed: %u", SPI_BUS, spi_lobo_get_speed(tsspi));

    // ==== Initialize the Display ====
    TFT_display_init();

    // ---- Detect maximum read speed ----
    max_rdclock = find_rd_speed();

    // ==== Set SPI clock used for display operations ====
    spi_lobo_set_speed(spi, DEFAULT_SPI_CLOCK);
    ESP_LOGI(TAG, "SPI: Max rd speed: %u, changed speed to %u", max_rdclock, spi_lobo_get_speed(spi));

    font_rotate = 0;
    text_wrap = 0;
    font_transparent = 0;
    font_forceFixed = 0;
    gray_scale = 0;
    TFT_setGammaCurve(DEFAULT_GAMMA_CURVE);
    TFT_setRotation(LANDSCAPE);
    TFT_setFont(DEFAULT_FONT, NULL);
    TFT_resetclipwin();

    /*
     * Create a one second periodic timer.
     */
    ret = esp_timer_create(&timerSecond, &timerHandle);
    assert(ret == ESP_OK);
    ret = esp_timer_start_periodic(timerHandle, 1000000);
    assert(ret == ESP_OK);

    return ret;
}



void TimerCallback(void *arg) 
{
    TimeSpent++;
    SecsCount++;
    Steady++;
    TimeBrewing++;
    runtime.TimeBrewing++;
    if ((SecsCount % 60) == 0)
	_NewMinute = true;

    if (TimeLeft) {
	TimeLeft--;
	if (TimeLeft == 5) {
	    SoundPlay(SOUND_TimeOut);
	}
	if ((TimeLeft % 60) == 0) {
	    pumpTime++;
	}
    }
}



void TimerSet(uint32_t seconds)
{
    Steady = TimeSpent = SecsCount = 0;
    TimeLeft = seconds;
}



void TimerShow(uint32_t Time, int X, int Y)
{
    uint8_t		Hours   = (uint8_t)(Time / 3600);
    uint8_t		Minutes = (uint8_t)((Time % 3600) / 60);
    uint8_t		Seconds = (uint8_t)(Time % 60);
    char		msg[32];
    static uint32_t	_oldTime = 0;

    if (Time != _oldTime) {
    	_fg = TFT_GREEN;
    	TFT_setFont(FONT_7SEG, NULL);
    	set_7seg_font_atrib(12, 2, 1, TFT_DARKGREY);
    	snprintf(s_timer, 15, "%02d:%02d:%02d", Hours, Minutes, Seconds);
    	TFT_print(s_timer, X, Y);
	_oldTime = Time;
	snprintf(msg, 31, "{\"timer\":\"%s\"}", s_timer);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }
}



void TopMessage(char *text)
{
    char	msg[96];

    snprintf(s_top_msg, 63, "%s", text);
    _fg = TFT_YELLOW;
    font_transparent = 1;
    TFT_setFont(DEJAVU24_FONT, NULL);
    TFT_fillRect(0, 0, 319, 25, TFT_NAVY);
    TFT_print(s_top_msg, CENTER, 2);
    font_transparent = 0;
    snprintf(msg, 95, "{\"top_msg\":\"%s\"}", s_top_msg);
    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
}



void MLT_info(int x, int y, bool update)
{
    char	ctemp[16], csp[16], cpower[16], msg[96];
    static char	ltemp[16], lsp[16], lpower[16];
    bool	con, cpwr, cpump = false;
    static bool	lon, lpwr, lpump;

    _bg = (color_t){ 48, 48, 48 };
    _fg = TFT_WHITE;
    color_t _led  = { 31,255, 31};
    color_t _pump = {127,175,255};
    color_t _pwr  = {255, 47, 47};

    if (! update) {
	TFT_fillRect(x, y, 178, 90, _bg);
	TFT_drawRect(x, y, 178, 90, _fg);
	TFT_drawFastHLine(x, y + 21, 178, _fg);
	TFT_setFont(DEJAVU18_FONT, NULL);
	TFT_print((char *)"MLT", x + 67, y + 3);
    }

    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
	sprintf(ctemp,  "%7.3f",    driver_state->mlt_pv);
	if (driver_state->mlt_mode) {
	    sprintf(csp,    "%6.2f sp", driver_state->mlt_sp);
	} else {
	    csp[0] = '\0';
	}
	if ((driver_state->mlt_mode == MLT_MODE_BANG) || (driver_state->mlt_mode == MLT_MODE_PID) || (driver_state->mlt_mode == MLT_MODE_EXT)) {
	    sprintf(cpower, "%3d%%",    driver_state->mlt_power);
	} else {
	    cpower[0] = '\0';
	}
	xSemaphoreGive(xSemaphoreDriver);
    }

    con = (MLT_pin) ? true : false;
    if ((con != lon) || (! update)) {
	if (con) {
	    TFT_fillCircle(x + 166, y + 11, 8, _led);
	} else {
	    TFT_fillCircle(x + 166, y + 11, 8, _bg);
	}
	lon = con;
	snprintf(msg, 95, "{\"mlt_led\":\"%s\"}", con ? "1":"0");
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }

    cpump = (Pump_pin) ? true : false;
    if ((cpump != lpump) || (! update)) {
	if (cpump) {
	    TFT_fillCircle(x + 11, y + 11, 8, _pump);
	} else {
	    TFT_fillCircle(x + 11, y + 11, 8, _bg);
	}
	lpump = cpump;
	snprintf(msg, 95, "{\"pump_led\":\"%s\"}", cpump ? "1":"0");
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }

    if (equipment.SSR2 == SSR2_ON_IDLE) {
    	cpwr = (HLT_pin) ? true : false;
        if ((cpwr != lpwr) || (! update)) {
	    if (cpwr) {
		TFT_fillCircle(x + 126, y + 11, 8, _pwr);
	    } else {
		TFT_fillCircle(x + 126, y + 11, 8, _bg);
	    }
	    lpwr = cpwr;
	    snprintf(msg, 95, "{\"hlt_led\":\"%s\"}", cpwr ? "1":"0");
	    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
	}
    }

    if (strcmp(ctemp, ltemp) || (! update)) {
    	TFT_setFont(USER_FONT, "/spiffs/fonts/Grotesk24x48.fon");
    	TFT_print(ctemp, x + 5, y + 23);
	strncpy(ltemp, ctemp, 16);
	snprintf(msg, 95, "{\"mlt_pv\":\"%s\"}", ctemp);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }

    TFT_setFont(DEJAVU18_FONT, NULL);
    if (strcmp(csp, lsp) || (! update)) {
    	TFT_clearStringRect(x + 5, y + 70, (char *)"123.45 sp");
    	TFT_print(csp, x + 5, y + 70);
	strncpy(lsp, csp, 16);
	snprintf(msg, 95, "{\"mlt_sp\":\"%s\"}", csp);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }
    if (strcmp(cpower, lpower) || (! update)) {
    	TFT_clearStringRect(x + 120, y + 70, (char *)"100%");
    	TFT_print(cpower, x + 120, y + 70);
	strncpy(lpower, cpower, 16);
	snprintf(msg, 95, "{\"mlt_power\":\"%s\"}", cpower);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }
}



void HLT_info(int x, int y, bool update, bool small)
{
    char        ctemp[16], csp[16], cpower[16], msg[96];
    static char	ltemp[16], lsp[16], lpower[16];
    bool        con = false;
    static bool lon;
    uint8_t	H;

    _bg = (color_t){ 63, 63, 64 };
    _fg = TFT_YELLOW;
    color_t _led = {255, 47, 47};
    H = (small) ? 70 : 90;

    if (! update) {
	TFT_fillRect(x, y, 178, H, _bg);
	TFT_drawRect(x, y, 178, H, _fg);
	TFT_drawFastHLine(x, y + 21, 178, _fg);
	TFT_setFont(DEJAVU18_FONT, NULL);
	TFT_print((char *)"HLT", x + 67, y + 3);
    }

    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
	sprintf(ctemp, "%7.3f", driver_state->hlt_pv);
	if (driver_state->hlt_mode == HLT_MODE_BANG) {
	    sprintf(cpower, "%3d%%", driver_state->hlt_power);
	} else {
	    cpower[0] = '\0';
	}
	if (driver_state->hlt_mode == HLT_MODE_BANG || driver_state->hlt_mode == HLT_MODE_OFF) {
	    sprintf(csp, "%6.2f sp", driver_state->hlt_sp);
	} else {
	    csp[0] = '\0';
	}
	xSemaphoreGive(xSemaphoreDriver);
    }

    con = (HLT_pin) ? true : false;
    if ((con != lon) || (! update)) {
	if (con) {
	    TFT_fillCircle(x + 166, y + 11, 8, _led);
	} else {
	    TFT_fillCircle(x + 166, y + 11, 8, _bg);
	}
	lon = con;
	snprintf(msg, 95, "{\"hlt_led\":\"%s\"}", con ? "1":"0");
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }

    if (strcmp(ltemp, ctemp) || (! update)) {
	if (small) {
    	    TFT_setFont(USER_FONT, "/spiffs/fonts/DejaVuSans24.fon");
    	    TFT_print(ctemp, x + 40,  y + 25);
	} else {
	    TFT_setFont(USER_FONT, "/spiffs/fonts/Grotesk24x48.fon");
	    TFT_print(ctemp, x + 5,  y + 23);
	}
	strncpy(ltemp, ctemp, 16);
	snprintf(msg, 95, "{\"hlt_pv\":\"%s\"}", ctemp);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }

    H = (small) ? 50 : 70;
    TFT_setFont(DEJAVU18_FONT, NULL);
    if (strcmp(csp, lsp) || (! update)) {
	TFT_clearStringRect(x + 5, y + H, (char *)"123.45 sp");
	TFT_print(csp, x + 5, y + H);
	strncpy(lsp, csp, 16);
	snprintf(msg, 95, "{\"hlt_sp\":\"%s\"}", csp);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }
    if (strcmp(cpower, lpower) || (! update)) {
	TFT_clearStringRect(x + 120, y + H, (char *)"100%");
	TFT_print(cpower, x + 120, y + H);
	strncpy(lpower, cpower, 16);
	snprintf(msg, 95, "{\"hlt_power\":\"%s\"}", cpower);
	ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
    }
}



void update_json(void)
{
    int Hour   = (TimeBrewing / 3600); 
    int Minute = ((TimeBrewing % 3600) / 60);

    if (counts == 0)
	counts = 1;	// Prevent division by zero.

    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
	snprintf(json_log->time, 11, "%02d:%02d", Hour, Minute);
	json_log->mlt_sp = driver_state->mlt_sp;
	json_log->mlt_pv = driver_state->mlt_pv;
	json_log->mlt_power = power_MLT / counts;
	json_log->mlt_tempreached = TempReached ? 1:0;
	json_log->pump_run = driver_state->pump_run;
	json_log->hlt_sp = driver_state->hlt_sp;
	json_log->hlt_pv = driver_state->hlt_pv;
	json_log->hlt_power = power_HLT / counts;
	json_log->event[0] = '\0';
	xSemaphoreGive(xSemaphoreDriver);
    }
}



void TFTstartWS(int client)
{
    char	msg[1024];
    char	mlt_sp[16], mlt_power[16], hlt_sp[16], hlt_power[16];

    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
	if (driver_state->mlt_sp) {
	    snprintf(mlt_sp, 15, "%6.2f sp", driver_state->mlt_sp);
	    snprintf(mlt_power, 15, "%3d%%", driver_state->mlt_power);
	} else {
	    mlt_sp[0] = '\0';
	    mlt_power[0] = '\0';
	}
	if (driver_state->hlt_sp) {
	    snprintf(hlt_sp, 15, "%6.2f sp", driver_state->hlt_sp);
	    snprintf(hlt_power, 15, "%3d%%", driver_state->hlt_power);
	} else {
	    hlt_sp[0] = '\0';
	    hlt_power[0] = '\0';
	}
    	snprintf(msg, 1023, "{\"main\":\"%d\",\"sub\":\"%d\",\"mlt_led\":\"%d\",\"mlt_pv\":\"%7.3f\",\"mlt_sp\":\"%s\",\"mlt_power\":\"%s\"" \
		    ",\"pump_led\":\"%d\",\"hlt_led\":\"%d\",\"hlt_pv\":\"%7.3f\",\"hlt_sp\":\"%s\",\"hlt_power\":\"%s\"" \
		    ",\"timer\":\"%s\",\"top_msg\":\"%s\"}",
		    Main_Screen, Sub_Screen, (MLT_pin) ? 1:0, driver_state->mlt_pv, mlt_sp, mlt_power,
		    (Pump_pin) ? 1:0, (HLT_pin) ? 1:0, driver_state->hlt_pv, hlt_sp, hlt_power,
		    s_timer, s_top_msg);

    	xSemaphoreGive(xSemaphoreDriver);
	ws_server_send_text_client(client, msg, strlen(msg));
    }

}



void task_tft(void *pvParameter)
{
    char	msg[96];

    ESP_LOGI(TAG, "Starting TFT/Touch");

    /*
     * Task loop. Read touchscreen events.
     */
    while (1) {
	/*
	 * Build new screen.
	 */
startover:

	updateRuntime = false;
	if (_NewMinute) {
	    _NewMinute = false;
	    NewMinute = true;
	}

	/*
	 * Timekeeping.
	 * In the WiFi task sntp is started and sets System_TimeOk if the
	 * clock is synced once.
	 */
	time(&now);
	localtime_r(&now, &timeinfo);

	if (Old_Screen != Main_Screen) {

	    if ((Main_Screen == MAIN_MODE_FREE) && ((config.ts_xleft == 0) || (config.ts_ybottom == 0))) {
		Main_Screen = MAIN_MODE_CALIBRATION;
	    }

	    /*
	     * With each screenchange, remove the timer too.
	     */
	    Sub_Screen = 0;
	    snprintf(msg, 95, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen);
	    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));

	    ESP_LOGD(TAG, "Change screen %d to %d", Old_Screen, Main_Screen);
	    _bg = TFT_BLACK;
	    TFT_fillScreen(_bg);
	    TFT_resetclipwin();
	    Buttons_Clear();
	    Old_Screen = Main_Screen;

	    switch (Main_Screen) {
		case MAIN_MODE_FREE:
			TopMessage((char *)"Hoofdmenu");
			MLT_info(71, 26, false);
			if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_HLT_IND)) {
			    HLT_info(71,150, false, false);
			}
			Buttons_Add(  5,  26, 60, 40, (char *)"Hand",  0);
			Buttons_Add(255,  26, 60, 40, (char *)"Auto",  1);
			Buttons_Add(  5, 200, 60, 40, (char *)"Info",  2);
			Buttons_Add(255, 200, 60, 40, (char *)"Tools", 3);
			Buttons_Show();
			break;

		case MAIN_MODE_CALIBRATION:
			Calibration_Init();
			break;

		case MAIN_INFO:
			sprintf(temp_buf, "BrewBoard %s", app_desc->version);
			TopMessage(temp_buf);
			_fg = TFT_YELLOW;
			TFT_setFont(UBUNTU16_FONT, NULL);
			TFT_print((char *)"Written by Michiel Broek (C) 2018-2020\r\n\n", 0, 50);
			//         -------------------------------------
			_fg = TFT_ORANGE;
			TFT_print((char *)"Parts are written by Chris Morgan,\r\n", 0, LASTY);
			TFT_print((char *)"Brett Beauregard, Blake Felt, LoBo,\r\n", 0, LASTY);
			TFT_print((char *)"and David Antliff.\r\n", 0, LASTY);
			ShowInteger(1,140, (char *)"Free memory", (char *)" bytes", esp_get_free_heap_size());
			ShowText(1,158, (char *)"IDF version", (char *)esp_get_idf_version());
			Buttons_Add(130, 200, 60, 40, (char *)"Ok", 0);
			Buttons[0].dark = true;
			Buttons_Show();
			break;

		case MAIN_TOOLS:
			TopMessage((char *)"Tools menu");
			Buttons_Add( 20, 40,120, 40, (char *)"Setup", 0);
			Buttons_Add( 20,120,120, 40, (char *)"Bestanden", 1);
			Buttons_Add(180, 40,120, 40, (char *)"Recepten", 2);
			Buttons_Add(180,120,120, 40, (char *)"Updates", 3);
			Buttons_Add(130, 200, 60, 40, (char *)"Ok", 4);
			Buttons[4].dark = true;
			Buttons_Show();
			break;

		case MAIN_TOOLS_SETUP:
		case MAIN_TOOLS_SETUP_CONFIG:
		case MAIN_TOOLS_SETUP_CO_EDIT:
		case MAIN_TOOLS_SETUP_EQUIPMENT:
		case MAIN_TOOLS_SETUP_EQ_EDIT:
		case MAIN_TOOLS_SETUP_CALIBRATION:
			Setup_Init();
			break;

		case MAIN_TOOLS_SETUP_WIFI:
		case MAIN_TOOLS_SETUP_WIFI_CUR:
		case MAIN_TOOLS_SETUP_WIFI_CON:
		case MAIN_TOOLS_SETUP_WIFI_NEW:
			if (WiFi_Init())
			    goto startover;
			break;

		case MAIN_TOOLS_RECIPE:
		case MAIN_TOOLS_RECIPE_EDIT:
			Recipes_Init();
			break;

		case MAIN_TOOLS_FILES:
		case MAIN_TOOLS_FILES_DIR:
		case MAIN_TOOLS_FILES_RESTORE:
		case MAIN_TOOLS_FILES_BACKUP:
			Files_Init();
			break;

		case MAIN_TOOLS_UPDATES:
			Updates_Init();
			break;

		case MAIN_AUTO_INIT1:
		case MAIN_AUTO_INIT2:
		case MAIN_AUTO_DELAYSTART:
		case MAIN_AUTO_HEATUP:
		case MAIN_AUTO_MASH:
		case MAIN_AUTO_TOBOIL:
		case MAIN_AUTO_BOILING:
		case MAIN_AUTO_COOLING_H:
		case MAIN_AUTO_COOLING_M:
		case MAIN_AUTO_COOLING_C:
		case MAIN_AUTO_WHIRLPOOL9:
		case MAIN_AUTO_WHIRLPOOL7:
		case MAIN_AUTO_WHIRLPOOL6:
		case MAIN_AUTO_WHIRLPOOL2:
		case MAIN_AUTO_DONE:
		case MAIN_AUTO_ABORT:
			if (Automation_Init())
			    goto startover;
			break;

		case MAIN_MANUAL_INIT:
		case MAIN_MANUAL_MAIN:
			if (Manual_Init())
			    goto startover;
			break;

		default:
			break;
	    }
	}

	/*
	 * Update screen
	 */
	switch (Main_Screen) {
	    case MAIN_MODE_FREE:
		MLT_info(71, 26, true);
		if ((equipment.SSR2 == SSR2_HLT_SHARE) || (equipment.SSR2 == SSR2_HLT_IND)) {
		    HLT_info(71, 150, true, false);
		}
		switch (Buttons_Scan()) {
		    case 0:	Main_Screen = MAIN_MANUAL_INIT;	break;
		    case 1:	Main_Screen = MAIN_AUTO_INIT1;	break;
		    case 2:	Main_Screen = MAIN_INFO;	break;
		    case 3:	Main_Screen = MAIN_TOOLS;	break;
		    default:	break;
		}
		if (System_TimeOk && (now != last)) {
		    last = now;
		    //strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
		    strftime(strftime_buf, sizeof(strftime_buf), "%a %e %b %Y %T", &timeinfo);
		    _bg = TFT_BLACK;
		    _fg = TFT_ORANGE;
		    TFT_setFont(DEJAVU18_FONT, NULL);
		    snprintf(msg, 95, " %s ", strftime_buf);
		    TFT_print(msg, CENTER, 125);
		    snprintf(msg, 95, "{\"timer\":\"%s\"}", strftime_buf);	// Fix string termination and only send once/second.
		    ws_server_send_text_clients((char *)"/ws", msg, strlen(msg));
		}
		break;

	    case MAIN_MODE_CALIBRATION:
		Calibration_Loop();
		Main_Screen = MAIN_MODE_FREE;
		break;

	    case MAIN_TOOLS:
		switch (Buttons_Scan()) {
		    case 0:	Main_Screen = MAIN_TOOLS_SETUP;		break;
		    case 1:	Main_Screen = MAIN_TOOLS_FILES;		break;
		    case 2:	Main_Screen = MAIN_TOOLS_RECIPE;	break;
		    case 3:	Main_Screen = MAIN_TOOLS_UPDATES;	break;
		    case 4:	Main_Screen = MAIN_MODE_FREE;		break;
		    default:	break;
		}
		break;

	    case MAIN_TOOLS_SETUP:
	    case MAIN_TOOLS_SETUP_CONFIG:
	    case MAIN_TOOLS_SETUP_CO_EDIT:
	    case MAIN_TOOLS_SETUP_EQUIPMENT:
	    case MAIN_TOOLS_SETUP_EQ_EDIT:
	    case MAIN_TOOLS_SETUP_CALIBRATION:
		Setup_Loop();
		break;

	    case MAIN_TOOLS_SETUP_WIFI:
	    case MAIN_TOOLS_SETUP_WIFI_CUR:
	    case MAIN_TOOLS_SETUP_WIFI_CON:
	    case MAIN_TOOLS_SETUP_WIFI_NEW:
		if (WiFi_Loop())
		    goto startover;
		break;

	    case MAIN_TOOLS_RECIPE:
	    case MAIN_TOOLS_RECIPE_EDIT:
		Recipes_Loop();
		break;

	    case MAIN_TOOLS_FILES:
	    case MAIN_TOOLS_FILES_DIR:
	    case MAIN_TOOLS_FILES_RESTORE:
	    case MAIN_TOOLS_FILES_BACKUP:
		Files_Loop();
		break;

	    case MAIN_TOOLS_UPDATES:
		Updates_Loop();
		break;

	    case MAIN_INFO:
		if (Buttons_Scan() == 0) {
		    Main_Screen = MAIN_MODE_FREE;
		}
		break;

	    case MAIN_AUTO_INIT1:
	    case MAIN_AUTO_INIT2:
	    case MAIN_AUTO_DELAYSTART:
	    case MAIN_AUTO_HEATUP:
	    case MAIN_AUTO_MASH:
	    case MAIN_AUTO_TOBOIL:
	    case MAIN_AUTO_BOILING:
	    case MAIN_AUTO_COOLING_H:
	    case MAIN_AUTO_COOLING_M:
	    case MAIN_AUTO_COOLING_C:
	    case MAIN_AUTO_WHIRLPOOL9:
	    case MAIN_AUTO_WHIRLPOOL7:
	    case MAIN_AUTO_WHIRLPOOL6:
	    case MAIN_AUTO_WHIRLPOOL2:
	    case MAIN_AUTO_DONE:
	    case MAIN_AUTO_ABORT:
		if (Automation_Loop())
		    goto startover;
		break;

	    case MAIN_MANUAL_INIT:
	    case MAIN_MANUAL_MAIN:
		if (Manual_Loop())
		    goto startover;
		break;

	    default:
		break;
	}

	if (updateRuntime) {
	    write_runtime();
	}

	/*
	 * Count power average during brewing.
	 */
	if ((Main_Screen >= MAIN_AUTO_MASH) && (Main_Screen < MAIN_AUTO_DONE)) {
	    if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
		power_MLT += driver_state->mlt_power;
		power_HLT += driver_state->hlt_power;
		counts++;
		xSemaphoreGive(xSemaphoreDriver);
	    }
	}
	
    	if (NewMinute) {
	    /*
	     * Brew logging.
	     */
	    if ((Main_Screen >= MAIN_AUTO_MASH) && (Main_Screen < MAIN_AUTO_DONE)) {
		update_json();
		log_json();
		power_MLT = power_HLT = counts = 0;
	    }
	}

	NewMinute = false;
	vTaskDelay(50 / portTICK_PERIOD_MS);
    }
}

mercurial