Программный модуль для терморегулятора батареи RST-191S. v.1.6



ПО является неотъемлемой частью терморегулятора батареи RST-191S, отдельно потребителю не поставляется и эксплуатируется только в составе устройства.

Фрагмент исходного кода
////////////////////////////////////////////////////////////////

//RST-191S

//v1.6

//application.c

////////////////////////////////////////////////////////////////

/*==============================================================
                          include
==============================================================*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "debug_info.h"

#include "nrf.h"
#include "nrf_delay.h"
#include "nrf_gpio.h"
#include "nrf_soc.h"
#include "app_timer.h"
#include "app_error.h"
#include "app_scheduler.h"
#include "crc16.h"

#include "com_slave.h"
#include "com_master.h"

#include "r4s_lib.h"
#include "r4s_master.h"

#include "param.h"
#include "Application_api.h"
#include "event_base.h"
#include "timers_config.h"
#include "buz_ac.h"
#include "button.h"
#include "button_services.h"
#include "indication.h"
#include "batt_nAA.h"
#include "com_slave_extention.h"

#include "master_control_table.h"
#include "temperature.h"
#include "temperature_service.h"
#include "adv_data_config.h"
#include "sfloat.h"
#include "calendar.h"
#include "soft_ind.h"
#include "pcf8563.h"
#include "display.h"
#include "motor.h"
#include "adv_data_config.h"
#include "adv_data.h"
#include "evcal.h"

/********* DEFINES **************************************************************/
/********* MACROS ***************************************************************/
/********* GLOBAL VARIABLES *****************************************************/
bool        BoolRtcOneSec = false;
uint32_t    AppCurrUtcTime = 0, AppCurrUtcShiftTime = 0;
bool        AppBoolLowBattery = false;
uint32_t    AppCountdownSeconds;//счетчик обратного отсчета секунд в BOOST
uint32_t    AppCountdownSecForNoCalendarTasks;//счетчик обратного отсчета секунд в режиме NO_CALENDAR_TASK
//uint16_t    AppCountdownVentilationMinutes;//счетчик обратного отсчета минут в режиме вентиляции
uint8_t     AppLockCalendarState = LOCK_CALENDAR_STATE;//это значит, что по умолчанию РУЧНОЙ режим без календаря
float       AppTargetTemperature;
bool AppFlagBlinkingNoCalendarTasks = false;//флаг мигания значком календаря AUTO
//Значения по умолчанию, для параметров в долговременной памяти, необходимы при сбросе к заводским настройкам при
// инициализации и при анперинге
param_t  AppParam0 __attribute__((aligned(4))) =
{
    PARAM_VER,            //uint16_t
    UNLOCK_CALENDAR_STATE,//uint8_t
    0,//powerOnCnt //uint8_t
    DEFAULT_PARAM_T_SHIFT,//float
    DEFAULT_PARAM_T_VENTILATION,//float
    0,//reserved uint16_t ctrLifeTestCnt;
    DEFAULT_PARAM_BOOST,//uint16_t
    DEFAULT_PARAM_T_HYSTERESIS,//float
    APP_DEFAULT_MANUAL_MODE_TARGET_TEMPERATURE,//float
    MOSCOW_UTC_SHIFT_TIME,//uint32_t
    0,//global_motor_cnt //uint32_t
    0,//flash_page_erase_cnt //uint16_t
    0,//bool=false VentilationState
    0,0,0, //reserved[3]
    0xFFFF //CRC16
};
uint8_t  AppSubstate = SUBSTATE_0;      //подсостояние state machine
float    AppCurrTemp = 0.;//значение текущей температуры (уже с коррекцией)
param_t  AppParam __attribute__((aligned(4)));
bool     AppLockState = false;         //состояние блокировки кнопок
app_state_t  AppState = APP_START;          //состояние state machine
app_state_t appPrevStateAtForceCalibration;//состояние перед калибровкой, необходимой при неисправности мотора (раннее заклинивание, мотор не доходит до нужного места)
bool appFlagForceCalibration = false;//флаг проведения доп. калибровки при неправильной работе мотора
/*********LOCAL VARIABLES *****************************************************/
static app_state_t  AppStatePrevious = APP_START; //предыдущее состояние state machine
static app_state_t              app_nextstate;                  //следующее состояние сразу после завершения ожидания полного останова мотора
static uint8_t                  app_lock_substate = SUBSTATE_0; //подсостояния нажатий кнопки для блокировки
static bool                     app_in_program_transition = false;//приложение переходит из одной программы в другую
static bool                     app_wait_key_release = false;   //флаг ожидания отжатия кнопки (нужен когда идет переход в другое состояние по времени по долгому нажатию)
static bool                     app_btns_enabled = true;        //кнопки доступны для анализа (полностью вышли из блокировки кнопок)
static uint8_t                  app_button_inactivity_cnt = 0;  //счетчик бездействия кнопок каждые 5с
static btn_data_t 				keyb_data;
static btn_services_data_t		keyb_serv_data;			/* данные состояния сервиса клавиатуры*/
static uint8_t app_param_reset_val = DEFAULT_PARAM_RESET;
static uint8_t app_param_calibration_val = APP_CALIBRATION_OFF;

static float                    app_curr_temp;                  //текущая измеренная температура (еще без сдвига, без коррекции)
static bool                     app_bValveMustMoving = false;   //состояние движения/покоя штока клапана
static bool                     app_bValveStopped = true;       //мотор точно остановился после движения
static int                      app_full_motor_cnt = 0;         //количество импульсов с мотора при полном проходе в одном направлении
static int                      app_curr_motor_cnt = 0;         //текущий счетчик импульсов с мотора, отсчет начинаем с полностью открытого штока
static int                      app_target_motor_cnt = 0;       //целевое значение счетчика мотора
static uint8_t                  app_motor_direction;            //направление движения штока в т.ч. при торможении (остаточное движение)

static bool                     app_timeout_flag = false;				/* флаг timeout-a для app_timer_id*/
static bool                     app_ble_connected = false;

APP_TIMER_DEF(app_timer_id);
APP_TIMER_DEF(led_interval_id);

static uint8_t                  app_battery_charge = 100;

//Это список AC, которые данный мастер запрашивает у ИУ при прямом коннекте с ним
static cm_dev_support_ac_t m_required_ac = {
    .ac = {
        AC_EXTENTION_START_CMD,
        AC_EXTENTION_STOP_CMD
        //а это пока только для календаря - AC_EXTENTION_TEMP_SFLOAT_CMD, поэтому здесь он не нужен
    }
};
/********* FUNCTION PROTOTYPES **************************************************/
uint32_t api_temp_test_proccess(void);
uint32_t api_battery_test_proccess(void); // фукция проверки батареи
void app_start_timer_for_next_motor_regulation(void);
void app_start_motor_regulation(void);
static void api_master_evt_handler(com_master_evt_t * p_evt);
static void master_process_event(mct_link_t *link);
/********* FUNCTIONS IMPLEMENTATION *********************************************/

/********************************************************************************
 *
 ********************************************************************************/
/**@brief 	Обработчик таймера таймаута.
 *
 * @param	none
 *
 * @return 	none
 */
static void app_timeout_timer_handler( void* p_context )
{
    //APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: app_timeout_timer_handler\r\n"RTT_CTRL_RESET);
    app_timeout_flag = true;
    return;
}

static void led_timer_sched_handler(void *p_event_data, uint16_t event_size)
{
    if(app_button_inactivity_cnt == SUBSTATE_0)//5 секунд
    {
        ind_led_light(false);//выключение подсветки после 5 секунд бездействия кнопок
        if(AppState == APP_CLOCK_SET_MIN || AppState == APP_CLOCK_SET_HOUR || AppState == APP_CLOCK_SET_DAY ||
           AppState == APP_CLOCK_BLINKING || AppState == APP_DISPLAY_AdA || AppState == APP_DISPLAY_AdA_1_2)//последние два - при управлении из вне при старте
        {
            //сохраняем время через 5с бездействия
            api_set_calendar_data(&AppCurrUtcTime, &AppCurrUtcShiftTime);
        }
        if(AppState == APP_ADVANCED_MENU || AppState == APP_SELECT_PROGRAM || AppState == APP_STAND_BY)
        {
            //через 5 сек бездействия  ЕСЛИ изменились AppParam сохраняем в долговременной памяти все программы и параметры
            if(memcmp(&param, &AppParam, sizeof(param_t) - sizeof(param.CRC)))
            {
                AppParam.flash_page_erase_cnt++;
                memcpy(&param, &AppParam, sizeof(param_t) - sizeof(param.CRC));
                Param_Save();
                APP_PRINTF(0,"after 5s we saved param:  LockCalendarState = %d\n", param.LockCalendarState);
            }
        }
        if(AppState == APP_SELECT_PROGRAM)
        {
            api_state_change(APP_STAND_BY);
        }
        if(AppState == APP_SELF_TEST)
        {
            //если мотор работает
            if(app_bValveMustMoving || !app_bValveStopped)
            {
                app_timer_stop(led_interval_id);//перестаем контролировать время до остановки мотора
                return;
            }
        }
    }
    else if(app_button_inactivity_cnt == SUBSTATE_1)//10 секунд
    {
        if(AppState == APP_SELF_TEST)
        {
            //выход по таймауту из self-test
            ht9b92_display_on();
            soft_ind_UI(IND_SET);
            app_timer_start( app_timer_id, APP_TIMER_TICKS(DISPLAY_SYMBOLS_UI_AT_LCD_TICK_MS, APP_TIMER_PRESCALER), NULL );
            api_state_change(APP_TEST);
        }
    }
    else if(app_button_inactivity_cnt == SUBSTATE_2)
    {
        //через 15с бездействия выходим из режима
        if(AppState == APP_CLOCK_SET_MIN || AppState == APP_CLOCK_SET_HOUR || AppState == APP_CLOCK_SET_DAY)
        {
            if(app_full_motor_cnt == 0)//если до калибровки
            {
                api_state_change(APP_CLOCK_BLINKING);
            }
            else
            {
                api_state_change(APP_STAND_BY);
            }
        }                
        if(AppState == APP_ADVANCED_MENU)
        {
            api_state_change(APP_STAND_BY);
        }
    }
    else 
    {
        if(app_button_inactivity_cnt >= TICKS_FOR_LOCK_AFTER_INACTIVITY_10_MIN - 1) //-1 так как app_button_inactivity_cnt++ у нас стоит после - в конце
        {
            app_timer_stop(led_interval_id);
            AppLockState = true;
            display_write_symbol_data(SYMB_T14_CHILD_LOCK, AppLockState);
        }
    }
    app_button_inactivity_cnt++;
}

static void led_timer_handler(void* p_context)
{
    app_sched_event_put(NULL, 0, led_timer_sched_handler);
}

////////////////////////////////////////////////////////////////
/**
 *  @brief  Функция вызывается при срабатывании часов (1 сек)
 *
 *  @param  none
 *  @return none
 *
 */
static void calendar_timer_handle(void){
    uint32_t err_code;
    
    err_code = evcal_one_sec_handler();
    APP_ERROR_CHECK(err_code);
}
/**
 *  @brief  Функция вызывается при срабатывании событий из календаря
 *
 *  @param  none
 *  @return none
 *
 */
static void evcal_calendar_task(evcal_info_packet_t const*p_data){
    APP_PRINTF(0,"evcal callback -> evcal_calendar_task()\n");
    //добавлена защита от срабатывания задач календаря при заблокированном календаре и в случае состояния Ошибки
    if(p_data == NULL || AppLockCalendarState == LOCK_CALENDAR_STATE || AppState == APP_ERROR || 
                         AppState == APP_BOOST || AppState == APP_DISPLAY_AdA || AppState == APP_DISPLAY_AdA_1_2)
    {
        APP_PRINTF(0,"return as p_data == NULL || AppLockCalendarState == LOCK_CALENDAR_STATE || AppState == APP_ERROR || AppState == APP_BOOST etc \n");
        return;
    }
    uint8_t action_code = p_data->array[1];
    
    switch(action_code)
    {
        case AC_EXTENTION_START_CMD:
        {
            exten_req_start_t *p_in_ext_pckt = (exten_req_start_t *) &p_data->array[2];
            if((p_in_ext_pckt->mask & 0x1) != 0){
                app_state_change_after_stop(APP_OP_STATE);
            }
            break;
        }
        case AC_EXTENTION_STOP_CMD:
        {
            exten_req_stop_t *p_in_ext_pckt = (exten_req_stop_t *) &p_data->array[2];
            if((p_in_ext_pckt->mask & 0x1) != 0){
                app_state_change_after_stop(APP_OFF_STATE);
            }
            break;
        }
        case AC_EXTENTION_TEMP_SFLOAT_CMD:
        {
            exten_req_temp_sfloat_cmd_t* reg_temp_sfloat = (exten_req_temp_sfloat_cmd_t*)&p_data->array[2];
            
            float temp_set = SFloat16ToFloat(reg_temp_sfloat->param1);
#ifdef DEBUG
            char s_temp[6];
            sprintf(s_temp,"%0.1f", temp_set);
#endif
            if(temp_set < MIN_REG_TEMPERATURE || temp_set > MAX_REG_TEMPERATURE)
            {
#ifdef DEBUG
                APP_PRINTF(0,"return as temp_set = %s -> it is out of range\n", s_temp);
#endif
                return;
            }
#ifdef DEBUG
            APP_PRINTF(0,"AppCurrUtcTime=%d, AC temp_set = %s\n", AppCurrUtcTime, s_temp);
#endif
            AppParam.TargetTemperature = AppTargetTemperature = temp_set;
            app_state_change_after_stop(APP_STAND_BY);//в APP_STAND_BY можно входить и повторно с полной инициализацией
            break;
        }
        default:
        {
            break;
        }
    }
    return;
}

/**@brief 	Обработчик таймера измерения температуры и заряда.
 *
 * @param	none
 *
 * @return 	none
 */
static void app_temp_data_handler( float *p_temp )
{
    AppCurrTemp = *p_temp + AppParam.A1_T_shift;
    if(AppState == APP_STAND_BY || AppState == APP_VENTILATION || AppState == APP_BOOST ||
         AppState == APP_OP_STATE || AppState == APP_OFF_STATE)
    {
        soft_ind_current_temp(AppCurrTemp, IND_SET);
    }
    if(api_temp_test_proccess() != NRF_SUCCESS)
    {
        api_state_change_to_error(APP_API_TEMPERATURE_SENSOR_ERROR);
    }
    
    app_battery_charge = batt_nAA_get_charge();
	
#ifdef DEBUG	
	char s_temp[40];
	sprintf(s_temp,"[BAT]: BAT = %d,temp = %0.1f \r\n",app_battery_charge, AppCurrTemp);
	APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_GREEN"%s"RTT_CTRL_RESET,s_temp);
#endif
	if(app_battery_charge <= MIN_BATTERY_CHARGE_LEVEL && !AppBoolLowBattery)
	{
		ind_state_change(IND_LOW_BATTERY_START);
	}
	if(app_battery_charge > MIN_BATTERY_CHARGE_LEVEL && AppBoolLowBattery)
    {
		ind_state_change(IND_LOW_BATTERY_STOP);
    }
	return;
}
////////////////////////////////////////////////////////////////
void app_init_rtc() {
    uint32_t err_code;
    uint32_t utc;// = 0x5A785640;

    err_code = pcf8563_init();
    APP_ERROR_CHECK(err_code);

    //pcf8563_set_time_utc(&utc);
    err_code = pcf8563_get_time_utc(&utc);
    if (err_code == NRF_SUCCESS) 
    {
//        calendar_set_time(utc, MOSCOW_UTC_SHIFT_TIME);//+3 часа по умолчанию для Москвы
        calendar_set_time(utc, AppParam.utcShift);//восстанавливаем тайм зону
        APP_PRINTF(0,"Restore time utc: %X, shift: %X\r\n", utc, AppParam.utcShift);
    }
    else if(err_code == NRF_ERROR_INVALID_DATA)
    {
        utc = 0;
        //err_code = 
        pcf8563_set_time_utc(&utc);
//        if(err_code != NRF_SUCCESS)
//            api_state_change_to_error(APP_API_RTC_ERROR);
    }
//    else 
//    {
//        api_state_change_to_error(APP_API_RTC_ERROR);
//    }
}
uint32_t app_rtc_update(void)
{
    #define MIN_TIME_DIFFERENCE     1   //если время nordic отличается от RTC более чем на 1 секунду, то синхронизируем
    uint32_t curutc, curshift, utc;
    static uint32_t secCnt = 0;//счетчик секунд 
    uint32_t err_code = NRF_SUCCESS;
    
    if(BoolRtcOneSec)
    {
        BoolRtcOneSec = false;
        if(++secCnt > TIME_CHECK_POINT_IN_SEC)
        {
            secCnt = 0;
            api_get_calendar_data(&curutc, &curshift);
            err_code = pcf8563_get_time_utc(&utc);
            if (err_code == NRF_SUCCESS) 
            {
                //Проверка ухода времени nordic от RTC
                if(abs((int32_t)utc - (int32_t)curutc) >= MIN_TIME_DIFFERENCE)
                {
                    api_set_calendar_data(&utc, &curshift);
                    APP_PRINTF(0,"Restored and synchronized utc time=%X (old curutc=%X)\r\n", utc, curutc);
                }
#ifdef DEBUG                
                else
                {
                    APP_PRINTF(0,"No difference between nordic and RTC utc time: %X, shift=%X\r\n", utc, curshift);
                }
#endif                
            } 
            else 
            {
                APP_ERROR_CHECK(err_code);
            }    
        }
    }
    return err_code;
}

void application_init(void)
{
    uint32_t err_code;
    
    //задаем адрес от куда считывать данные о клавиатуре
    err_code = button_services_set_address(&keyb_data);
    APP_ERROR_CHECK(err_code);

    APP_ERROR_CHECK(calendar_set_one_sec_callback(calendar_timer_handle)); //установка callback часов
    APP_ERROR_CHECK(evcal_set_callback(evcal_calendar_task)); //установка callback при срабатывании расписания

    err_code = app_timer_create( &app_timer_id, APP_TIMER_MODE_SINGLE_SHOT, app_timeout_timer_handler );
    APP_ERROR_CHECK( err_code );
    //по первому прерыванию 5с выключаем подсветку и т.д.
    APP_ERROR_CHECK(app_timer_create( &led_interval_id, APP_TIMER_MODE_REPEATED, led_timer_handler ));

    err_code = temperature_service_init(&app_curr_temp, app_temp_data_handler);
    APP_ERROR_CHECK(err_code);

    if(Param_Init() == true)
    {
        //восстанавливаем необходимые параметры из долговременной памяти
        memcpy(&AppParam, &param, sizeof(param_t));
    }
    else
    {
        //устанавливаем начальные значения по умолчанию
        memcpy(&AppParam, &AppParam0, sizeof(param_t));
        memcpy(&param,    &AppParam0, sizeof(param_t));
        Param_Save();
    }
    AppLockCalendarState = AppParam.LockCalendarState;
    APP_PRINTF(0, "application_init(): AppLockCalendarState = %d, AppParam.powerOnCnt=%d, AppParam.ctrLifeTestCnt = %d\n", AppLockCalendarState,
        AppParam.powerOnCnt, AppParam.ctrLifeTestCnt);
    memcpy(&AppTargetTemperature, &AppParam.TargetTemperature, sizeof(float));
    AppCurrTemp = temp_read_val() + AppParam.A1_T_shift;//коррекция температуры, первый раз надо здесь делать
    if(AppParam.powerOnCnt <= MAX_POWER_ON_CNT_FOR_MASS_FW_UPDATE)//чтобы досчитал до MAX + 1 = 4
    {
        AppParam.powerOnCnt++;//счетчик включения питания/перепрошивки
    }
    soft_ind_set_all(IND_SET);
    app_timer_start( app_timer_id, APP_TIMER_TICKS(DISPLAY_ALL_LCD_SEG_TICK_MS, APP_TIMER_PRESCALER), NULL );
    
    com_master_set_required_ac(&m_required_ac);
    com_master_set_event_handler(api_master_evt_handler);
    com_master_set_internal_action_handler(master_process_event);
}

/**
* 	@brief	Открытие сессии/транзакции в календаре
*
*	@param	type read/write
*
*	@return возвращает код ошибки
*
*/
uint8_t api_add_trans_in_calendar(uint8_t type)
{
	if(type!=0 && type!=1) return ERSP_ERROR_INVALID_PARAM;
		
	uint32_t err_code = evcal_add_transaction((!type)?(EVCAL_TRANSACTION_READ):(EVCAL_TRANSACTION_WRITE));
	switch (err_code)
	{
		case NRF_SUCCESS:
		{
			return ERSP_SUCCESS;
		}
		case NRF_ERROR_INVALID_PARAM:
		{
			return ERSP_ERROR_INVALID_PARAM;
		}
		case DRIVER_MODULE_NOT_INITIALZED:
		{
			return ERSP_ERROR_INTERNAL;
		}
		case NRF_ERROR_FORBIDDEN:
			return  ERSP_ERROR_FORBIDDEN;
	}
	return ERSP_ERROR_UNKNOWN;

}
/**
* 	@brief	Закрытие сессии в календаре
*
*	@param	task commit/rollback
*
*	@return возвращает код ошибки
*
*/
uint8_t api_dec_trans_in_calendar(uint8_t task)
{
	if(task!=0 && task!=1) return ERSP_ERROR_INVALID_PARAM;
		
	uint32_t err_code = evcal_dec_transaction((!task)?(EVCAL_COMMIT_TRANSACTION):(EVCAL_ROLLBACK_TRANSACTION));
	switch (err_code)
	{
		case NRF_SUCCESS:
		{
			return ERSP_SUCCESS;
		}
		case NRF_ERROR_INVALID_PARAM:
		{
			return ERSP_ERROR_INVALID_PARAM;
		}
		case DRIVER_MODULE_NOT_INITIALZED:
		{
			return ERSP_ERROR_INTERNAL;
		}
		case NRF_ERROR_FORBIDDEN:
			return  ERSP_ERROR_FORBIDDEN;
	}
	return ERSP_ERROR_UNKNOWN;

}

uint8_t api_erase_evcal(void)
{
	if(evcal_erase_all_tasks() == NRF_SUCCESS){
		return ERSP_SUCCESS;
	} else {
		return ERSP_ERROR_INTERNAL;
	}
}

/********************************************************************************
 *
 ********************************************************************************/
/**@brief 	Функция выполняет работу при выходе из соответствующего состояния
 *
 * @param	none
 *
 * @return 	none
 */
void application_init_prew_state( app_state_t app_new_state )
{
	static app_state_t    			app_prew_state = APP_START;
	
	if( app_prew_state == app_new_state )return;

	switch( app_prew_state )
	{
		case APP_TEST:
		{
			APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: APP_TEST >>\r\n"RTT_CTRL_RESET);
			break;
		}
		case APP_STAND_BY:
		{
			APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: APP_STAND_BY>>\r\n"RTT_CTRL_RESET);
			break;
		}
        case APP_CLOCK_SET_MIN:
        case APP_CLOCK_SET_HOUR:
        case APP_CLOCK_SET_DAY:
            soft_ind_set_all(IND_CLR);
            break;
        case APP_PAIRING:
        {
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: APP_PAIRING>>\r\n"RTT_CTRL_RESET);
            app_advertising_unset_paring_flag();
            com_master_pairing_stop();
            break;
        }
        case APP_PAIRING_ERROR:
            soft_ind_current_temp(0, IND_CLR);
            break;
        default:
        {
            break;
        }
    }
    app_prew_state = app_new_state;
    return;
}

/**@brief 	Функция выполняет работу при входе в новое состояние
 *
 * @param	none
 *
 * @return 	none
 */
void application_init_new_state( app_state_t app_new_state )
{
    uint32_t err_code;

    static app_state_t  app_prew_state = APP_START;

    APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_RED"[APPL]: >>APP SET NEW STATE - %d\r\n"RTT_CTRL_RESET, api_get_state()); // Отладка стейта выдаваемого для приложения

    if( app_prew_state == app_new_state && app_new_state != APP_STAND_BY)return;

    switch( app_new_state )
    {
        case APP_START:
        {
            break;
        }
        case APP_MASS_FW_UPDATE:
        {
            AppParam.powerOnCnt = 0;//сбрасываем счетчик при входе в режим массовой перепрошивки
            soft_ind_set_all(IND_CLR);
            soft_ind_FU(IND_SET);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_MASS_FW_UPDATE\n"RTT_CTRL_RESET);
            AppParam.flash_page_erase_cnt++;
            memcpy(&param, &AppParam, sizeof(param_t) - sizeof(param.CRC));
            Param_Save();
            break;
        }
        case APP_SELF_TEST:
        {
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_SELF_TEST\r\n"RTT_CTRL_RESET);
            break;
        }
        case APP_TEST:
        {
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_TEST\r\n"RTT_CTRL_RESET);
            break;
        }
        case APP_CLOCK_BLINKING:
            ind_state_change(IND_CLOCK_BLINKING);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_CLOCK_BLINKING\r\n"RTT_CTRL_RESET);
            break;
        case APP_CLOCK_SET_MIN:
            soft_ind_set_all(IND_CLR);
            ind_state_change(IND_CLOCK_SET_MIN);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_CLOCK_SET_MIN\r\n"RTT_CTRL_RESET);
            break;
        case APP_CLOCK_SET_HOUR:
            ind_state_change(IND_CLOCK_SET_HOUR);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_CLOCK_SET_HOUR\r\n"RTT_CTRL_RESET);
            break;
        case APP_CLOCK_SET_DAY:
            ind_state_change(IND_CLOCK_SET_DAY);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_CLOCK_SET_DAY\r\n"RTT_CTRL_RESET);
            break;
        case APP_DISPLAY_AdA:
            ind_state_change(IND_DISPLAY_AdA);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_DISPLAY_AdA\r\n"RTT_CTRL_RESET);
            break;
        case APP_DISPLAY_AdA_1_2:
            app_timer_stop(app_timer_id);
            app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
            app_full_motor_cnt = 0;
            api_motor_move(CLOSE_VALVE);
            AppSubstate = SUBSTATE_0;
            ind_state_change(IND_DISPLAY_AdA_1);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_DISPLAY_AdA_1_2\r\n"RTT_CTRL_RESET);
            break;
        case APP_STAND_BY:
        {
            app_timer_stop(app_timer_id);
            soft_ind_set_all(IND_CLR);
            display_write_symbol_data(SYMB_T6_INDOOR_T, IND_SET);
            display_write_symbol_data(SYMB_T7_SET_T_SET, IND_SET);
            display_write_symbol_data(SYMB_T24_VALVE_TOP, IND_SET);
            display_write_symbol_data(SYMB_T16_VALVE_BOTTOM, IND_SET);
            soft_ind_current_temp(AppCurrTemp, IND_SET);//чтобы не ждать 10(или 60) секунд
            ind_state_change(IND_APP_STANDBY);
            if(AppLockCalendarState == UNLOCK_CALENDAR_STATE)
            {
                display_write_symbol_data(SYMB_T12_AUTOMATIC_MODE, IND_SET);
            }
            soft_ind_target_temp(AppTargetTemperature, IND_SET);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_STAND_BY\n"RTT_CTRL_RESET);
            AppSubstate = SUBSTATE_1;
            app_start_timer_for_next_motor_regulation();
            break;
        }
//        case APP_NO_CALENDAR_TASKS:
//            AppCountdownSeconds = APP_BLINKING_AUTO;
//            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_NO_CALENDAR_TASKS\n"RTT_CTRL_RESET);
//            ind_state_change(IND_NO_CALENDAR_TASKS);
//            break;
        case APP_SELECT_PROGRAM:
            app_timer_stop(app_timer_id);
            AppSubstate = SUBSTATE_0;
            ind_state_change(IND_PROGRAM_ADVANCED);//IND_PROGRAM_P0);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_SELECT_PROGRAM\n"RTT_CTRL_RESET);
            break;
        case APP_ADVANCED_MENU:
            AppSubstate = SUBSTATE_0;
            app_param_reset_val = DEFAULT_PARAM_RESET;
            ind_state_change(IND_ADVANCED_MENU);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_ADVANCED_MENU\n"RTT_CTRL_RESET);
            break;
        case APP_PARAMS_RESET:
            memcpy(&AppParam.A1_T_shift, &AppParam0.A1_T_shift, ((uint8_t*)&AppParam0.TargetTemperature/*Programms*/ - (uint8_t*)&AppParam0.A1_T_shift));
            //быть внимательнее если будет меняться структура параметров по умолчанию
            AppParam.VentilationState = AppParam0.VentilationState;//исправление бага
        
            soft_ind_set_all(IND_SET);
            app_timer_start( app_timer_id, APP_TIMER_TICKS(DISPLAY_ALL_LCD_SEG_TICK_MS, APP_TIMER_PRESCALER), NULL );
            break;
        case APP_VENTILATION:
            app_timer_stop(app_timer_id);
            AppSubstate = SUBSTATE_1;
//            AppCountdownVentilationMinutes = AppParam.A2_time_ventilation;
            soft_ind_set_all(IND_CLR);
            display_write_symbol_data(SYMB_T23_VENTIALTION_MODE, IND_SET);
            soft_ind_current_temp(AppCurrTemp, IND_SET);//чтобы не ждать 10(или 60) секунд
            soft_ind_target_temp(AppParam.TargetTemperature, IND_SET);
            display_write_symbol_data(SYMB_T24_VALVE_TOP, IND_SET);
            display_write_symbol_data(SYMB_T16_VALVE_BOTTOM, IND_SET);
            ind_state_change(IND_VENTILATION);
            if(AppLockCalendarState == UNLOCK_CALENDAR_STATE)
            {
                display_write_symbol_data(SYMB_T12_AUTOMATIC_MODE, IND_SET);
            }
            app_set_valve_closed_percent(APP_DEFAULT_VENTILATION_VALVE_POSITION_PERCENT);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_VENTILATION\n"RTT_CTRL_RESET);
            break;
        case APP_OP_STATE:
        case APP_OFF_STATE:
        case APP_BOOST:
        {
            app_timer_stop(app_timer_id);
            soft_ind_set_all(IND_CLR);
            display_write_symbol_data(SYMB_T24_VALVE_TOP, IND_SET);
            display_write_symbol_data(SYMB_T16_VALVE_BOTTOM, IND_SET);
            soft_ind_current_temp(AppCurrTemp, IND_SET);//чтобы не ждать 10(или 60) секунд
            switch(app_new_state)
            {
                case APP_OP_STATE:
                    api_motor_move(OPEN_VALVE);
                    ind_state_change(IND_MOTOR_OP);
                    APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_OP_STATE\r\n"RTT_CTRL_RESET);
                    break;
                case APP_OFF_STATE:
                    api_motor_move(CLOSE_VALVE);
                    ind_state_change(IND_MOTOR_OFF);
                    APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_OFF_STATE\r\n"RTT_CTRL_RESET);
                    break;
                case APP_BOOST:
                    api_motor_move(OPEN_VALVE);
                    app_in_program_transition = true;//переходное состояние из одной программы в другую (пока только в Boost)
                    AppCountdownSeconds = AppParam.A4_boost;
                    ind_state_change(IND_MOTOR_BOOST);
                    APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_BOOST\r\n"RTT_CTRL_RESET);
                    break;
                default:
                    break;
            }
            if(AppLockCalendarState == UNLOCK_CALENDAR_STATE)
            {
                display_write_symbol_data(SYMB_T12_AUTOMATIC_MODE, IND_SET);
            }
            app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
            AppSubstate = SUBSTATE_0;//главное что не SUBSTATE_2 - переход к следующему состоянию после останова
            break;
        }
        case APP_PAIRING:
        {
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_PAIRING\r\n"RTT_CTRL_RESET);
            err_code = app_timer_stop(app_timer_id);
            APP_ERROR_CHECK( err_code );
            
            app_timeout_flag = false;
        
            err_code = app_timer_start(app_timer_id,APP_TIMER_TICKS(APP_PAIRING_TIMEOUT_MS, APP_TIMER_PRESCALER), NULL );
            APP_ERROR_CHECK( err_code );
            
            ind_state_change(IND_PAIRING);
            com_master_pairing_start();
            
            app_advertising_set_paring_flag();
            break;
        }
        case APP_PAIRING_SUCCESS:
            err_code = app_timer_stop(app_timer_id);//чтобы остановить контроль ошибки перинга по таймауту
            APP_ERROR_CHECK( err_code );
            ind_state_change(IND_PAIRING_SUCCESS);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_PAIRING_SUCCESS\r\n"RTT_CTRL_RESET);
            break;
        case APP_PAIRING_ERROR:
            err_code = app_timer_start(app_timer_id,APP_TIMER_TICKS(APP_PAIRING_ERROR_MS, APP_TIMER_PRESCALER), NULL );
            APP_ERROR_CHECK( err_code );
            ind_state_change(IND_PAIRING_ERROR);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_PAIRING_ERROR\r\n"RTT_CTRL_RESET);
            break;
        case APP_UNPAIRING:
        {
            err_code = app_timer_stop(app_timer_id);//чтобы остановить контроль ошибки перинга по таймауту
            APP_ERROR_CHECK( err_code );
            app_timeout_flag = false;
            err_code = app_timer_start(app_timer_id,APP_TIMER_TICKS(APP_UNPAIRING_MS, APP_TIMER_PRESCALER), NULL );
            APP_ERROR_CHECK( err_code );
            ind_state_change(IND_UNPAIRING);
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_UNPAIRING\r\n"RTT_CTRL_RESET);
            
            //события очистки при анперинге
            evcal_clear_transactions();//сброс событий календаря
            //события календаря (до этого уже должны быть очищены все транзакции)
            api_add_trans_in_calendar(EVCAL_TRANSACTION_WRITE);
            api_erase_evcal();
            api_dec_trans_in_calendar(EVCAL_COMMIT_TRANSACTION);
            
            //эти счетчики не надо сбрасывать
            AppParam0.global_motor_cnt = AppParam.global_motor_cnt;
            AppParam0.flash_page_erase_cnt = AppParam.flash_page_erase_cnt;
            AppParam0.powerOnCnt = AppParam.powerOnCnt;

            memcpy(&AppParam, &AppParam0, sizeof(param_t));//возвращение к зав.настройкам, сохранение во flash будет через 5 с
            AppLockCalendarState = AppParam.LockCalendarState;
            memcpy(&AppTargetTemperature, &AppParam.TargetTemperature, sizeof(float));
            AppCurrTemp = app_curr_temp + AppParam.A1_T_shift;//коррекция температуры, первый раз надо здесь делать тоже

            com_master_unpairing();
            com_slave_unpair_proccess();
            //api_param_update();
            break;
        }
#ifdef NRF51
		case APP_SLAVE_FW_UPDATE:
		{
			APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_SLAVE_FW_UPDATE\r\n"RTT_CTRL_RESET);

			err_code = app_timer_stop(app_timer_id);
			APP_ERROR_CHECK( err_code );
			
			app_timeout_flag = false;

			err_code = app_timer_start(app_timer_id,APP_TIMER_TICKS(APP_FW_UPDATE_TIMEOUT_MS, APP_TIMER_PRESCALER), NULL );
			APP_ERROR_CHECK( err_code );
			
			break;
		}
#endif
		case APP_ERROR:
		{
			APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_ERROR err=%d\r\n"RTT_CTRL_RESET, AppSubstate);
            switch(AppSubstate)
            {
                case APP_API_MOTOR_ERROR:
                    ind_state_change(IND_ERROR_MOTOR);
                    break;
                case APP_API_TEMPERATURE_SENSOR_ERROR:
                    ind_state_change(IND_ERROR_TEMPERATURE_SENSOR);
                    break;
//                case APP_API_RTC_ERROR:
//                    ind_state_change(IND_ERROR_RTC);
//                    break;
            }
			break;
		}
		default:
		{
			break;
		}
	}
    //чтобы не возвращаться из состояний PAIRING_SUCCESS или PAIRING_ERROR снова в APP_PAIRING, а возвращаться
    // в состояние перед началом перинга
    if(app_prew_state != APP_PAIRING)
    {
        AppStatePrevious = app_prew_state;//запоминаем состояние перед блокировкой, чтобы потом в него вернуться
        APP_PRINTF(0, "AppStatePrevious = %d\n", AppStatePrevious);
    }
	app_prew_state = app_new_state;

    {
        uint8_t adv_app_state;
        uint16_t adv_app_state_param;// = ADV_STAND_BY_PRM_NO;
        //конвертер - настраивается в каждом старом проекте индивидуально
        //adv_app_state = api_get_state();
//        if(AppState == APP_FW_UPDATE_STATE)
//        {
//            adv_app_state = ADV_ST_STAND_BY;
//            adv_app_state_param = ADV_STAND_BY_PRM_FW_UPDATE;
//        }            
        if(AppState == APP_PAIRING)
        {
            adv_app_state = ADV_ST_STAND_BY;
            adv_app_state_param = ADV_STAND_BY_PRM_PAIRING;
        }            
        else if(AppState == APP_STAND_BY)
        {
            adv_app_state = ADV_ST_WORK;
            adv_app_state_param = ADV_WORK_PRM_EXECUTION;
        }
        else
        {
            adv_app_state = ADV_ST_STAND_BY;
            adv_app_state_param = ADV_STAND_BY_PRM_PROG_SELECT;
        }

        app_advertising_set_app_state(adv_app_state, adv_app_state_param);
    }

	return;
}

void appStartForceCalibration(void)
{
    //задача для мотора не была выполнена и видимо по причине либо неисправности мотора, либо заметного снижения
    // напряжения питания батарей => надо откалиброваться
    appFlagForceCalibration = true;
    appPrevStateAtForceCalibration = AppState;
    api_state_change(APP_DISPLAY_AdA_1_2);
}
/********************************************************************************
 *
 ********************************************************************************/
void application_process()
{
    uint32_t err_code;
    uint16_t motor_cnt;
    static int sum_motor_cnt = 0, motor_cnt_AdA1, motor_cnt_AdA2;

    err_code = button_read( &keyb_data );
    APP_ERROR_CHECK(err_code);
    err_code = button_services_read( &keyb_serv_data );
    APP_ERROR_CHECK(err_code);

//    if(AppIterateTimerFl == true) {
//        if(keyb_data.btn_state_mask == 0){
//            AppIterateTimerFl = false;
//            APP_ERROR_CHECK(app_timer_stop(app_iterate_timer_id));
//        }
//    }
    
    {
        static uint32_t old_mask = 0xFFFFFFFF;//для того чтобы при старте этот код уже начал работать
        uint32_t new_mask = ALL_BUTTONS_MASK & keyb_data.btn_state_mask;
        if( new_mask != old_mask)
        {
            //произошло любое действие с любыми кнопками
            old_mask = new_mask;
            ind_led_light(true);//включаем подсветку
            app_button_inactivity_cnt = SUBSTATE_0;
            app_timer_stop(led_interval_id);
            app_timer_start( led_interval_id, APP_TIMER_TICKS(LED_TICK_MS, APP_TIMER_PRESCALER), NULL );
        }
    }
    //во всех режимах можно по нажатию btn2 включать/выключать блокировку кнопок
    if(app_lock_substate == SUBSTATE_0)
    {
        if( keyb_data.btn_state[BUTTON_CHAN_2_ID].state == BTN_PRESSED &&
            keyb_data.btn_state[BUTTON_CHAN_2_ID].pressed_time_ms >= APP_LOCK_STATE_ENTER_TIME_MS)
        {
            AppLockState = !AppLockState;
            display_write_symbol_data(SYMB_T14_CHILD_LOCK, AppLockState);
            app_lock_substate = SUBSTATE_1;//чтобы не было сразу повторных переключений блокировки
        }
    }
    else
    {
        if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
        {
            app_lock_substate = SUBSTATE_0;
            if(!AppLockState)
            {
                return;//чтобы исключить обработку в приложении только данного конкретного отжатия кнопки при разблокировке
            }
        }
    }
    if(!AppLockState && app_lock_substate == SUBSTATE_0)
    {
        app_btns_enabled = true;
    }
    else
    {
        app_btns_enabled = false;
    }
    //если ожидаем отжатия любой из клавиш
    if(app_wait_key_release)
    {
        if( keyb_data.btn_state[BUTTON_CHAN_1_ID].event_release + keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release
            + keyb_data.btn_state[BUTTON_CHAN_3_ID].event_release + keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release
            + keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release >= BTN_EVENT_CAPTURED)
        {        
            app_wait_key_release = false;
            return;//пропускаем обработку только ожидаемых отжатий, которые надо игнорировать
        }
    }
    
    { //Код контроля мотором во всех состояниях
        static int tmp_cnt = 0, tmp_cnt2 = 0;
        static bool bMotorJustStopped = false;
        
        if(app_bValveMustMoving || !app_bValveStopped)//если шток клапана должен двигаться или еще не успел остановиться после команды Стоп
        {
            if(app_timeout_flag)//срабатывает (1000 / MOTOR_CONTROL_TICK_MS) раз в секунду при движении штока
            {
                static uint16_t max_motor_cnt = 0, last_motor_cnt = 0;
                motor_cnt = getMotorFreqCnt();
                APP_PRINTF(0,"motor_cnt = %d\n", motor_cnt);
              #ifdef MY_DEBUG
                if(app_curr_motor_cnt > 800) motor_cnt = 0;//для ускорения тестирования
              #endif
                //поиск максимального числа шагов на периоде измерения
                if(max_motor_cnt < motor_cnt) max_motor_cnt = motor_cnt;
                //проверка торможения мотора и при нормальной скорости, 
                if((max_motor_cnt > MIN_MOTOR_CNT && motor_cnt < max_motor_cnt * MOTOR_DECELERATION_PERCENT / 100) ||
                    (motor_cnt <= MIN_MOTOR_CNT && last_motor_cnt >= motor_cnt) //и при пониженной скорости (это надо тестировать)
                  )
                {
                    api_motor_move(STOP_VALVE);
                    APP_PRINTF(0,"motorStop(): max_motor_cnt = %d, motor_cnt = %d\n", max_motor_cnt, motor_cnt);
                }
                last_motor_cnt = motor_cnt;
                
                if(motor_cnt == 0)
                {
                    app_bValveStopped = true;
                    bMotorJustStopped = true;
                    max_motor_cnt = 0;//каждый раз сбрасываем, так как при понижении напряжения на батарейках это значение может тоже меняться
                    if(++tmp_cnt >= MAX_CNT_MOTOR_ERROR_DETECTION_S)
                    {
                        tmp_cnt = 0;
                        app_timeout_flag = false;
                        api_motor_move(STOP_VALVE);
                        APP_PRINTF(0,"motorStop(): cnt for error\n");
                        app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                        api_state_change_to_error(APP_API_MOTOR_ERROR);
                    }
                }
                else
                {
                    tmp_cnt = tmp_cnt2 = 0;//сбрасываем эти счетчики при движении
                    app_bValveStopped = false;
                    //подсчет номера текущего шага
                    if( app_motor_direction == OPEN_VALVE)
                    {
                        app_curr_motor_cnt -= (int)motor_cnt;
                    }
                    else
                    {
                        app_curr_motor_cnt += (int)motor_cnt;
                    }
                    AppParam.global_motor_cnt += (uint32_t)motor_cnt;
                }
            }
        }//if(app_bValveMustMoving || !app_bValveStopped)
        else
        {
            //здесь точно известно, что стоим
            tmp_cnt = 0;
            if(bMotorJustStopped)
            {
                #define MOTOR_TO_STANDBY_CNT_IN_MAIN_CYCLE_TICKS    20 //это примерно пара секунд
                if(++tmp_cnt2 > MOTOR_TO_STANDBY_CNT_IN_MAIN_CYCLE_TICKS)
                {
                    tmp_cnt2 = 0;
                    bMotorJustStopped = false;
                    motorStandby();//снимаем питание с микросхемы драйвера мотора (~ через пару секунд после команды Стоп)
                }
            }
        }
    }
    
    switch(AppState)
    {
        case APP_SELF_TEST:
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                if(!app_bValveMustMoving && app_bValveStopped)
                {
                    app_button_inactivity_cnt = SUBSTATE_0;
                    app_timer_stop(led_interval_id);
                    app_timer_start( led_interval_id, APP_TIMER_TICKS(LED_TICK_MS, APP_TIMER_PRESCALER), NULL );
                }
                else
                {
                    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                }
            }
            if( keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release == BTN_EVENT_CAPTURED)
            {
                buz_add_beep(150,150,BUZ_BEEP_SINGLE);
                soft_ind_prg_stage(4, IND_SET);
                api_motor_move(CLOSE_VALVE);
                app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
            }
            if( keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release == BTN_EVENT_CAPTURED)
            {
                buz_add_beep(150,150,BUZ_BEEP_SINGLE);
                soft_ind_prg_stage(5, IND_SET);
                api_motor_move(OPEN_VALVE);
                app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
            }
            if( keyb_data.btn_state[BUTTON_CHAN_1_ID].event_release == BTN_EVENT_CAPTURED)
            {
                buz_add_beep(150,150,BUZ_BEEP_SINGLE);
                soft_ind_prg_stage(1, IND_SET);
                api_motor_move(STOP_VALVE);
            }
            if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
            {
                buz_add_beep(150,150,BUZ_BEEP_SINGLE);
                soft_ind_prg_stage(2, IND_SET);
                ht9b92_display_on();
            }
            if( keyb_data.btn_state[BUTTON_CHAN_3_ID].event_release == BTN_EVENT_CAPTURED)
            {
                buz_add_beep(150,150,BUZ_BEEP_SINGLE);
                soft_ind_prg_stage(3, IND_SET);
                motorStandby();
                app_timer_stop( app_timer_id);
                ht9b92_display_off();
            }
            break;
        case APP_MASS_FW_UPDATE:
            break;
        case APP_START:
        {
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                if(keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                    AppParam.powerOnCnt <= MAX_POWER_ON_CNT_FOR_MASS_FW_UPDATE)
                {
                    api_state_change(APP_MASS_FW_UPDATE);
                    break;
                }
                
                //делаем здесь, так как к этому моменту конденсатор 100 мкФ на питании микросхемы RTC уже точно зарядится
                app_init_rtc();
                //очистка всего дисплея
                soft_ind_set_all(IND_CLR);
                
                err_code =  api_temp_test_proccess();
                if(err_code != NRF_SUCCESS)
                {
                    api_state_change_to_error(APP_API_TEMPERATURE_SENSOR_ERROR);
                    break;
                }
                
                err_code =  api_battery_test_proccess();
                if(err_code != NRF_SUCCESS)
                {
                    ind_state_change(IND_LOW_BATTERY_START);
                }
                
                if(keyb_data.btn_state[BUTTON_CHAN_2_ID].state == BTN_PRESSED)
                {
                    api_state_change(APP_SELF_TEST);
                }
                else
                {
                    soft_ind_UI(IND_SET);
                    app_timer_start( app_timer_id, APP_TIMER_TICKS(DISPLAY_SYMBOLS_UI_AT_LCD_TICK_MS, APP_TIMER_PRESCALER), NULL );
                    api_state_change(APP_TEST);
                }
            }
            break;
        }
        case APP_TEST:
        {
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
//                api_param_update();//старт мониторинга температуры, напряжения питания (м.б. можно упростить)
              #ifdef MY_DEBUG
                api_state_change(APP_STAND_BY);//для пропуска калибровки
                app_full_motor_cnt = 800;
                app_curr_motor_cnt = 0;
                APP_PRINTF(0, "app_full_motor_cnt = %d\n", app_full_motor_cnt);
              #else
                api_state_change(APP_DISPLAY_AdA);
                //api_state_change(APP_CLOCK_BLINKING);
                if(AppParam.powerOnCnt <= MAX_POWER_ON_CNT_FOR_MASS_FW_UPDATE)
                {
                    AppParam.flash_page_erase_cnt++;
                    memcpy(&param, &AppParam, sizeof(param_t) - sizeof(param.CRC));
                    Param_Save();
                }
                else
                {
                    //чтобы в дальнейшем при сохранении других параметров этот счетчик оставался максимальным
                    AppParam.powerOnCnt = MAX_POWER_ON_CNT_FOR_MASS_FW_UPDATE;
                }
              #endif
            }
            break;
        }
        case APP_CLOCK_BLINKING:
            if( app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
            {
                api_state_change(APP_CLOCK_SET_MIN);
                break;
            }
            if( app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS &&
                keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS)
            {
                app_wait_key_release = true;
                api_state_change(APP_PAIRING);
                break;
            }
            break;
        case APP_CLOCK_SET_MIN:
            if(app_btns_enabled)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    api_state_change(APP_CLOCK_SET_HOUR);
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppCurrUtcTime -= 60;//уменьшаем на 60 секунд= 1 минуту
                }
                if( keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppCurrUtcTime += 60;//добавляем 60 секунд= 1 минуту
                }
                if( app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS &&
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    api_state_change(APP_PAIRING);
                    break;
                }
            }
            else
            {
                if(app_full_motor_cnt == 0)//до калибровки
                {
                    api_state_change(APP_CLOCK_BLINKING);
                }
                else //при редактировании времени из меню программ
                {
                    api_state_change(APP_STAND_BY);
                }
            }
            break;
        case APP_CLOCK_SET_HOUR:
            if(app_btns_enabled)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    api_state_change(APP_CLOCK_SET_DAY);
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppCurrUtcTime -= 3600;//уменьшаем на 1 час
                }
                if( keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppCurrUtcTime += 3600;//добавляем 1 час
                }
                if( app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS &&
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    api_state_change(APP_PAIRING);
                    break;
                }
            }
            else
            {
                if(app_full_motor_cnt == 0)//до калибровки
                {
                    api_state_change(APP_CLOCK_BLINKING);
                }
                else //при редактировании времени из меню программ
                {
                    api_state_change(APP_STAND_BY);
                }
            }
            break;
        case APP_CLOCK_SET_DAY:
            if(app_btns_enabled)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    //здесь полностью закончили редактирование => сохраняем время
                    api_set_calendar_data(&AppCurrUtcTime, &AppCurrUtcShiftTime);
                    if(app_full_motor_cnt == 0)//до калибровки
                    {
                        api_state_change(APP_DISPLAY_AdA);
                    }
                    else //при редактировании времени из меню программ
                    {
                        api_state_change(APP_SELECT_PROGRAM);
                    }
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppCurrUtcTime -= (3600*24);//уменьшаем на 1 день
                }
                if( keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppCurrUtcTime += (3600*24);//добавляем 1 день
                }
                if( app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS &&
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    api_state_change(APP_PAIRING);
                    break;
                }
            }
            else
            {
                if(app_full_motor_cnt == 0)//до калибровки
                {
                    api_state_change(APP_CLOCK_BLINKING);
                }
                else //при редактировании времени из меню программ
                {
                    api_state_change(APP_STAND_BY);
                }
            }
            break;
        case APP_DISPLAY_AdA:
            if(app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
            {
                api_state_change(APP_DISPLAY_AdA_1_2);
            }
            if( app_btns_enabled && keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS &&
                keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS)
            {
                app_wait_key_release = true;
                api_state_change(APP_PAIRING);
                break;
            }
            break;
        case APP_DISPLAY_AdA_1_2:
            if(app_timeout_flag)
            {
//                static volatile int sum_motor_cnt = 0, motor_cnt_AdA1, motor_cnt_AdA2;
                
                app_timeout_flag = false;
                app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                switch(AppSubstate)
                {
                    case SUBSTATE_0://начальное небольшое призакрытие клапана  AdA 1
                    {
                        static uint8_t sub0_cnt = 0;//счетчик периодов контроля мотора MOTOR_CONTROL_TICK_MS
                        if(motor_cnt == 0 || (++sub0_cnt > MAX_SUB0_ADA1_CNT))
                        {
                            //клапан немного призакрылся, это чтобы отошел от жесткого края если был полностью открыт (чтобы не ломать шестеренки)
                            sub0_cnt = 0;
                            api_motor_move(OPEN_VALVE);
                            app_curr_motor_cnt = 0;//для тестирования (хотя можно и оставить, это не будет мешать)
                            sum_motor_cnt = 0;
                            AppSubstate++;
                        }
                    }
                        break;
                    case SUBSTATE_1://начальное небольшое призакрытие клапана  AdA 1
                        if(motor_cnt == 0)
                        {
                            //клапан полностью открылся
                            api_motor_move(CLOSE_VALVE);
                            app_curr_motor_cnt = 0;//для тестирования (хотя можно и оставить, это не будет мешать)
                            sum_motor_cnt = 0;
                            AppSubstate++;
                        }
                        break;
                    case SUBSTATE_2://полное закрытие клапана AdA 1
                        if(motor_cnt == 0)
                        {
                            //клапан полностью закрылся
                            motor_cnt_AdA1 = sum_motor_cnt;
                            sum_motor_cnt = 0;
                            if(motor_cnt_AdA1 < MOTOR_CNT_FOR_ERROR)
                            {
                                api_motor_move(STOP_VALVE);
                                api_state_change_to_error(APP_API_MOTOR_ERROR);
                            }
                            else
                            {
                                ind_state_change(IND_DISPLAY_AdA_2);
                                api_motor_move(OPEN_VALVE);
                                AppSubstate++;
                            }
                        }
                        else
                        {
                            sum_motor_cnt += motor_cnt;
                            if(sum_motor_cnt > MAX_THRESHOLD_FOR_FULL_MOTOR_CNT)
                            {
                                api_state_change_to_error(APP_API_MOTOR_ERROR);
                            }
                        }
                        break;
                    case SUBSTATE_3://полное открытие клапана AdA 2
                        if(motor_cnt == 0)
                        {
                            //клапан полностью открылся
                            api_motor_move(STOP_VALVE);
                            app_timer_stop(app_timer_id);
                            motor_cnt_AdA2 = sum_motor_cnt;
                            APP_PRINTF(0, "motor_cnt_AdA1 = %d, motor_cnt_AdA2 = %d\n", motor_cnt_AdA1, motor_cnt_AdA2);
                            //если разница счета в прямом и обратном направлении сильно отличается,
                            // то это тоже какая-то неисправность и ее надо индицировать
                            if(abs(motor_cnt_AdA2 - motor_cnt_AdA1) > MOTOR_CNT_FOR_ERROR)
                            {
                                api_state_change_to_error(APP_API_MOTOR_ERROR);
                            }
                            else
                            {
                                //берем меньшее для более надежной работы (чтобы точно мотор доходил до выбранного положения закрыто)
                                if(motor_cnt_AdA1 < motor_cnt_AdA2) app_full_motor_cnt = motor_cnt_AdA1;
                                else app_full_motor_cnt = motor_cnt_AdA2;
                                //app_full_motor_cnt = (motor_cnt_AdA1 + motor_cnt_AdA2) / 2;
                                app_curr_motor_cnt = 0;//обязательное обнуление счетчика (происходит в конце калибровки)
                                APP_PRINTF(0, "app_full_motor_cnt = %d\n", app_full_motor_cnt);
                                if( appFlagForceCalibration )
                                {
                                    appFlagForceCalibration = false;
                                    api_state_change(appPrevStateAtForceCalibration);//успешное завершение доп.калибровки при сбоях мотора
                                }
                                else
                                {
                                  #if 0
                                    //для зацикливания калибровки - для теста
                                    app_timer_stop(app_timer_id);
                                    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                                    api_motor_move(CLOSE_VALVE);
                                    AppSubstate = SUBSTATE_0;
                                    ind_state_change(IND_DISPLAY_AdA_1);
                                    APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: >>APP_DISPLAY_AdA_1_2\r\n"RTT_CTRL_RESET);
                                  #endif

                                    api_state_change(APP_STAND_BY);//успешное завершение обычной калибровки
                                }
                            }
                        }
                        else
                        {
                            sum_motor_cnt += motor_cnt;
                            if(sum_motor_cnt > MAX_THRESHOLD_FOR_FULL_MOTOR_CNT)
                            {
                                api_motor_move(STOP_VALVE);
                                app_timer_stop(app_timer_id);
                                api_state_change_to_error(APP_API_MOTOR_ERROR);
                            }
                        }
                        break;
                }
            }
            break;
        case APP_STAND_BY:// state machine
        {
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                //если должны двигаться или еще не остановились до полного останова после команды Стоп
                if(app_bValveMustMoving || !app_bValveStopped)
                {
                    //Алгоритм регулировки температуры
                    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                    APP_PRINTF(0, "app_target_motor_cnt = %d, app_curr_motor_cnt = %d\n", app_target_motor_cnt, app_curr_motor_cnt);
                    
                    //если достигли пределов или целевого счетчика (шага)
                    if( 
                        ((app_curr_motor_cnt >= app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT || 
                                app_curr_motor_cnt >= app_target_motor_cnt) && app_motor_direction == CLOSE_VALVE) ||
                        ((app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT || app_curr_motor_cnt <= app_target_motor_cnt)
                                                                            && app_motor_direction == OPEN_VALVE) ||
                        (app_bValveMustMoving == false && AppSubstate != SUBSTATE_2)//или останавливаемся по событию торможения мотора (но не в SUBSTATE_2) - это нужно, чтобы присвоить AppSubstate = SUBSTATE_0
                    )
                    {
                        api_motor_move(STOP_VALVE);
                        APP_PRINTF(0,"motorStop(): at APP_STAND_BY\n");
                        AppSubstate = SUBSTATE_0;//состояние: команда остановиться
                    }
                }
                else
                {  //полностью остановились
                    if(AppSubstate == SUBSTATE_0)
                    {
                        if(
                            ((app_curr_motor_cnt >= app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT || 
                                app_curr_motor_cnt >= app_target_motor_cnt) && app_motor_direction == CLOSE_VALVE) ||
                            ((app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT || app_curr_motor_cnt <= app_target_motor_cnt)
                                                                            && app_motor_direction == OPEN_VALVE)
                        )
                        {
                            AppSubstate = SUBSTATE_1;//ждем когда снова можно управлять мотором
                            app_start_timer_for_next_motor_regulation();
                        }
                        else
                        {
                            appStartForceCalibration();
                            break;
                        }
                    }
                    else if(AppSubstate == SUBSTATE_1)
                    {
                        app_start_motor_regulation();
                    }
                    else if(AppSubstate == SUBSTATE_2)
                    {
                        APP_PRINTF(0, "last app_curr_motor_cnt after stop and before next state = %d\n", app_curr_motor_cnt);
                        api_state_change(app_nextstate);
                    }
                }
            }
//          api_temp_process();
            if(app_btns_enabled && !app_wait_key_release)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_1_ID].state == BTN_PRESSED && 
                    keyb_data.btn_state[BUTTON_CHAN_3_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_1_ID].pressed_time_ms >= APP_UNPAIRING_STATE_ENTER_TIME_MS &&
                    keyb_data.btn_state[BUTTON_CHAN_3_ID].pressed_time_ms >= APP_UNPAIRING_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_UNPAIRING);
                    break;
                }
                if( keyb_data.btn_state[BUTTON_CHAN_1_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_3_ID].state != BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_1_ID].pressed_time_ms >= APP_ENTER_PROGRAMMING_MENU_TIME_MS
                  )
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_SELECT_PROGRAM);
                    break;
                }
                if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)//Ok, Boost
                {
                    app_state_change_after_stop(APP_BOOST);
                }
                if( keyb_data.btn_state[BUTTON_CHAN_3_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    api_lock_calendar(!AppLockCalendarState);
//                    app_state_change_after_stop(APP_STAND_BY);
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    if(AppTargetTemperature > MIN_REG_TEMPERATURE)
                    {
                        AppTargetTemperature -= TEMPERATURE_REG_STEP;
                    }
                    soft_ind_target_temp(AppTargetTemperature, IND_SET);
                    //com_master_prior_proccess(EVT_BTN_PRESSED(BUTTON_CHAN_2_ID, BTN_CLICK_SINGLE));
                    AppParam.TargetTemperature = AppTargetTemperature;
                }
                if( keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    if(AppTargetTemperature < MAX_REG_TEMPERATURE)
                    {
                        AppTargetTemperature += TEMPERATURE_REG_STEP;
                    }
                    soft_ind_target_temp(AppTargetTemperature, IND_SET);
                    //com_master_prior_proccess(EVT_BTN_PRESSED(BUTTON_CHAN_2_ID, BTN_CLICK_SINGLE));
                    AppParam.TargetTemperature = AppTargetTemperature;
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS &&
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_PAIRING_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_PAIRING);
                    break;
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED && 
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].state != BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_OP_OFF_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_OFF_STATE);
                }
                if( keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_4_ID].state != BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_OP_OFF_STATE_ENTER_TIME_MS)
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_OP_STATE);
                }
            }//if(app_btns_enabled && !app_wait_key_release)

            app_check_tasks_availability();

            //если температура стала <= критической Т вентиляции и режим вентиляции разрешен
            if(AppCurrTemp <= AppParam.A2_T_ventilation && AppParam.VentilationState)
            {
                app_state_change_after_stop(APP_VENTILATION);
            }
            break;
        }//case APP_STAND_BY:
//        case APP_NO_CALENDAR_TASKS:
//            //3 секунды моргаем знаком auto
//            if(AppCountdownSeconds == 0)
//            {
//                AppParam.LockCalendarState = AppLockCalendarState = LOCK_CALENDAR_STATE;
//                api_state_change(AppStatePrevious);
//                //api_state_change(APP_STAND_BY);
//                api_save_params_with_delay();//emulation of 5s noactions for saving of params
//            }
//            break;
        case APP_VENTILATION:
        {
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                //если должны двигаться или еще не остановились до полного останова после команды Стоп
                if(app_bValveMustMoving || !app_bValveStopped)
                {
                    //Алгоритм регулировки температуры
                    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                    APP_PRINTF(0, "app_target_motor_cnt = %d, app_curr_motor_cnt = %d\n", app_target_motor_cnt, app_curr_motor_cnt);
                    
                    //если достигли пределов или целевого счетчика (шага)
                    if( 
                        ((app_curr_motor_cnt >= app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT || 
                                app_curr_motor_cnt >= app_target_motor_cnt) && app_motor_direction == CLOSE_VALVE) ||
                        ((app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT || app_curr_motor_cnt <= app_target_motor_cnt)
                                                                            && app_motor_direction == OPEN_VALVE) ||
                        (app_bValveMustMoving == false && AppSubstate != SUBSTATE_2)//или останавливаемся по событию торможения мотора -  - это нужно, чтобы присвоить AppSubstate = SUBSTATE_0
                    )
                    {
                        api_motor_move(STOP_VALVE);
                        APP_PRINTF(0,"motorStop(): at APP_VENTILATION\n");
                        AppSubstate = SUBSTATE_0;//состояние: команда остановиться
                    }
                }
                else
                {  //полностью остановились
                    if(AppSubstate == SUBSTATE_2)
                    {
                        APP_PRINTF(0, "last app_curr_motor_cnt after stop and before next state = %d\n", app_curr_motor_cnt);
                        api_state_change(app_nextstate);
                    }
                }
            }
            if(AppCurrTemp >= AppParam.A2_T_ventilation + AppParam.A5_T_hysteresis)
            {
                app_state_change_after_stop(APP_STAND_BY);
            }
            if(app_btns_enabled && !app_wait_key_release)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_1_ID].state == BTN_PRESSED &&
                    keyb_data.btn_state[BUTTON_CHAN_1_ID].pressed_time_ms >= APP_ENTER_PROGRAMMING_MENU_TIME_MS)
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_SELECT_PROGRAM);
                }
                if( keyb_data.btn_state[BUTTON_CHAN_3_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    api_lock_calendar(!AppLockCalendarState);
                }
            }
            app_check_tasks_availability();
            break;
        }
        case APP_SELECT_PROGRAM:
        {
            if(app_btns_enabled && !app_wait_key_release)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_1_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    if(++AppSubstate > SUBSTATE_1) AppSubstate = SUBSTATE_0;
                    ind_state_change((ind_state_t)(IND_PROGRAM_ADVANCED/*IND_PROGRAM_P0*/ + AppSubstate));
                }
                if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    switch(AppSubstate)
                    {
                        case SUBSTATE_0://advanced
                            api_state_change(APP_ADVANCED_MENU);
                            break;
                        case SUBSTATE_1://clock
                            api_state_change(APP_CLOCK_SET_MIN);
                            break;
                    }
                }
            }
            break;
        }
        case APP_PARAMS_RESET:
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                api_state_change(APP_STAND_BY);
            }
            break;
        case APP_ADVANCED_MENU:
        {
            if(app_btns_enabled && !app_wait_key_release)
            {
                if( keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    AppSubstate++;
                    if(AppSubstate > SUBSTATE_5)
                    {
                        if(app_param_reset_val == DEFAULT_PARAM_RESET_DO)
                        {
                            //advanced params reset
                            api_state_change(APP_PARAMS_RESET);
                        }
                        else
                        {
                            if(app_param_calibration_val == APP_CALIBRATION_ON) //ВКЛ калибровки
                            {
                                api_state_change(APP_DISPLAY_AdA_1_2);
                                app_param_calibration_val = APP_CALIBRATION_OFF;//ВЫКЛ калибровки (по умолчанию)
                            }
                            else
                            {
                                api_state_change(APP_STAND_BY);
                            }
                        }
                    }
                    else
                    {
                        ind_state_change(IND_ADVANCED_MENU);
                    }
                }
                if( keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    switch(AppSubstate)
                    {
                        case SUBSTATE_0:
                            if(AppParam.A1_T_shift > MIN_PARAM_T_SHIFT)
                            {
                                AppParam.A1_T_shift -= TEMPERATURE_REG_STEP;
                                AppCurrTemp -= TEMPERATURE_REG_STEP;
                                soft_ind_target_temp(AppParam.A1_T_shift, IND_SET);//коррекция текущей температуры
                            }
                            break;
                        case SUBSTATE_1:
                            if(AppParam.A2_T_ventilation > MIN_PARAM_T_VENTILATION)
                            {
                                AppParam.A2_T_ventilation -= REG_STEP_PARAM_T_VENTILATION;
                            }
                            else
                            {
                                AppParam.VentilationState = false;
                            }
                            break;
//                        case SUBSTATE_2:
//                            if(AppParam.A2_time_ventilation > MIN_PARAM_TIME_VENTILATION)
//                            {
//                                AppParam.A2_time_ventilation -= REG_STEP_PARAM_TIME_VENTILATION;
//                            }
//                            break;
                        case SUBSTATE_2:
                            app_param_calibration_val ^= 1;
                            soft_ind_target_temp(app_param_calibration_val, IND_SET);
                            display_write_symbol_data(SYMB_T27_SET_GRAD_C, IND_CLR);
                            break;
                        case SUBSTATE_3:
                            if(AppParam.A4_boost > MIN_PARAM_BOOST)
                            {
                                AppParam.A4_boost -= REG_STEP_PARAM_BOOST;
                            }
                            soft_ind_countdown(AppParam.A4_boost, IND_SET);
                            break;
                        case SUBSTATE_4:
                            if(AppParam.A5_T_hysteresis > MIN_PARAM_T_HYSTERESIS)
                            {
                                AppParam.A5_T_hysteresis -= REG_STEP_PARAM_T_HYSTERESIS;
                            }
                            soft_ind_target_temp(AppParam.A5_T_hysteresis, IND_SET);
                            break;
                        case SUBSTATE_5:
                            app_param_reset_val = DEFAULT_PARAM_RESET_DO;
                            soft_ind_target_temp(DEFAULT_PARAM_RESET_DO, IND_SET);
                            display_write_symbol_data(SYMB_T27_SET_GRAD_C, IND_CLR);
                            break;
                    }
                }
                if( keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    switch(AppSubstate)
                    {
                        case SUBSTATE_0:
                            if(AppParam.A1_T_shift < MAX_PARAM_T_SHIFT)
                            {
                                AppParam.A1_T_shift += TEMPERATURE_REG_STEP;
                                AppCurrTemp += TEMPERATURE_REG_STEP;
                                soft_ind_target_temp(AppParam.A1_T_shift, IND_SET);//коррекция текущей температуры
                            }
                            break;
                        case SUBSTATE_1:
                            if(AppParam.A2_T_ventilation < MAX_PARAM_T_VENTILATION)
                            {
                                if(AppParam.VentilationState)
                                {
                                    AppParam.A2_T_ventilation += REG_STEP_PARAM_T_VENTILATION;
                                }
                                else
                                {
                                    AppParam.VentilationState = true;
                                }
                            }
                            break;
//                        case SUBSTATE_2:
//                            if(AppParam.A2_time_ventilation < MAX_PARAM_TIME_VENTILATION)
//                            {
//                                AppParam.A2_time_ventilation += REG_STEP_PARAM_TIME_VENTILATION;
//                            }
//                            break;
                        case SUBSTATE_2:
                            app_param_calibration_val ^= 1;
                            soft_ind_target_temp(app_param_calibration_val, IND_SET);
                            display_write_symbol_data(SYMB_T27_SET_GRAD_C, IND_CLR);
                            break;
                        case SUBSTATE_3:
                            if(AppParam.A4_boost < MAX_PARAM_BOOST)
                            {
                                AppParam.A4_boost += REG_STEP_PARAM_BOOST;
                            }
                            soft_ind_countdown(AppParam.A4_boost, IND_SET);
                            break;
                        case SUBSTATE_4:
                            if(AppParam.A5_T_hysteresis < MAX_PARAM_T_HYSTERESIS)
                            {
                                AppParam.A5_T_hysteresis += REG_STEP_PARAM_T_HYSTERESIS;
                            }
                            soft_ind_target_temp(AppParam.A5_T_hysteresis, IND_SET);
                            break;
                        case SUBSTATE_5:
                            app_param_reset_val = DEFAULT_PARAM_RESET;
                            soft_ind_target_temp(DEFAULT_PARAM_RESET, IND_SET);
                            display_write_symbol_data(SYMB_T27_SET_GRAD_C, IND_CLR);
                            break;
                    }
                }
            }
            break;
        }
        case APP_OP_STATE:
        case APP_OFF_STATE:
        {
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                //если должны двигаться или еще не остановились до полного останова после команды Стоп
                if(app_bValveMustMoving || !app_bValveStopped)
                {
                    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                    APP_PRINTF(0, "app_curr_motor_cnt = %d\n", app_curr_motor_cnt);
                    
                    //если достигли пределов или целевого счетчика (шага)
                    if( 
                        (((app_curr_motor_cnt >= app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT ) && app_motor_direction == CLOSE_VALVE) ||
                        ((app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT ) && app_motor_direction == OPEN_VALVE)) && (AppSubstate != SUBSTATE_2)
                    )
                    { //а по событию торможения мотора эта же функция уже была вызвана ранее, поэтому еще раз ее не надо вызывать
                        api_motor_move(STOP_VALVE);
                        APP_PRINTF(0,"motorStop(): at APP_OP_STATE or APP_OFF_STATE\n");
                    }
                }
                else
                {  //полностью остановились
                    if(AppSubstate == SUBSTATE_2)
                    {
                        APP_PRINTF(0, "last app_curr_motor_cnt after stop and before next state = %d\n", app_curr_motor_cnt);
                        api_state_change(app_nextstate);
                    }
                    else
                    {
                        //Если команда не выполнилась
                        if(
                            !(((app_curr_motor_cnt >= app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT ) && app_motor_direction == CLOSE_VALVE) ||
                            ((app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT ) && app_motor_direction == OPEN_VALVE))
                          )
                        {
                            appStartForceCalibration();
                            break;
                        }
                    }
                  #ifdef CTR_LIFE_TEST
                    if(AppState == APP_OP_STATE)
                    {
                        api_state_change(APP_OFF_STATE);
                        AppParam.ctrLifeTestCnt++;
                        APP_PRINTF(0, "AppParam.ctrLifeTestCnt = %d\n", AppParam.ctrLifeTestCnt);
                        memcpy(&param, &AppParam, sizeof(param_t) - sizeof(param.CRC));
                        Param_Save();
                    }
                    else
                    {
                        api_state_change(APP_OP_STATE);
                    }
                    soft_ind_current_temp(AppParam.ctrLifeTestCnt / 100, IND_SET);
                    soft_ind_target_temp (AppParam.ctrLifeTestCnt % 100, IND_SET);
                  #endif
                }
            }
            if(app_btns_enabled && !app_wait_key_release)
            {
                if(AppState == APP_OP_STATE)
                {
                    if( keyb_data.btn_state[BUTTON_CHAN_5_ID].state == BTN_PRESSED &&
                        keyb_data.btn_state[BUTTON_CHAN_5_ID].pressed_time_ms >= APP_OP_OFF_STATE_ENTER_TIME_MS)
                    {
                        app_wait_key_release = true;
                        app_state_change_after_stop(APP_STAND_BY);//возвращаемся всегда в нормальный режим
                    }
                }
                else
                {
                    if( keyb_data.btn_state[BUTTON_CHAN_4_ID].state == BTN_PRESSED &&
                        keyb_data.btn_state[BUTTON_CHAN_4_ID].pressed_time_ms >= APP_OP_OFF_STATE_ENTER_TIME_MS)
                    {
                        app_wait_key_release = true;
                        app_state_change_after_stop(APP_STAND_BY);//возвращаемся всегда в нормальный режим
                    }
                }
                if( keyb_data.btn_state[BUTTON_CHAN_3_ID].event_release == BTN_EVENT_CAPTURED)
                {
                    api_lock_calendar(!AppLockCalendarState);
                }
            }
            app_check_tasks_availability();
            break;
        }
        case APP_BOOST:// state machine
        {
            if(app_timeout_flag)
            {
                app_timeout_flag = false;
                //если должны двигаться или еще не остановились до полного останова после команды Стоп
                if(app_bValveMustMoving || !app_bValveStopped)
                {
                    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
                    APP_PRINTF(0, "app_curr_motor_cnt = %d\n", app_curr_motor_cnt);
                    
                    //если достигли предела
                    if( app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT && AppSubstate != SUBSTATE_2)
                    { //а по событию торможения мотора эта же функция уже была вызвана ранее, поэтому еще раз ее не надо вызывать
                        api_motor_move(STOP_VALVE);
                        APP_PRINTF(0,"motorStop(): at APP_BOOST\n");
                    }
                }
                else
                {  //полностью остановились
                    app_in_program_transition = false;//переходное состояние завершилось
                    if(AppSubstate == SUBSTATE_2)
                    {
                        APP_PRINTF(0, "last app_curr_motor_cnt after stop and before next state = %d\n", app_curr_motor_cnt);
                        api_state_change(app_nextstate);
                    }
                    else
                    {
                        //если правильно дошли
                        if( app_curr_motor_cnt <= MOTOR_CNT_EDGE_SHIFT )
                        {
                            //начинаем обратный отсчет (сюда попадем даже если при старте уже стояли в полностью открытом состоянии, т.к. таймер всегда стартует)
                            ind_state_change(IND_MOTOR_BOOST_COUNTDOWN);
                        }
                        else
                        {
                            appStartForceCalibration();
                            break;
                        }
                    }
                }
            }
            if(app_bValveStopped && !app_bValveMustMoving)
            {
                if(AppCountdownSeconds == 0)
                {
                    //api_state_change(APP_STAND_BY);
                    api_state_change(AppStatePrevious);// теперь возвращаемся в предыдущее до BOOST состояние
                    break;
                }
            }
            if(app_btns_enabled)
            {
                //выход из boost по отжатию любой кнопки
                if( keyb_data.btn_state[BUTTON_CHAN_1_ID].event_release + keyb_data.btn_state[BUTTON_CHAN_2_ID].event_release
                    + keyb_data.btn_state[BUTTON_CHAN_3_ID].event_release + keyb_data.btn_state[BUTTON_CHAN_4_ID].event_release
                    + keyb_data.btn_state[BUTTON_CHAN_5_ID].event_release >= BTN_EVENT_CAPTURED)
                {
                    app_state_change_after_stop(APP_STAND_BY);
                }
            }
            app_check_tasks_availability();
            break;
        }
        case APP_ERROR:// state machine
        {
            if(app_btns_enabled && (AppSubstate == APP_API_TEMPERATURE_SENSOR_ERROR))
            {
                //выход из ошибки Е5 по долгому (3s) нажатию b1
                if( keyb_data.btn_state[BUTTON_CHAN_1_ID].pressed_time_ms >= APP_ERROR_TEMPERATURE_RESET_TIME_MS)
                {
                    app_wait_key_release = true;
                    app_state_change_after_stop(APP_STAND_BY);
                }
            }
            break;
        }
        case APP_PAIRING:
        {
            if( app_timeout_flag == true )//таймаут перинга
            {
                app_timeout_flag = false;
                api_state_change(APP_PAIRING_ERROR);
            }
            break;
        }
        case APP_PAIRING_SUCCESS:
//            api_state_change(APP_STAND_BY);
            api_state_change(AppStatePrevious);
            break;
        case APP_PAIRING_ERROR:
            if( app_timeout_flag == true )//время вывода 00
            {
                app_timeout_flag = false;
                api_state_change(AppStatePrevious);
                //api_state_change(APP_STAND_BY);
            }
            break;
#ifdef NRF51
		case APP_SLAVE_FW_UPDATE:
		{
			if( app_timeout_flag == true )		//таймаут обновления прошивки
			{				
				api_state_change(APP_STAND_BY);
			}
			break;
		}
#endif
        case APP_UNPAIRING:
        {
            if( app_timeout_flag == true )//время вывода []
            {
                app_timeout_flag = false;
                api_state_change(APP_STAND_BY);
            }
            break;
        }
        default:
        {
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_YELLOW"[APPL]: default\r\n"RTT_CTRL_RESET);
            api_state_change(APP_STAND_BY);
            break;
        }
    }
    return;
}
/********************************************************************************
 *
 ********************************************************************************/

/********************************************************************************
 *
 ********************************************************************************/
/**
 * 	описание в application_api.h
*/

uint16_t api_get_version(void)
{
	return FIRMWARE_VER;
}
/***************************COMMON************************************/
void api_state_change(app_state_t new_state)
{
    AppState = new_state;
    application_init_prew_state( AppState );
    application_init_new_state( AppState );
    api_ind_connected(app_ble_connected);//всегда выводим состояние коннекта по BLE
}

bool api_state_check(app_state_t state)
{
    if(AppState == state)
	{
		return true;
	}
	else
	{
		return false;
	}
}
uint8_t api_get_state(void)
{
    if(app_in_program_transition) return APP_API_IN_PROGRAM_TRANSITION;
    
    switch(AppState)
    {
#ifdef NRF51
		case APP_SLAVE_FW_UPDATE:
		{
			return APP_API_FW_UPDATE_STATE;
		}
#endif
        case APP_MASS_FW_UPDATE:
            return APP_API_MASS_FW_UPDATE;
        case APP_DISPLAY_AdA:
            return APP_API_NO_CALIBRATION;
        case APP_STAND_BY:
        case APP_BOOST:      //открытие на полную на определенное время
        case APP_OP_STATE:   //открытие на полную
        case APP_OFF_STATE:  //полное закрытие
            return APP_API_NORMAL_STATE;
        case APP_CLOCK_BLINKING:
        case APP_CLOCK_SET_MIN:
        case APP_CLOCK_SET_HOUR:
        case APP_CLOCK_SET_DAY:
        case APP_SELECT_PROGRAM://выбор программы
        case APP_ADVANCED_MENU://инженерное меню
            return APP_API_MAINTENANCE_STATE;
        case APP_DISPLAY_AdA_1_2:
            return APP_API_ADA_CALIBRATION_STATE;
        case APP_VENTILATION:
            return APP_API_VENTILATION_STATE;
        case APP_PAIRING:
            return APP_API_PAIRING_STATE;
        case APP_ERROR:
            return APP_API_ERROR_STATE;

        /* Стейт подменяется на APP_API_NORMAL_STATE */
        //case APP_NO_CALENDAR_TASKS:
        case APP_PAIRING_SUCCESS:
        //case APP_PAIRING_ERROR: Приложение не может видеть эти состояния
        //case APP_UNPAIRING:
        case APP_PARAMS_RESET:
            return APP_API_NORMAL_STATE;

        default:
            return APP_API_OTHER_STATE;
    }
}

uint32_t api_temp_test_proccess(void)
{
	if(AppCurrTemp>= MIN_TEMPERATURE && AppCurrTemp <= MAX_TEMPERATURE)
	{
		return NRF_SUCCESS;
	}
	return NRF_ERROR_INVALID_STATE;
}

uint32_t api_battery_test_proccess(void)
{
	uint32_t err_code;
	
	err_code = batt_nAA_read_charge_val(&app_battery_charge );
	APP_ERROR_CHECK(err_code);
	
	if(app_battery_charge > MIN_BATTERY_CHARGE_LEVEL)
	{
		return NRF_SUCCESS;
	}
	return NRF_ERROR_INVALID_STATE;
}
/***************************PERIPHERAL************************************/
/**
 * 	описание в application_api.h
*/
uint8_t api_get_battery_charge(void)
{
	return app_battery_charge;
}

/**
 * 	описание в application_api.h
*/
int16_t api_get_temp(void)
{
	return FloatToSFloat16(AppCurrTemp);
}

uint8_t api_get_error(void)
{
    uint8_t err;
    switch(AppState)
    {
        case APP_ERROR:
            err = AppSubstate;
            break;
        default:
            err = APP_API_NO_ERROR;
            break;
    }
    return  (err | (AppBoolLowBattery ? APP_API_LOW_BATTERY_WARNING : 0));
}

/**
 * 	описание в application_api.h
*/
//void api_master_event_proccess(uint8_t index)
//{
//    com_master_proccess(EVT_BTN_PRESSED(index, BTN_CLICK_SINGLE));
//}

#ifdef NRF51
uint8_t api_enter_fw_update_state(void)
{
	if((AppState == APP_STAND_BY)	
		  ||( AppState == APP_SLAVE_FW_UPDATE))
	{
		api_state_change(APP_SLAVE_FW_UPDATE);
		return 1;
	}
	return 0;
}
void api_fw_update_proc(void)
{
	uint32_t err_code;
	if( AppState == APP_SLAVE_FW_UPDATE )
	{
		err_code = app_timer_stop(app_timer_id);
		APP_ERROR_CHECK( err_code );

		err_code = app_timer_start(app_timer_id,APP_TIMER_TICKS(APP_FW_UPDATE_TIMEOUT_MS, APP_TIMER_PRESCALER), NULL );
		APP_ERROR_CHECK( err_code );
	}
}
#endif

/********************************************************************************/
/***************************CENTRAL************************************/
/********************************************************************************/

static void api_master_set_default( uint8_t tok_id, cm_dev_support_ac_t * required_ac )
{
    mct_transaction_t * tran = NULL;
    
    mct_link_t link;
    memset(&link, 0, sizeof(mct_link_t));
    
    mct_searchp_context_t search;
    memset(&search, 0, sizeof(mct_searchp_context_t));
    
    
    bool start_support = false;
    bool stop_support = false;
    
    uint32_t err_code;
    
	APP_PRINTF(0, RTT_CTRL_TEXT_BRIGHT_GREEN"[APPL]: api_master_pairing_complete\r\n"RTT_CTRL_RESET);
    
    // Тип транзакции READ_WRITE не рекомендуется использовать, если не будет гарантированной записи
    // иначе пока транзакция не будет закрыта остальные транзакции не возможны
    err_code = mct_open_transaction(&tran, TRAN_READ_WRITE, TRAN_COMMON_PERM);
    if (err_code != NRF_ERROR_BUSY){
        APP_ERROR_CHECK(err_code);
        
        bool defaultAllowFl = true;
        
//  Чтобы пэйринг по дефолту был доступен 5 раз нужно раскоментировать код ниже
//        search.params.filter.event_mask = EVENT_CODE_MASK; // Также будем искать по коду события
//        search.params.values.event = BTN_EVENT_CODE<<EVENT_CODE_Pos; // Только события нажатия кнопки
//        
//        uint8_t evt_cnt = 0;
//        while (mct_find_link(tran, &search, &link) == NRF_SUCCESS){ // События найдено
//            if (link.event & ((1ul<<PairButtonId)<<(BTN_EVT_MASK_Pos))) {
//                evt_cnt++;
//                if (evt_cnt > 5) { // 5 - количество событий, позволенных для кнопки PairButtonId
//                   defaultAllowFl = false; // Тогда события не нужны
//                   break;
//                }
//            }
//        }
        
        if (required_ac->is_support[0]){
            start_support = true;
        }
        if (required_ac->is_support[1]){
            stop_support = true;
        }
        
        if(defaultAllowFl && (stop_support || start_support))
        {
            link.tok_id = tok_id;
            
            if (stop_support) {            
                link.event = EVT_REGULATOR(EVT_REG_TEMPERATURE, EVT_REG_OFF);
                link.ac = AC_EXTENTION_STOP_CMD;
                link.data[0]=0xFF;
                link.data[1]=0xFF;
                link.enable = 0x01;
                link.perm = MCT_PERM(MCT_PERM_ANY_USER, MCT_PERM_ANY_USER); // полный доступ
                // Рекомендуется создавать события с ограничениями НЕ ПРИ ПРЯМОМ ПЭЙРИНГЕ
                // поскольку при анпейринге такие события не будут удалены.
                //
                // Событие, которое может быть прочитано только устройством
                //link.perm = MCT_PERM(MCT_PERM_ONLY_MASTER, MCT_PERM_ANY_USER);
                // Событие, которое может быть изменено только устройством
                //link.perm = MCT_PERM(MCT_PERM_ANY_USER, MCT_PERM_ONLY_MASTER);
                // Событие, которое не может быть изменено и не может быть прочитано через приложение
                //link.perm = MCT_PERM(MCT_PERM_ONLY_MASTER, MCT_PERM_ONLY_MASTER);
                link.uuid = 0;

                err_code = mct_set_link(tran, &link);
                if (err_code != NRF_ERROR_NO_MEM)
                    APP_ERROR_CHECK(err_code);
            }
            
            if (start_support){
                link.event = EVT_REGULATOR(EVT_REG_TEMPERATURE, EVT_REG_MUCH_MORE);
                link.ac = AC_EXTENTION_START_CMD;
                link.data[0]=0xFF;
                link.data[1]=0xFF;
                link.enable = 0x01;
                link.perm = MCT_PERM(MCT_PERM_ANY_USER, MCT_PERM_ANY_USER); // полный доступ
                link.uuid = 0;

                err_code = mct_set_link(tran, &link);
                if (err_code != NRF_ERROR_NO_MEM)
                    APP_ERROR_CHECK(err_code);
            }
        }
        
        err_code = mct_commit(&tran);
        APP_ERROR_CHECK(err_code);
        
        // Уведомляем модули о обновлении mct
        com_slave_mct_notify();
//        api_param_update();
    }
}

static void api_master_evt_handler(com_master_evt_t * p_evt) {
    switch (p_evt->id) {
        case COM_MASTER_PAIRING_SUCCESS:{
            api_state_change(APP_STAND_BY);
            break;
        }
        case COM_MASTER_PAIRING_ERROR: {
            APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_GREEN"[APPL]: api_master_pairing_error\r\n"RTT_CTRL_RESET);
            api_state_change(APP_STAND_BY);
            break;
        }
        case COM_MASTER_DEFAULT_CONFIG_REQUIRED:{
            api_master_set_default(
                p_evt->param.default_config_required.tok_id,
                p_evt->param.default_config_required.p_required_ac
            );
            break;
        }
    }
}

#ifdef NRF51
uint8_t api_go_to_stand_by(void)
{
	if(( AppState != APP_STAND_BY )
		&& ( AppState != APP_PAIRING )
		&& ( AppState != APP_SLAVE_FW_UPDATE )
    )
	{
		return 0;
	}
	if( AppState == APP_PAIRING
		|| AppState == APP_SLAVE_FW_UPDATE
    )
	{
		api_state_change(APP_STAND_BY);
	}
	return 1;	
}
#endif

//void api_temp_process(void)
//{
//	for(uint8_t i=0; i<TEMPERATURE_TRIGGER_MAX_COUNT;i++)
//	{
//		temp_event_state_read(&app_temp_trigger[i]);
//		
//		if(app_temp_trigger[i].temp_event == TEMP_EVENT_CAPTURED)
//		{
//            com_master_proccess(app_temp_trigger[i].com_event);
//			app_temp_trigger[i].temp_event = TEMP_NO_EVENT;
//			APP_PRINTF(0,RTT_CTRL_TEXT_BRIGHT_GREEN"[TEMP]: TEMP TRIGGER %d\r\n",app_temp_trigger[i].trigger_temp_value);
//		}
//	}
//}

static void master_process_event(mct_link_t *link) {
    switch(link->ac){
//        case AC_EXTANSION_START_CMD:{
//                if (api_state_check(APP_PASSIVE)) {
//                    api_state_change(APP_STAND_BY);
//                }
//            }
//            break;
//        case AC_EXTANSION_STOP_CMD: {
//            if (!api_state_check(APP_PASSIVE))
//                 api_state_change(APP_PASSIVE);
//            }
//            break;
        default: // неизвестный action code
            break;
    }
}

void api_param_update(void)
{
    //temp_param_update(app_temp_trigger);
    return;
}
/*****************************************************************************************************/
///////////Календарь ////////////////////
uint8_t api_set_calendar_data(void *timestamp, void *shift) {
    calendar_utc_t m_time_stamp = 0;
    calendar_shift_utc_t m_shift = 0;

    AppCurrUtcTime = m_time_stamp = uint32_decode(timestamp); //преобразуем данные массива в uint32_t
    AppCurrUtcShiftTime = m_shift = uint32_decode(shift); //преобразуем данные массива в uint32_t
    
    uint32_t err_code = calendar_set_time(m_time_stamp, m_shift);
    switch (err_code) {
        case NRF_SUCCESS:
            //APP_ERROR_CHECK(
            pcf8563_set_time_utc(&m_time_stamp);
            return ERSP_SUCCESS;
        case NRF_ERROR_INVALID_DATA:
            return ERSP_ERROR_INVALID_DATA;
        case DRIVER_MODULE_NOT_INITIALZED:
            return ERSP_ERROR_INTERNAL;
    }

    return ERSP_ERROR_UNKNOWN;
}

/**
 * 	описание в application_api.h
 */
void api_get_calendar_data(void *timestamp, void *shift) {
    calendar_utc_t m_time_stamp = 0;
    calendar_shift_utc_t m_shift = 0;

    //получаем данные о текущем UTC и времени сдвига
    uint32_t err_code = calendar_get_utc_time(&m_time_stamp, &m_shift);
    APP_ERROR_CHECK(err_code);

    memcpy(timestamp, &m_time_stamp, sizeof (calendar_utc_t));
    memcpy(shift, &m_shift, sizeof (calendar_shift_utc_t));
}

void api_motor_move(uint8_t direction)
{
  #ifdef MY_DEBUG
    return;
  #endif
    switch(direction)
    {
        case STOP_VALVE:
            motorStop();
            app_bValveMustMoving = false;
            break;
        case OPEN_VALVE:
            if(app_curr_motor_cnt > MOTOR_CNT_EDGE_SHIFT || app_full_motor_cnt == 0)//или в калибровке
            {
                motorMoveForward();//открыть клапан
                app_bValveMustMoving = true;
                app_bValveStopped = false;//так как период измерений 0.5с 
            }
            app_motor_direction = direction;
            display_write_symbol_data(SYMB_T17_VALVE_CLOSED, IND_CLR);
            break;
        case CLOSE_VALVE:
            if(app_curr_motor_cnt < app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT || app_full_motor_cnt == 0)//или в калибровке
            {
                motorMoveBackward();//закрыть клапан
                app_bValveMustMoving = true;
                app_bValveStopped = false;//так как период измерений 0.5с 
            }
            app_motor_direction = direction;
            display_write_symbol_data(SYMB_T17_VALVE_CLOSED, IND_SET);
            break;
    }
}

void api_state_change_to_error(uint8_t err)
{
    AppSubstate = err;
    api_state_change(APP_ERROR);
}

void app_start_timer_for_next_motor_regulation(void)
{
    int temper_diff = (int)(AppTargetTemperature - AppCurrTemp);
    int app_motor_next_move_time_minutes = MAX_TIME_OF_NEXT_MOTOR_REGULATION_MINUTES - abs(temper_diff);
    if(app_motor_next_move_time_minutes < MIN_TIME_OF_NEXT_MOTOR_REGULATION_MINUTES) 
        app_motor_next_move_time_minutes = MIN_TIME_OF_NEXT_MOTOR_REGULATION_MINUTES;
    if(app_motor_next_move_time_minutes > MAX_TIME_OF_NEXT_MOTOR_REGULATION_MINUTES) 
        app_motor_next_move_time_minutes = MAX_TIME_OF_NEXT_MOTOR_REGULATION_MINUTES;
    //при входе в standby (это основные режимы работы) стартуем таймер следующей регулировки штока
    // и это время зависит от разности температур (текущей и целевой)
    app_timer_stop(app_timer_id);
    app_timer_start( app_timer_id, APP_TIMER_TICKS(app_motor_next_move_time_minutes * 60000, APP_TIMER_PRESCALER), NULL );
}

void app_start_motor_regulation(void)
{
    float temper_diff = AppTargetTemperature - AppCurrTemp;
    //если температура не вышла из гистерезиса или уже стоим на предельных значениях штока и поэтому продолжать дальше нельзя
    if(fabs(temper_diff) < AppParam.A5_T_hysteresis ||
        (temper_diff > 0 && app_curr_motor_cnt < MOTOR_CNT_EDGE_SHIFT) ||
        (temper_diff < 0 && app_curr_motor_cnt > app_full_motor_cnt - MOTOR_CNT_EDGE_SHIFT))
    {
        app_start_timer_for_next_motor_regulation();
    }
    else
    {
        app_target_motor_cnt = (int)((float)app_curr_motor_cnt - temper_diff * (float)MOTOR_CNTS_FOR_ONE_GRAD_RELATION * app_full_motor_cnt);
        APP_PRINTF(0, "app_target_motor_cnt = %d\n", app_target_motor_cnt);
        if(temper_diff > 0)
        {
            api_motor_move(OPEN_VALVE);//c контролем шага
            com_master_proccess(EVT_REGULATOR(EVT_REG_TEMPERATURE, EVT_REG_MUCH_MORE));
        }
        else
        {
            api_motor_move(CLOSE_VALVE);//c контролем шага
            com_master_proccess(EVT_REGULATOR(EVT_REG_TEMPERATURE, EVT_REG_OFF));
        }
        app_timer_stop(app_timer_id);
        app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
    }
}

bool app_param_check_init()
{
    uint16_t crc = crc16_compute((uint8_t*)&param, sizeof(param)-sizeof(param.CRC),NULL);

    if(param.Ver != PARAM_VER || crc != param.CRC)
    {
        APP_PRINTF(0,"Param_Init false, param.Ver=%d, crc=%X, param.CRC=%X \r\n", param.Ver, crc, param.CRC);
        memset(&param,0xFF,sizeof(param_t));
        return false;
    }
    APP_PRINTF(0,"Param_Init true\r\n", param.Ver, crc, param.CRC);
    return true;//successive initialization/reading of parameters
}

uint8_t api_get_current_week_day(void)
{
    uint32_t curutc, curshift;
    api_get_calendar_data(&curutc, &curshift);
    return (uint8_t)(((curutc + curshift)/86400 + 4) % 7);//+4 это четверг в начале отсчета UTC
}

void app_state_change_after_stop(app_state_t state)
{
    if(app_bValveStopped && app_bValveMustMoving == false)//чтобы счетчик шагов не сбился
    {
        APP_PRINTF(0, "last app_curr_motor_cnt when stopped = %d\n", app_curr_motor_cnt);
        api_state_change(state);
    }
    else
    {
        //подсостояние состояния APP_STAND_BY, APP_VENTILATION, APP_BOOST, APP_OP_STATE, APP_OFF_STATE: 
        //  ожидание полного останова мотора
        AppSubstate = SUBSTATE_2;
        app_nextstate = state;//после останова надо будет перейти в это состояние
//        app_in_program_transition = true;//переходное состояние из одной программы в другую
        api_motor_move(STOP_VALVE);
        APP_PRINTF(0,"motorStop():    app_state_change_after_stop()\n");
    }
}

void app_set_valve_closed_percent(uint8_t closed_percent)
{
    app_target_motor_cnt = (int)(((float)closed_percent * app_full_motor_cnt) / (float)100.);
    if(app_curr_motor_cnt < app_target_motor_cnt - MOTOR_CNT_EDGE_SHIFT / 2)
    {
        api_motor_move(CLOSE_VALVE);//c контролем шага
    }
    else if(app_curr_motor_cnt > app_target_motor_cnt + MOTOR_CNT_EDGE_SHIFT / 2)
    {
        api_motor_move(OPEN_VALVE);//c контролем шага
    }
    app_timer_start( app_timer_id, APP_TIMER_TICKS(MOTOR_CONTROL_TICK_MS, APP_TIMER_PRESCALER), NULL );
}
//индикация коннекта по BLE
void api_ind_connected(bool ConnectedFl)
{
    app_ble_connected = ConnectedFl;
    display_write_symbol_data(SYMB_T19_BLE_APP_CONNECTED, ConnectedFl);
}
//сохранение параметров после 5 секунд отсутствия новых команд, эмуляция бездействия нажатий кнопок
void api_save_params_with_delay(void)
{
    app_button_inactivity_cnt = SUBSTATE_0;
    app_timer_stop(led_interval_id);
    app_timer_start( led_interval_id, APP_TIMER_TICKS(LED_TICK_MS, APP_TIMER_PRESCALER), NULL );
}
//установка нового режима из мобильного приложения
bool api_set_program(uint8_t prg, bool lockCal, float targetTemperature)
{
    app_state_t state;
    if(prg > APP_PRG_CLOSE) return false;
    switch(prg)
    {
        case APP_PRG_MANUAL:
            if(targetTemperature < MIN_REG_TEMPERATURE || targetTemperature > MAX_REG_TEMPERATURE)
            {
                return false;
            }
            state = APP_STAND_BY;
            AppParam.TargetTemperature = AppTargetTemperature = targetTemperature;
            soft_ind_target_temp(AppTargetTemperature, IND_SET);
            break;
        case APP_PRG_BOOST:
            state = APP_BOOST;
            break;
        case APP_PRG_OPEN:
            state = APP_OP_STATE;
            break;
        case APP_PRG_CLOSE:
            state = APP_OFF_STATE;
            break;
    }
    api_lock_calendar(lockCal);

    if(state != AppState)//защита от повторной установки той же программы (состояния)
    {
        app_state_change_after_stop(state);
    }
    api_save_params_with_delay();
    return true;
}
//возвращает текущую программу
uint8_t api_get_program(void)
{
    static uint8_t prg = APP_PRG_MANUAL;
//    if(app_in_program_transition) return prg;//то есть при переходе возвращаем предыдущую установленную программу
    switch(AppState)
    {
        case APP_STAND_BY:
            prg = APP_PRG_MANUAL;
            break;
        case APP_BOOST:
            prg = APP_PRG_BOOST;
            break;
        case APP_OP_STATE:
            prg = APP_PRG_OPEN;
            break;
        case APP_OFF_STATE:
            prg = APP_PRG_CLOSE;
            break;
        default:
            //программа не меняется
            break;
    }
    return prg;
}
void app_check_tasks_availability(void)
{
    if(AppLockCalendarState == UNLOCK_CALENDAR_STATE)
    {
                  //постоянно проверяем наличие хотя бы одной незавершенной задачи в календаре
//                bool bTaskExist;
//                uint32_t error = evcal_at_least_one_task_is_not_over(&bTaskExist);
//                if(error != NRF_SUCCESS || !bTaskExist)
        //постоянно проверяем наличие хотя бы одной задачи в календаре
        uint8_t taskNum;
        uint32_t error = evcal_get_task_number(&taskNum);
        if(error != NRF_SUCCESS || taskNum == 0)
        {
            AppCountdownSecForNoCalendarTasks = APP_BLINKING_AUTO;
            AppFlagBlinkingNoCalendarTasks = true;
            AppLockCalendarState = LOCK_CALENDAR_STATE;
            //app_state_change_after_stop(APP_NO_CALENDAR_TASKS);                
        }
    }
}

void api_lock_calendar(bool lockCal)
{
    AppLockCalendarState = lockCal;
    if(AppState == APP_STAND_BY || AppState == APP_VENTILATION || AppState == APP_BOOST ||
         AppState == APP_OP_STATE || AppState == APP_OFF_STATE)
    {
        display_write_symbol_data(SYMB_T12_AUTOMATIC_MODE, (AppLockCalendarState==UNLOCK_CALENDAR_STATE ? IND_SET : IND_CLR));
    }

    app_check_tasks_availability();
    AppParam.LockCalendarState = AppLockCalendarState;
    api_save_params_with_delay();
}

Made on
Tilda