/*================================================================ Copyright (c) 2021, Quectel Wireless Solutions Co., Ltd. All rights reserved. Quectel Wireless Solutions Proprietary and Confidential. =================================================================*/ /*================================================================= EDIT HISTORY FOR MODULE This section contains comments describing changes made to the module. Notice that changes are listed in reverse chronological order. WHEN WHO WHAT, WHERE, WHY ------------ ------- ------------------------------------------------------------------------------- =================================================================*/ /*=========================================================================== * include files ===========================================================================*/ #include #include #include #include "ql_api_osi.h" #include "ql_log.h" #include "psm_demo.h" #include "ql_api_datacall.h" #include "ql_power.h" #include "ql_api_rtc.h" #include "ql_api_dev.h" #include "ql_api_nw.h" /*=========================================================================== * Macro Definition ===========================================================================*/ #define QL_PSMDEMO_LOG_LEVEL QL_LOG_LEVEL_INFO #define QL_PSMDEMO_LOG(msg, ...) QL_LOG(QL_PSMDEMO_LOG_LEVEL, "ql_PSM", msg, ##__VA_ARGS__) #define QL_PSMDEMO_LOG_PUSH(msg, ...) QL_LOG_PUSH("ql_PSM", msg, ##__VA_ARGS__) #define leapyear(year) ((year) % 4 == 0) #define days_in_year(a) (leapyear(a) ? 366 : 365) #define days_in_month(a) (month_days[(a) - 1]) #define FEBRUARY 2 #define STARTOFTIME 1970 #define SECDAY 86400L #define SECYR (SECDAY * 365) #if !defined(require_action) #define require_action(x, action, str) \ do \ { \ if(x != 0) \ { \ QL_PSMDEMO_LOG(str); \ {action;} \ } \ } while( 1==0 ) #endif #define QL_PSMDEMO_SIM_ID 0 #define QL_PSMDEMO_WAKE_UP_TIME 60 //unit:s /*=========================================================================== * Variate ===========================================================================*/ ql_task_t psm_task = NULL; ql_sem_t psm_rtc_update_sem =NULL; ql_sem_t psm_data_reg_sem = NULL; static int month_days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /*=========================================================================== * Functions ===========================================================================*/ static int ql_rtc_fix_weekday(ql_rtc_time_t *rtc_time) { int leapsToDate; int lastYear; int day; int MonthOffset[] = { 0,31,59,90,120,151,181,212,243,273,304,334 }; if (rtc_time->tm_year < 1753) return -1; lastYear=rtc_time->tm_year-1; //Number of leap corrections to apply up to end of last year leapsToDate = lastYear/4 - lastYear/100 + lastYear/400; //This year is a leap year if it is divisible by 4 except when it is divisible by 100 unless it is divisible by 400 //e.g. 1904 was a leap year, 1900 was not, 1996 is, and 2000 will be if((rtc_time->tm_year%4==0) && ((rtc_time->tm_year%100!=0) || (rtc_time->tm_year%400==0)) && (rtc_time->tm_mon>2)) { //We are past Feb. 29 in a leap year day=1; } else { day=0; } day += lastYear*365 + leapsToDate + MonthOffset[rtc_time->tm_mon-1] + rtc_time->tm_mday; rtc_time->tm_wday=day%7; return 0; } static int ql_sec_conv_rtc_time(int64_t* time_t, ql_rtc_time_t *rtc_time) { int i; long hms, day; day = *time_t / SECDAY; hms = *time_t % SECDAY; //Hours, minutes, seconds are easy rtc_time->tm_hour = hms / 3600; rtc_time->tm_min = (hms % 3600) / 60; rtc_time->tm_sec = (hms % 3600) % 60; //Number of years in days for (i = STARTOFTIME; day >= days_in_year(i); i++) { day -= days_in_year(i); } rtc_time->tm_year = i; //Number of months in days left if (leapyear(rtc_time->tm_year)) { days_in_month(FEBRUARY) = 29; } for (i = 1; day >= days_in_month(i); i++) { day -= days_in_month(i); } days_in_month(FEBRUARY) = 28; rtc_time->tm_mon = i; //Days are what is left over (+1) from all that. rtc_time->tm_mday = day + 1; //Determine the day of week return ql_rtc_fix_weekday(rtc_time); } static int ql_rtc_conv_sec_time(int64_t* time_t, ql_rtc_time_t* rtc_time) { int mon = rtc_time->tm_mon; int year = rtc_time->tm_year; int64_t days, hours; mon -= 2; if (0 >= (int)mon) { // 1..12 -> 11,12,1..10 mon += 12; //Puts Feb last since it has leap day year -= 1; } days = (unsigned long)(year / 4 - year / 100 + year / 400 +367 * mon / 12 + rtc_time->tm_mday) + year * 365 - 719499; hours = (days * 24) + rtc_time->tm_hour; *time_t = (hours * 60 + rtc_time->tm_min) * 60 + rtc_time->tm_sec; return 0; } void ql_psm_nw_notify_cb(uint8_t sim_id, unsigned int ind_type, void *ind_msg_buf) { switch(ind_type) { case QUEC_NW_NITZ_TIME_UPDATE_IND: { QL_PSMDEMO_LOG("time update!"); ql_rtos_semaphore_release(psm_rtc_update_sem); break; } case QUEC_NW_DATA_REG_STATUS_IND: { ql_nw_common_reg_status_info_s *data_reg_status=(ql_nw_common_reg_status_info_s *)ind_msg_buf; QL_PSMDEMO_LOG("Sim%d data reg status changed, current status is %d", sim_id, data_reg_status->state); if((QL_NW_REG_STATE_HOME_NETWORK == data_reg_status->state) || (QL_NW_REG_STATE_ROAMING == data_reg_status->state)) { ql_rtos_semaphore_release(psm_data_reg_sem); } break; } } } void ql_psm_cb(void *ctx) { return; } static bool ql_psm_set_cfun(void) { uint8_t cfun = 0; int ret = 0; if(ql_dev_get_modem_fun(&cfun, QL_PSMDEMO_SIM_ID) != QL_DEV_SUCCESS) { return false; } QL_PSMDEMO_LOG("cfun: %d", cfun); if(QL_DEV_CFUN_MIN == cfun) { return true; } ret = ql_dev_set_modem_fun(QL_DEV_CFUN_AIR, 0, QL_PSMDEMO_SIM_ID); if(ret != QL_DEV_SUCCESS) { QL_PSMDEMO_LOG("set QL_DEV_CFUN_AIR, ret: 0x%x", ret); return false; } ret = ql_dev_set_modem_fun(QL_DEV_CFUN_FULL, 0, QL_PSMDEMO_SIM_ID); if(ret != QL_DEV_SUCCESS) { QL_PSMDEMO_LOG("set QL_DEV_CFUN_FULL, ret: 0x%x", ret); return false; } return true; } static bool ql_psm_set_alarm(void) { int ret = 0; int64_t time_sec=0; ql_rtc_time_t tm={0}; ret = ql_rtc_get_time(&tm); if(ret != QL_RTC_SUCCESS) { QL_PSMDEMO_LOG("get time err"); return false; } ql_rtc_print_time(tm); ql_rtc_conv_sec_time(&time_sec,&tm); time_sec += QL_PSMDEMO_WAKE_UP_TIME; ql_sec_conv_rtc_time(&time_sec,&tm); ret = ql_rtc_set_alarm(&tm); if(ret != QL_RTC_SUCCESS) { QL_PSMDEMO_LOG("set alarm err"); return false; } ql_rtc_print_time(tm); //Enable RTC alarm ret = ql_rtc_enable_alarm(1); if(ret != QL_RTC_SUCCESS) { QL_PSMDEMO_LOG("enable alarm err"); return false; } return true; } static void ql_psm_demo_thread(void *param) { uint8_t source = 0; int ret = 0,retry_count = 0; int profile_idx = 1; //range 1 to 7 ql_nw_reg_status_info_s* nw_info = (ql_nw_reg_status_info_s*)calloc(1,sizeof(ql_nw_reg_status_info_s)); ql_data_call_info_s* info = (ql_data_call_info_s*)calloc(1,sizeof(ql_data_call_info_s)); if(nw_info == NULL|| info ==NULL) { QL_PSMDEMO_LOG("calloc fail!"); goto PSM_EXIT; } if(QL_SLEEP_SUCCESS != ql_psm_register_enter_cb(ql_psm_cb,NULL)) { QL_PSMDEMO_LOG("register psm callback fail!"); goto PSM_EXIT; } if(QL_NW_SUCCESS != ql_nw_register_cb(ql_psm_nw_notify_cb)) { QL_PSMDEMO_LOG("register network callback fail"); goto PSM_EXIT; } if(0 == ql_get_powerup_reason(&source) && QL_PWRUP_PSM_WAKEUP == source) { QL_PSMDEMO_LOG("PSM WAKEUP!"); ret = ql_get_data_call_info(QL_PSMDEMO_SIM_ID, profile_idx, info); if(QL_DATACALL_SUCCESS == ret && info->v4.state) { QL_PSMDEMO_LOG("info.v4.addr.ip: %s", ip4addr_ntoa(&(info->v4.addr.ip))); } else{ QL_PSMDEMO_LOG("ql_get_data_call_info fail!"); goto PSM_EXIT; } //Reset alarm for PSM wake up if(!ql_psm_set_alarm()) { QL_PSMDEMO_LOG("alarm set fail!"); goto PSM_EXIT; } } else{ //Enable PSM when first power on char *periodic_TAU_time="00100001"; //T3412 char *active_time="00000001"; //T3324 if(QL_SLEEP_SUCCESS == ql_psm_sleep_enable(QL_PSMDEMO_SIM_ID, true, periodic_TAU_time, active_time)) { ret = ql_rtos_semaphore_create(&psm_rtc_update_sem, 0); if(ret != QL_OSI_SUCCESS) { QL_PSMDEMO_LOG("psm_rtc_update_sem created failed, ret = 0x%x", ret); goto PSM_EXIT; } ret = ql_rtos_semaphore_create(&psm_data_reg_sem, 0); if(ret != QL_OSI_SUCCESS) { QL_PSMDEMO_LOG("ql_data_reg_sem created failed, ret = 0x%x", ret); goto PSM_EXIT; } //Call a necessary delay to avoid failure of getting cfun status when power on. ql_rtos_task_sleep_ms(2000); if(!ql_psm_set_cfun()) { QL_PSMDEMO_LOG("cfun set fail!"); goto PSM_EXIT; } if(ql_rtos_semaphore_wait(psm_rtc_update_sem, 10000)) { QL_PSMDEMO_LOG("psm_rtc_update_sem time out"); } //PDP active ret = ql_nw_get_reg_status(QL_PSMDEMO_SIM_ID, nw_info); if((QL_NW_REG_STATE_HOME_NETWORK != nw_info->data_reg.state) && (QL_NW_REG_STATE_ROAMING != nw_info->data_reg.state)) { if(ql_rtos_semaphore_wait(psm_data_reg_sem, 10000)) { QL_PSMDEMO_LOG("ql_data_reg_sem time out"); goto PSM_EXIT; } } ret = ql_set_data_call_asyn_mode(QL_PSMDEMO_SIM_ID, profile_idx, 0); //Set to sync mode //Try reconnect 10 times, time interval is 20 seconds while(((ret = ql_start_data_call(QL_PSMDEMO_SIM_ID, profile_idx, QL_PDP_TYPE_IP, NULL, NULL, NULL, 0)) != QL_DATACALL_SUCCESS) && (retry_count < 10)) { retry_count++; QL_PSMDEMO_LOG("gprs reconnect fail, the retry count is %d", retry_count); ql_rtos_task_sleep_ms(20000); } if(QL_DATACALL_SUCCESS != ret) { goto PSM_EXIT; } ret = ql_get_data_call_info(QL_PSMDEMO_SIM_ID, profile_idx, info); if(QL_DATACALL_SUCCESS == ret && info->v4.state) { QL_PSMDEMO_LOG("info.v4.addr.ip: %s", ip4addr_ntoa(&(info->v4.addr.ip))); } else{ QL_PSMDEMO_LOG("ql_get_data_call_info fail!"); goto PSM_EXIT; } //set alarm if(!ql_psm_set_alarm()) { QL_PSMDEMO_LOG("alarm set fail!"); goto PSM_EXIT; } QL_PSMDEMO_LOG("psm enable!"); ql_autosleep_enable(true); } else { QL_PSMDEMO_LOG("psm enable fail!"); goto PSM_EXIT; } } PSM_EXIT: if(nw_info) { free(nw_info); nw_info = NULL; } if(info) { free(info); info = NULL; } ql_rtos_semaphore_delete(psm_rtc_update_sem); ql_rtos_semaphore_delete(psm_data_reg_sem); psm_rtc_update_sem = NULL; psm_data_reg_sem = NULL; ql_nw_register_cb(NULL); ql_rtos_task_delete(NULL); } void ql_psm_app_init(void) { QlOSStatus err = QL_OSI_SUCCESS; err = ql_rtos_task_create(&psm_task, 4096, APP_PRIORITY_NORMAL, "ql_psmdemo", ql_psm_demo_thread, NULL, 3); if(err != QL_OSI_SUCCESS) { QL_PSMDEMO_LOG("psm demo task created failed", err); return; } return; }