#include "main.h"
#include <string.h>
#include <stdlib.h>
#include "cmsis_os.h"
#include <stdbool.h>

/* Demo includes. */
#include "automation.h"
#include "parcelize.h"
#include "signals.h"
#include "sample_service.h"
#include "actuator_mgr.h"
#include "flash_page.h"
#include "utils.h"
#include "rtc_if.h"
#include <time.h>
#include <math.h>
#include "lora_app.h"

#define NUMBER_OF_VALVES 8
#define TEMP_HYSTERESIS	 2
#define MAX_WATERING_SEQ 100

//volatile uint8_t low_power_on = 0;
bool actuatorRunning[4] = { false };
uint8_t receivedInitialValue = 0;
uint16_t programDuration[4] = {0};
FiniteStateMachine finiteStateMachine;

extern ADC_HandleTypeDef hadc1;

//
static WATERING_OF_THE_DAY_STRUCT wateringList_of_the_day[MAX_WATERING_SEQ] = {0};

WATERING_GENERAL default_settings = {
		DATA_VERSION,
		3,
		2,
		12,
		0, 0,
		1,
		{
				{ 8, false, { 35, 0 }, 1, 0.0 },
				{ 9, false, { 3, 0 }, 2, 0.0 },
				{ 10, true, { 15, 80 }, 4, 0.0 },
				{ 11, true, { 0, 0 }, 0, 0.0 },
				{ 12, true, { 0, 0 }, 0, 0.0 }
		},
		{
				{ 0, true, false, false, { {0, 0, 0, { 10, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 1, true, false, false, { {0, 0, 0, { 20, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 2, true, false, false, { {0, 0, 0, { 20, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 3, true, false, false, { {0, 0, 0, { 5, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 4, true, false, false, { {0, 5, 15, { 0, 20, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 5, true, false, false, { {0, 0, 0, { 0, 20, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 6, true, false, false, { {0, 0, 0, { 0, 0, 5, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 },
				{ 7, true, false, false, { {0, 0, 0, { 0, 0, 0, 30 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }}, {0, 0, 0, { 0, 0, 0, 0 }} }, 90 }
		},
		{
				{ 0, 0x2A, 0, 0, 1, { 480, 0, 0, 0 }, 80, false, 60},
				{ 1, 0x7F, 5, 0, 1, { 585, 0, 0, 0 }, 100, true, 0},
				{ 2, 0x5F, 1, 0, 3, { 420, 540, 660, 0 }, 100, true, 0},
				{ 3, 0x5F, 3, 0, 1, { 360, 0, 0, 0 }, 100, true, 0}
		},
		{ 55, 60, 70, 75, 85, 90, 100, 100, 90, 85, 75, 65 }
};

PARAM_SYS_STRUCT default_paramSys = {
		{ 0, 0, 0, 0 }
};

FLASH_STRUCT Flash;

extern osMutexId flashMutexHandle;
extern osThreadId outputsTaskHandle;

extern RTC_HandleTypeDef hrtc;
extern RTC_AlarmTypeDef sAlarm;
extern IWDG_HandleTypeDef hiwdg;


int8_t getCaseTemperature()
{
	float Avg_Slope = 2.5;
	float V25 = 0.76;

	HAL_ADC_Start(&hadc1); // start the adc
	HAL_ADC_PollForConversion(&hadc1, 100); // poll for conversion
	uint32_t raw = HAL_ADC_GetValue(&hadc1); // get the adc value
	float Vsense = raw * 3.3 / 4095;
	HAL_ADC_Stop(&hadc1); // stop adc
	meas_case_temperature = (((V25 - Vsense) / Avg_Slope) + 25);
	return (int8_t)meas_case_temperature;
}

/*-----------------------------------------------------------*/

uint16_t findValvesStepState( uint8_t step )
{
	uint8_t valves = wateringList_of_the_day[step].valves;
	return valves;
}

uint8_t findAverageFlowRate( uint16_t valvesState )
{
uint16_t avgPumpRate = 0;
uint16_t valvesNbr = 0;

	for( int v = 0; v < 8; v++ )
	{
		if( valvesState & (1<<v) )
		{
			valvesNbr++;
			avgPumpRate += Flash.settings.valves[v].pumpAdjust;
		}
	}
	if( !valvesNbr )
		avgPumpRate = 100;
	else
		avgPumpRate /= valvesNbr;

	return avgPumpRate;
}

void wateringStats( uint16_t month_id )
{
	memset(programDuration,0,sizeof(programDuration));
	for( int p=0; p < 4; p++ )
	{
		// Search for valve running time
		for( int v=0; v < 8; v++ )
		{
			float rt = Flash.settings.valves[v].sequence[0].runTime[p];
			if( (rt>0) && Flash.settings.programs[p].monthlySeasonalAdjust )
			{
				rt = rt * Flash.settings.programs[p].seasonalAdjust / 100;
				rt = rt * Flash.settings.seasonalMonthlyParam[month_id] / 100;
			}
			rt *= Flash.settings.programs[p].nStartTimes;
			programDuration[p] += rt;
		}
	}
}

int findDayDelay( uint8_t st )
{
	return difftime(wateringList_of_the_day[st].jTime,wateringList_of_the_day[0].jTime) / (3600*24);
}

int dailyStepWatering( uint8_t step, char* buffer )
{
	char buff[80];
	struct tm* top_t;

	time_t jtime = wateringList_of_the_day[step].jTime;
	top_t = localtime( &jtime );
	sprintf(buff,"%d: %02d:%02d:%02d",step,top_t->tm_hour,top_t->tm_min,top_t->tm_sec);
	strcat(buffer,(char*)buff);

	int diff_day = findDayDelay(step);
	if( diff_day <= 0 )
		strcpy(buff," ");
	else
		sprintf(buff,"(J+%d) ",diff_day);
	strcat(buffer,(char*)buff);

	strcpy((char*)buff," v");
	for( int v=0; v<8; v++ )
	{
		if( wateringList_of_the_day[step].valves & (1<<v) )
			buff[v+2] = '1' + v;
		else
			buff[v+2] = '-';
	}
	buff[10] = '\0';
	strcat(buffer,(char*)buff);

	return diff_day;
}

void dailyWatering( char* buffer )
{
	int a = 1;

	while( wateringList_of_the_day[a].jTime != 0 )
	{
		dailyStepWatering( a++, buffer );
		strcat(buffer,"\n");
	}
}

int getCycleFromPeriod( SelectDays per )
{
int cycle = 0;

	if( per > OVER_2_DAYS )
		cycle = per - 2;
	return cycle;
}

int dayCycleRemaining( time_t current_t, int pId )
{
int cycle = 0;

	int diffDay = 0;
	cycle = getCycleFromPeriod(Flash.settings.programs[pId].period);
	if( cycle > 0 )
		diffDay = cycle - ((int)(difftime(current_t,Flash.paramSys.currentDayCycle[pId]) / 86400) % (cycle+1));
	return diffDay;
}

/* Routines for programs */
bool isWateringDay( WATERING_PROGRAM* o, struct tm* t )
{
bool ret = false;
time_t current_t;

	uint8_t wday = t->tm_wday;					// Get day of the week
	if( (o->wateringWeekDay & (1<<wday)) > 0 )
	{
		switch(o->period) {
		case PER_EVEN:
			if( t->tm_mday % 2 == 0 )
				ret = true;
			break;
		case PER_ODD:
			if( t->tm_mday % 2 > 0 )
				ret = true;
			break;
		case PER_ODD31:
			if( t->tm_mday % 2 > 0 && t->tm_mday <= 29 )
				ret = true;
			break;
		case OVER_2_DAYS:
		case OVER_3_DAYS:
		case OVER_4_DAYS:
		case OVER_5_DAYS:
			current_t = mktime(t);
			if( dayCycleRemaining(current_t, o->id) == 0 )
				ret = true;
			break;
		default: // PER_SELECT
			ret = true;
			break;
		}
	}

	return ret;
}

int compareWateringList(const void *s1, const void *s2)
{
	WATERING_OF_THE_DAY_STRUCT* e1 = (WATERING_OF_THE_DAY_STRUCT *)s1;
	WATERING_OF_THE_DAY_STRUCT* e2 = (WATERING_OF_THE_DAY_STRUCT *)s2;

	return e1->jTime - e2->jTime;
}

void registeringValves( WATERING_OF_THE_DAY_STRUCT list[] )
{
	int i = 1;
	while( list[i].jTime != 0 )
	{
		if( list[i].tempState )
			list[i].valves = list[i-1].valves | list[i].valves;
		else
			list[i].valves = list[i-1].valves & ~(list[i].valves);
		i++;
	}
}

bool isStepsRunTime( WATERING_SEQUENCE seq[] )
{
	if( seq[0].startYearDay != 0 ) {
		for( int i = 1; i < 4; i++ ) {
			if( seq[i].startYearDay <= seq[i-1].startYearDay ) {
				return false;
			}
		}
		return true;
	}
	return false;
}

uint16_t wateringList( struct tm* current_time, WATERING_OF_THE_DAY_STRUCT list[] )
{
struct tm top_time, start_of_the_day;
time_t top_t, start_of_the_day_t;
uint16_t ti=0;
bool delayedStart;

	// Create sprinkling list of the day
	memset(list, 0, sizeof(wateringList_of_the_day));
	// Get day of the year used with growth steps
	uint8_t yday = current_time->tm_yday;

	// Initialize 1st position of the list with the start of the day
	start_of_the_day = *current_time;
	start_of_the_day.tm_hour = 0;
	start_of_the_day.tm_min = 0;
	start_of_the_day.tm_sec = 0;
	start_of_the_day_t = mktime(&start_of_the_day); // Find the end of the current day
	list[ti++].jTime = start_of_the_day_t;

	top_time = start_of_the_day;

	for( int p=0; p < 4; p++ )
	{
		WATERING_PROGRAM* wp = &Flash.settings.programs[p];
		if( isWateringDay( wp,current_time) )	// Checking if it is or not a watering day
		{
			for( int t=0; (t < wp->nStartTimes)  && (ti < (MAX_WATERING_SEQ-2)); t++ )
			{
				delayedStart = false;
				uint16_t stime = wp->startTimes[t];
				top_time.tm_hour = stime / 60;
				top_time.tm_min = stime % 60;
				top_time.tm_sec = 0;
				top_t = mktime(&top_time); // Watering start time at the current day

				// Search for valve running time
				for( int v=0; (v < 8) && (ti < (MAX_WATERING_SEQ-2)); v++ )
				{
					WATERING_VALVE* wv = &Flash.settings.valves[v];
					int sId = 0;
					if( isStepsRunTime(wv->sequence) )	// Using sequence according to the 4 growth steps (init, development, middle, late)
					{
						sId = 3;
						while( sId >= 0 && wv->sequence[sId--].startYearDay >= yday );
						if( sId < 0 )	// No active valve in this program!
							continue;
					}

					// Calculation of watering runtime
					float rt = wv->sequence[sId].runTime[p];
					if(  rt > 0 )
					{
						if( wp->monthlySeasonalAdjust ) // Applying monthly correction
						{
							rt = rt * wp->seasonalAdjust / 100;
							rt = rt * Flash.settings.seasonalMonthlyParam[current_time->tm_mon] / 100;
						}
						if( wp->valveDelay > 0 )
						{
							if( delayedStart )
							{
								top_t = list[ti-1].jTime;
								top_t += wp->valveDelay;
							}
							else
								delayedStart = true;
						}

						uint16_t cycleTime = wv->sequence[sId].cycleTime;
						if( cycleTime == 0 )	// Just one watering at specific time
						{
							// Calculate watering start time
							list[ti].jTime = top_t;
							list[ti].progId = 'A' + p;
							list[ti].tempState = true;
							list[ti].pumpRate = wv->pumpAdjust;
							list[ti].valves = (1<<v);
							ti++;
							// and end time
							top_t += (uint16_t)(rt * 60);
							list[ti].jTime = top_t;
							list[ti].progId = 'A' + p;
							list[ti].tempState = false;
							list[ti].pumpRate = 90;
							list[ti].valves = (1<<v);
							ti++;
						}
						else // Day cycle
						{
							uint16_t nPeriod = rt / cycleTime;
							cycleTime *= 60;
							uint16_t soakTime = wv->sequence[sId].soakTime * 60 + cycleTime;
							for( int pe=0; pe < nPeriod; pe++ )
							{
								// Calculate watering start time
								time_t start_t = top_t + (pe*soakTime);
								list[ti].jTime = start_t;
								list[ti].progId = 'A' + p;
								list[ti].tempState = true;
								list[ti].pumpRate = wv->pumpAdjust;
								list[ti].valves = (1<<v);
								ti++;
								// and end time
								list[ti].jTime = start_t + cycleTime;
								list[ti].progId = 'A' + p;
								list[ti].tempState = false;
								list[ti].pumpRate = 90;
								list[ti].valves = (1<<v);
								ti++;
							}
						}
					}
				}
			}
		}
	}
	// Sorting
	qsort(list, ti, sizeof(WATERING_OF_THE_DAY_STRUCT), compareWateringList);
	// registering valves in bitfied
	registeringValves( list );

	return ti;
}

WATERING_OF_THE_DAY_STRUCT findFirstStartAtNextWatering( time_t t_day )
{
WATERING_OF_THE_DAY_STRUCT list[MAX_WATERING_SEQ], midnight = {0};
uint16_t size, nLoop = 31;
struct tm* currTime;

	midnight.jTime = t_day + (24*3600);
	do
	{
		t_day += (24*3600);
		currTime = localtime ( &t_day );
		size = wateringList( currTime, list );
		nLoop--;
	} while( size < 2 && nLoop > 0 );

	return ( nLoop > 0 ? list[1] : midnight );
}

uint16_t optimizeWatering( WATERING_OF_THE_DAY_STRUCT list[] )
{
	int i = 1;
	while( list[i].jTime != 0 )
	{
		if( (list[i-1].jTime == list[i].jTime) )
		{
			int cv = list[i-1].valves;
			int ccv = list[i].valves;
			int j = i;
			while( list[j-1].jTime != 0 )
			{
				list[j-1] = list[j];
				j++;
			}
			if( ccv > cv )
				list[i-1].valves |= ccv;
			else
				list[i-1].valves = ccv;
		}
		else
			i++;
	}
	return i;
}

uint16_t initDaylyWateringList()
{
struct tm current_time;
WATERING_OF_THE_DAY_STRUCT nextWatering;
uint16_t ti=0;

	now( &current_time ); // Current date time
	// Create sprinkling list of the day - Return the size of the list
	ti = wateringList( &current_time, wateringList_of_the_day );

	nextWatering = findFirstStartAtNextWatering( wateringList_of_the_day[0].jTime ); // Find first watering start from the given time
	ti = optimizeWatering( wateringList_of_the_day );
	// End the list with the first start of the next watering
	wateringList_of_the_day[ti] = nextWatering;

	// Stats
	wateringStats(current_time.tm_mon);

	return difftime(nextWatering.jTime,wateringList_of_the_day[0].jTime) / (3600*24);
}

void initCurrentDayCycle( void* ptr )
{
	time_t *ldc = (time_t*)(ptr);
	int cycle = 0;

	for( int p=0; p<4; p++ )
	{
		cycle = getCycleFromPeriod(Flash.settings.programs[p].period);
		if( cycle > 0 )
		{
			if( ptr == NULL || ldc[p] != Flash.paramSys.currentDayCycle[p] )
				Flash.paramSys.currentDayCycle[p] = wateringList_of_the_day[0].jTime;
		}
		else
			Flash.paramSys.currentDayCycle[p] = 0;
	}
}

uint8_t findSprinklingStep()
{
struct tm current_time, *new_time;
time_t current_t;
uint8_t s;

	now( &current_time ); // Current date time
	// Find the right sequence in this list
	s = 0;
	current_t = mktime(&current_time);
	while( current_t >= wateringList_of_the_day[s].jTime )
		s++;

	new_time = localtime(&wateringList_of_the_day[s].jTime);

	int diffTime = findDayDelay(s);
	s--;
	if( wateringList_of_the_day[s].valves == 0 )
	{
		APP_LOG(TS_OFF, VLEVEL_M, "No watering until %02d:%02d:%02d", new_time->tm_hour, new_time->tm_min, new_time->tm_sec);
	}
	else
	{
		APP_LOG(TS_OFF, VLEVEL_M, "Watering: v"BYTE_TO_BINARY_PATTERN" until %02d:%02d:%02d",BYTE_TO_BINARY(wateringList_of_the_day[s].valves),
				new_time->tm_hour, new_time->tm_min, new_time->tm_sec);
	}
	if( diffTime > 0 )
		APP_LOG(TS_OFF, VLEVEL_M, " (D+%d)",diffTime);
	APP_LOG(TS_OFF, VLEVEL_M, "\r\n");

	// Set next alarm
	set_alarmA( new_time );

	return s;
}

void Automation_sendCommandToActuators( uint16_t id, ActuatorStates state, uint8_t flowRate )
{
osMessageQId destQueue;

	destQueue = getQueueId(OUTPUTS);

	MSG_STRUCT *p_sendMsg = allocMemoryPoolMessage(); // receiver must free
	p_sendMsg->opcode = OP_OUTPUT_ONCHANGE;
	p_sendMsg->outputs = id;
	p_sendMsg->changeState = state;
	p_sendMsg->pumpRate = flowRate;
	osMessagePut(destQueue, (uint32_t)p_sendMsg, osWaitForever);
}

void mainStatement(ActuatorStates state)
{
	switch( state ) {
	case Z_ALL_OFF:
		finiteStateMachine.stm = APERIODIC;
		APP_LOG(TS_OFF, VLEVEL_M, "Received instruction to stop...\r\n");
		Automation_sendCommandToActuators( ALL_OUTPUTS_OFF, Z_ALL_OFF, 100 );
		stop_alarmA();
		actuator_reset_state();
		break;
	case Z_RESTART:
		updateAUXtimer(AUX_230VAC_PORT);
	case Z_UPDATE:
		finiteStateMachine.stm = PERIODIC;
		APP_LOG(TS_OFF, VLEVEL_M, "Received instruction to update zones\r\n");
		initDaylyWateringList();
		uint8_t step = findSprinklingStep();
		uint16_t vss = findValvesStepState(step);
		uint8_t flow = findAverageFlowRate(vss);
		Automation_sendCommandToActuators( vss, Z_UPDATE, flow );
		APP_LOG(TS_OFF, VLEVEL_M, "Case temperature: %.1f C\r\n", meas_case_temperature);
		actuator_reset_state();
		break;
	case E_STOP:
		finiteStateMachine.stm = ESTOP;
		APP_LOG(TS_OFF, VLEVEL_M, "Received E-Stop...\r\n");
	   	addErrorToList( 0x112 );
		Automation_sendCommandToActuators( ALL_OUTPUTS_OFF, Z_ALL_OFF, 100 );
		stop_alarmA();
		break;
	case UPDATE_SETTINGS:
	case Z_START:
	default:
		break;
	}
}

bool saveSettings( FLASH_STRUCT* data )
{
	uint32_t ret = 0;

	osStatus status = osMutexWait(flashMutexHandle, 5000);
	if (status == osOK)
	{
		APP_LOG(TS_OFF, VLEVEL_M, "Saving settings...\r\n");
		int numofbytes = sizeof( FLASH_STRUCT );
		uint32_t ret = Flash_Write_Data(FLASH_USER_START_ADDR, (__IO uint64_t *)data, numofbytes );
		if( ret != 0 )
			APP_LOG(TS_OFF, VLEVEL_M, "Flash write error %d\r\n",ret);
		osMutexRelease(flashMutexHandle);
	}
	osDelay(50);

	return ret == 0;
}

static void Automation_processMsg(MSG_STRUCT *p_msg)
{
uint8_t zoneTimeUpdate = 0;
osMessageQId destQueue;
MSG_STRUCT *p_sendMsg;
uint8_t num;

	CommandOpcode opcode = p_msg->opcode;

	switch( opcode ) {
	case OP_RTC:
		if( finiteStateMachine.stm != PERIODIC )
			break;
	case OP_MAIN:
		mainStatement(p_msg->changeState);
		break;
	case OP_FEATURE:
		num = p_msg->lParam[0];
		if(  num > 0 && num < 4 )
		{
			WATERING_ACTUATOR* wa = &(Flash.settings.actuators[num-1]);
			//wa->outputNum = num + 8;
			wa->autoRunEnabled = p_msg->lParam[1];
			wa->autoRunFeature = p_msg->lParam[2];
			switch( wa->autoRunFeature ) {
			case AAR_DISABLE :
				wa->setpoints[0] = wa->setpoints[1] = 0;
				break;
			case AAR_OVER :
			case AAR_ABOVE:
				wa->setpoints[0] = p_msg->lParam[3];
				wa->setpoints[1] = 0;
				break;
			case AAR_INRANGE:
			case AAR_OUTOFRANGE:
				wa->setpoints[0] = p_msg->lParam[3];
				wa->setpoints[1] = p_msg->lParam[4];
				break;
			}
			zoneTimeUpdate = 1;
		}
		break;
	case OP_CAN: destQueue = (osMessageQId)getQueueId(BLE_COMM);
	    p_sendMsg = allocMemoryPoolMessage(); // receiver must free
	    p_sendMsg->opcode = OP_CAN;
	    memcpy(p_sendMsg,p_msg,sizeof(MSG_STRUCT));
	    osMessagePut(destQueue, (uint32_t)p_sendMsg, osWaitForever);
		break;
	default:
		break;
	};

	if( zoneTimeUpdate )
	{
		saveSettings( &Flash );
	}
	osDelay( 20 );
}

void updateAUXtimer( uint16_t actuatorId )
{
	struct tm current_time;
	time_t current_t;
	uint16_t local_current_minutes_from_midnight;

	if( Flash.settings.auxStartTime == Flash.settings.auxEndTime ) {
		stop_alarmB();
		if( Flash.settings.auxStartTime != 0 ) {	// Enabled AUX output forever
			actuator_AAR_ON( actuatorId );
            APP_LOG(TS_ON, VLEVEL_M, "AUX220V enabled\r\n");
		}
		else {
			actuator_AAR_OFF( actuatorId );	// else disable AUX output
            APP_LOG(TS_ON, VLEVEL_M, "AUX220V disabled\r\n");
		}
	}
	else {
		now( &current_time ); // Current UTC date time
		current_time.tm_sec = 0;
		// Afficher l'heure locale après modification
		current_t = mktime(&current_time);

		// Calculate minutes past midnight
		local_current_minutes_from_midnight = current_time.tm_hour * 60 + current_time.tm_min;
		actuator_AAR_OFF( actuatorId );
	    uint16_t nextAlarmInMinutes = 0;
	    if (Flash.settings.auxEndTime < Flash.settings.auxStartTime) {
	        // Interval is on two days
	        if (local_current_minutes_from_midnight >= Flash.settings.auxStartTime ||
	        		local_current_minutes_from_midnight < Flash.settings.auxEndTime) {
	        	actuator_AAR_ON( actuatorId );
	        	nextAlarmInMinutes = Flash.settings.auxEndTime - local_current_minutes_from_midnight;
	        }
	        else
	        	nextAlarmInMinutes = Flash.settings.auxStartTime - local_current_minutes_from_midnight;
	    } else {
	        // Interval is on the same day
	        if (local_current_minutes_from_midnight >= Flash.settings.auxStartTime &&
	        		local_current_minutes_from_midnight < Flash.settings.auxEndTime) {
	        	actuator_AAR_ON( actuatorId );
	        	nextAlarmInMinutes = Flash.settings.auxEndTime - local_current_minutes_from_midnight;
	        }
	        else {
	        	if( Flash.settings.auxStartTime < local_current_minutes_from_midnight )
	        		nextAlarmInMinutes = (24*60) - local_current_minutes_from_midnight + Flash.settings.auxStartTime;
	        	else
	        		nextAlarmInMinutes = Flash.settings.auxStartTime - local_current_minutes_from_midnight;
	        }
	    }
	    // Set alarm B from current time
	    current_t += (nextAlarmInMinutes*60);
        struct tm *alarmB = gmtime(&current_t);
        if (alarmB != NULL) {
            APP_LOG(TS_ON, VLEVEL_M, "Alarm B is setting at (GMT%+03d) %s\r",Flash.settings.timeZoneOffset,
            		asctime(alarmB));
            set_alarmB( alarmB );
        }
	}
}

void actuator_reset_state()
{
	for( int id = 0; id < 4; id++ )
		actuatorRunning[id] = false;
}

static void Automation_processFrame()
{
	// Fill up Tank 1 with Tank 2 if low level in Tank 1. Automatic stop of transfer when low level in Tank 2
	actuator_AAR_OUTOFRANGE( PORT2_16A, gh_waterTankLevel[0], 0 );
	// Run greenhouse fan if inside temperature over setpoint
	actuator_AAR_OVER( PORT0_8A, gh_insideTemperature[0], TEMP_HYSTERESIS );
	osDelay( 10 );
}

void vAutomationTask( void *pvParameters )
{
FLASH_STRUCT saved_data;

/* Remove warning for unused parameter */
    (void)pvParameters ;

	osMessageQId myQueueId = getQueueId(AUTOMATION);

	finiteStateMachine.stm = IDLE;

	//check for saved date in EEPROM
	APP_LOG(TS_OFF, VLEVEL_M, "Checking EEPROM for stored data\r\n");
	int numofwords = (sizeof( FLASH_STRUCT )/4)+((sizeof( FLASH_STRUCT )%4)!=0);
	Flash_Read_Data(FLASH_USER_START_ADDR, (__IO uint32_t *)&saved_data, numofwords );
	osDelay(50);
	if( saved_data.settings.versionID != DATA_VERSION ) // Bad version
	{
		saved_data.settings = default_settings;
		initCurrentDayCycle( NULL );
		saveSettings( &saved_data );
	}
	Flash = saved_data;

	// Unlock Outputs and LoRaWAN tasks
	xTaskNotifyGive( outputsTaskHandle );

	// Update AUX timer
	updateAUXtimer(AUX_230VAC_PORT);

	// Update valve status when first powered on
	initDaylyWateringList();
	uint8_t step = findSprinklingStep();
	uint16_t vss = findValvesStepState(step);
	uint8_t flow = findAverageFlowRate(vss);
	Automation_sendCommandToActuators( vss, Z_UPDATE, flow );

	finiteStateMachine.stm = PERIODIC;
	//sendWarnError(0x999);

	while(1) {
		osEvent event;
	    event = osMessageGet(myQueueId, 100);
#ifdef AUTO_RESTART
	    HAL_IWDG_Refresh(&hiwdg);
#endif
	    if( finiteStateMachine.stm != IDLE )
	    	HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

	    if (event.status == osEventMessage) {
	      MSG_STRUCT* p_recvMsg = event.value.p;

	      Automation_processMsg(p_recvMsg);
	      freeMemoryPoolMessage(p_recvMsg);
	    }
	    else if (event.status == osEventTimeout) {
	      Automation_processFrame();
	    }
	}
}
