业务功能
无线定时模块从业务角度来看用于支撑WiFi的定时开关功能以及系统定时重启功能


模块层级划分

对应以上两个功能,在业务层实现了 web 接口以及各个运营商对应的 ubus 接口,在接口层实现了操作 mib 表的接口供业务层调用,CM 操作使用 IPC call 对服务层的各项进行配置,最后调用 ioctl 对系统生效

在接口层修改定时计划表后,通过调用 igdCmOamCtrl() 进行 WiFi 无线定时规则的更新,并通过 cmd 执行 wtimer 进程将规则写入 wtimer.rule
wtimerd 负责读 wtimer.rule 并判断规则是否命中,使用 cmd 调用对应的 app 执行定时规则
表结构
mib 表: WW_SCHEDULE_TAB
<Dir Name="WW_SCHEDULE_TAB"> <!--index=0-->
<Value Name="ulStateAndIndex" Value=""/>
<Value Name="ulTimerIndex" Value=""/>
<Value Name="ulSSIDIndex" Value="0"/>
<Value Name="aucStartTime" Value="00:00"/>
<Value Name="aucEndTime" Value="00:00"/>
<Value Name="aucControlCycle" Value="0"/>
<Value Name="ucTimerEnable" Value="0"/>
<Value Name="ucAction" Value="0"/>
<Value Name="aucPad" Value=""/>
<Value Name="ulBitmap" Value="0"/>
</Dir>IGD 表: IGD_WW_SCHEDULE_TAB
#define IGD_WW_SCHEDULE_TAB (IGD_DEVICE_TAB_START + 452)
#define IGD_WW_SCHEDULE_MAX_NUM 32
typedef struct {
uword32 ulStateAndIndex;
uword32 ulTimerIndex;
uword32 ulSSIDIndex;
word8 aucStartTime[TIME_LENGTH];
word8 aucEndTime[TIME_LENGTH];
word8 aucControlCycle[CYCLE_MAX_LENGTH];
uword8 ucTimerEnable;
uword8 ucAction;
uword8 aucPad[2];
uword32 ulBitmap;
} __PACK__ IgdwwScheduleTab;数据流

Web 或者 ubus 上发送的数据被解析后通过 CM 层写入 mib 表,然后通过 igdCmOamCtrl() 运行 cmd 更新 wtimer 中定时规则链表 wtimer_list 并写入 wtimer.rule 规则文件,wtimerd 负责从规则文件中读取最近规则并计算时间设置定时器,定时器终止时发出信号更新 wtimer.rule 规则文件并计算下一任务的超时时间
业务层
Web Api
主要包括两个接口:
- 增删改:parse_wireless_wlan_time(cJSON *post)
- 查:get_wireless_wlan_time(request *wp, cJSON *root)
POST:
static unsigned int parse_wireless_wlan_time(cJSON *post)
{
unsigned int err = WWAPI_ERRCODE_OK;
int igd_ret = 0;
IgdwwScheduleTab TimerTask = {0};
char week_day[8] = {0};
HI_OS_MEMSET_S(&TimerTask, sizeof(TimerTask), 0, sizeof(TimerTask));
const cJSON *data = JSON_GetObjectItem_Check(data, post, "data", &err);
unsigned int action = JSON_GetNumberItem_Check(action, data, "action", &err);
const cJSON *ruleList = JSON_GetObjectItem_NoCheck(ruleList, data, "ruleList", &err);
if (has_ruleList)
{
unsigned int list_num = 0;
list_num = cJSON_GetArraySize(ruleList);
for (unsigned int i = 0; i < list_num; i++)
{
// 解析规则json数据到TimerTask
...
// 添加和修改需检查规则冲突
if (WWAPI_RULE_ACT_DEL != action)
{
igd_ret = check_timer_conflict(&TimerTask);
if (0 != igd_ret)
{
...
return igd_ret;
}
}
// 执行action写mib
switch (action)
{
// 新增规则
case WWAPI_RULE_ACT_ADD:
igd_ret = igdCmConfAdd(IGD_WW_SCHEDULE_TAB, (unsigned char *)&TimerTask, sizeof(IgdwwScheduleTab));
...
break;
// 修改规则
case WWAPI_RULE_ACT_MOD:
igd_ret = igdCmConfSet(IGD_WW_SCHEDULE_TAB, (unsigned char *)&TimerTask, sizeof(IgdwwScheduleTab));
...
break;
// 删除规则
case WWAPI_RULE_ACT_DEL:
igd_ret = igdCmConfDel(IGD_WW_SCHEDULE_TAB, (unsigned char *)&TimerTask, sizeof(IgdwwScheduleTab));
...
break;
default:
break;
}
}
// 更新wtimer.rule文件
igdCmOamCtrl(IGD_CM_CMD_WRITE_WIFI_RULE_TO_WTIMER);
}
/* mesh配置同步 */
...
return err;
}其中除删除规则外需要进行冲突检测(判断设置规则是否与其它规则时间重合)
GET:
static unsigned int get_wireless_wlan_time(request *wp, cJSON *root)
{
int igd_ret = 0;
unsigned int rule_num = 0;
cJSON *data = JSON_AddObjectToObject(data, root, "data");
cJSON *wlan_time_list = JSON_AddArrayToObject(wlan_time_list, data, "ruleList");
IgdwwScheduleTab TimerTask[MAX_TIMER_RULES] = {0};
char week_day[16] = {0};
// 获取规则数
igd_ret = igdCmConfGetEntryNum(IGD_WW_SCHEDULE_TAB, &rule_num);
...
HI_OS_MEMSET_S(TimerTask, sizeof(IgdwwScheduleTab)*MAX_TIMER_RULES, 0, sizeof(IgdwwScheduleTab)*MAX_TIMER_RULES);
// 获取所有规则存入TimerTask数组
igd_ret = igdCmConfGetAllEntry(IGD_WW_SCHEDULE_TAB, (unsigned char *)TimerTask, sizeof(IgdwwScheduleTab)*MAX_TIMER_RULES);
...
//
for (unsigned int i = 0; i < rule_num; i++)
{
cJSON *rule = JSON_AddObjectToArray(rule, wlan_time_list);
JSON_AddNumberToObject(rule, "index", TimerTask[i].ulTimerIndex);
memset(week_day, 0, sizeof(week_day));
add_comma(TimerTask[i].aucControlCycle, week_day, sizeof(week_day));
JSON_AddStringToObject(rule, "repeatDay", week_day);
JSON_AddStringToObject(rule, "startTime", TimerTask[i].aucStartTime);
JSON_AddStringToObject(rule, "endTime", TimerTask[i].aucEndTime);
JSON_AddBoolToObject(rule, "enable", TimerTask[i].ucTimerEnable);
}
return WWAPI_ERRCODE_OK;
}Ubus Api
ubus 注册了三个方法:
UBUS_METHOD("getTimedTask", ahsapd_obj_timedtask_meth_getTimedTask, ahsapd_obj_timedtask_meth_getTimedTask_policy), // 输出所有定时规则
UBUS_METHOD("addTimedTask", ahsapd_obj_timedtask_meth_addTimedTask, ahsapd_obj_timedtask_meth_addTimedTask_policy), // 增加定时规则
UBUS_METHOD("deleteTimedTask", ahsapd_obj_timedtask_meth_deleteTimedTask, ahsapd_obj_timedtask_meth_deleteTimedTask_policy), // 删除定时规则- getTimedTask 方法:通过调用 CM 层
igdCmConfGet()分别从IGD_WW_SCHEDULE_TAB和IGD_SCHEDULED_REBOOT_TAB表中获取定时规则 - addTimedTask 方法:根据传入的
taskId调用 CM 层igdCmConfGet()查找是否有该规则,若有则调用 CM 层igdCmConfSet()修改该taskId对应规则,若没有则调用 CM 层igdCmConfAdd()新增一条规则,下面以无线定时为例:
static int add_wifi_timertask_info(CMCC_TIME_TASK_T *T_Task)
{
// T_Task为解析完成的Ubus指令
...
timerTask.ulTimerIndex = T_Task->taskId;
rsp = igdCmConfGet(IGD_WW_SCHEDULE_TAB, (unsigned char *)&timerTask, sizeof(timerTask));
...
if (0 != rsp)
{
//没获取到taskId对应的entry代表是新增
action = ADD_ACTION;
}
else
{
//获取到则是修改
action = SET_ACTION;
}
...
if (ADD_ACTION == action)
{
rsp = igdCmConfAdd(IGD_WW_SCHEDULE_TAB, (unsigned char *)&timerTask, sizeof(IgdwwScheduleTab));
}
else if (SET_ACTION == action)
{
rsp = igdCmConfSet(IGD_WW_SCHEDULE_TAB, (unsigned char *)&timerTask, sizeof(IgdwwScheduleTab));
}- deleteTimedTask 方法:
static int ahsapd_obj_timedtask_meth_deleteTimedTask(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
int ret = AHSAPD_FAIL;
struct blob_attr *tb[ARRAY_SIZE(ahsapd_obj_timedtask_meth_deleteTimedTask_policy)] = {NULL};
unsigned int taskId = 0;
// 解析msg
...
taskId = blobmsg_get_u32(tb[AHSAPD_TIMEDTASK_GDELETETIMEDTASK_TASKID]);
// 从两个igd表中查找taskId进行删除
ret = del_timertask_info(taskId);
if (AHSAPD_FAIL == ret)
{
AHSAPD_DBG("delete timertask info fail ");
goto exit;
}
ret = AHSAPD_OK;
exit:
return ret;
}CM 层接口
CM 层接口作用是对 mib 表进行增删改查,无线定时包括如下接口:
- igdCmScheduleGet (uword 8 *pucInfo, uword 32 len); 查看 mib 中关于无线定时规则的单条配置信息,通过传入 index 进行查找
- igdCmScheduleAdd (uword 8 *pucInfo, uword 32 len); 添加单条无线定时规则至 mib 中
- igdCmScheduleSet (uword 8 *pucInfo, uword 32 len); 修改指定的 index 修改 mib 中的定时规则
- igdCmScheduleDel (uword 8 *pucInfo, uword 32 len); 通过指定的 index 删除 mib 中的定时规则
- igdCmScheduleEntryNumGet (uword 8 *pucInfo, uword 32 len); 获取定时规则的数量
- igdCmScheduleEntryGetAll (uword 8 *pucInfo, uword 32 len); 获取所有的定时规则
- igdCmOamCtrl (IGD_CM_CMD_WRITE_WIFI_RULE_TO_WTIMER); 遍历所有定时规则,将开启规则写入 wtimer,并判断是否有击中时间的规则
wtimer
wtimer.c 负责接收 cmd 命令来将操作 /var/wtimer.rule 规则文件,包括 add 、delete、list 三种分别用于写入,删除以及列出规则,下面是写入规则代码:
// 命令行解析的规则加入链表
list_add(&timer->list, &head);
...
// 打开规则文件
FILE* fp = fopen(WTIMER_RULE_FILE, mode);
...
struct wtimer_list* node = NULL;
// 遍历规则链表
list_for_each_entry(node, head, list) {
struct wtimer_rule* rule = &node->rule;
char line[256] = {0};
// 解析规则为以逗号隔开的一行写入文件
(void)snprintf(line, sizeof(line), "%08x,%08x,%s,%u,%u,%u,%u,%u,%u,%u:%s\n",
rule->signature, rule->add_time, rule->group, rule->type, rule->week,
rule->month, rule->day, rule->hour, rule->min, rule->sec, rule->shell);
int cnt = fputs(line, fp);
if (EOF == cnt) {
LOGWARN("fputs rule(%s) fail,errno(%d)\n", line, errno);
(void)fclose(fp);
return -2;
}
}wtimerd.c 创建一个信号文件描述符以及一个定时器文件描述符,使用 select() I/O 多路复用函数处理多个文件描述符的 I/O 操作,阻塞持续监控信号文件和时间同步文件是否可读和定时器超时,当定时器超时或接收到信号或系统时间被同步,则取消阻塞来执行定时规则,或者更新规则文件及最近规则的执行时间
update_wtimer_list(); // 读 wtimer.rule 更新计时器列表
struct timeval timeout = {0};
update_select_timeout(&timeout); // 更新计时器超时时间
while (1) {
fd_set rset = set;
int sel = select(max_fd+1, &rset, NULL, NULL, &timeout);
...
if (0 == sel) {
do_rule_action(); // 计时器超时,执行任务命令
update_select_timeout(&timeout); // 更新计时器超时时间
continue;
}
if (FD_ISSET(sfd, &rset)) {
do_check_signal(sfd); // 接收到信号,更新计时器列表
update_select_timeout(&timeout); // 更新计时器超时时间
continue;
}
if (FD_ISSET(tfd, &rset)) {
do_check_time(tfd); // 系统时间被同步,更新计时器动作时间
update_select_timeout(&timeout); // 更新计时器超时时间
continue;
}
LOGERR("call select exception,errno(%d)\n", errno);
return -2;
}触发定时器超时执行任务:
static void do_rule_action(void)
{
...
struct wtimer_list* timer = list_first_entry(&wtimer_head, typeof(*timer), list);
do_shell_cmd(timer->rule.shell); // 执行任务命令
...
switch (timer->rule.type) {
case RULE_INTERVAL:
/* fall-through */
case RULE_PER_WEEK:
/* fall-through */
case RULE_PER_MONTH:
/* fall-through */
case RULE_PER_DAY:
/* fall-through */
case RULE_PER_HOUR:
/* fall-through */
case RULE_PER_MIN:
// 周期任务,重新计算下次执行时间更新链表
/*
compute_act_time函数执行前提是 ntp 完成同步,负责计算触发的时间(计算机元年+相对启动时间的偏移量),根据规则时间的偏移量是否大于当前时间的偏移量来决定是否在当前周期触发,计算偏移量写入对应链表节点
*/
timer->act_time = compute_act_time(&timer->rule);
struct list_head* list = move_one_rule(&timer->list);
insert_wtimer_rule(&wtimer_head, list);
return;
case RULE_RELATIVE:
/* fall-through */
case RULE_ABSOLUTE:
/* fall-through */
default:
// 删除任务
del_one_rule(&timer->list);
return;
}
}更新超时时间:
static void update_select_timeout(struct timeval* timeout)
{
unsigned int uptime = get_uptime_sec();
unsigned int act_time = get_first_act_time();
timeout->tv_sec = (act_time > uptime) ? (act_time - uptime) : 0;
timeout->tv_usec= 0;
...
}timer_task_app

由于 wtimer 执行规则的方式是执行 cmd 命令,所以开关 wifi 以及重启系统的操作做成 app 方便 wtimer 调用。同时为了无线定时时间生效前断电设备,定时生效时间内上电设备,定时规则可以生效;在无线定时时间内断电设备,无线定时时间结束后再上电设备,无线可以被拉起,设计了 wifi_schedule app
reboot_once. c
int main(int argc, char **argv)
{
...
// 判断定时重启任务是否存在
int timer_idx = atoi(argv[1]);
rsp = igdCmConfGetEntryNum(IGD_SCHEDULED_REBOOT_TAB, &tasknum);
...
for (i = 0; i < tasknum; i++)
{
if (timer_idx == pTimerTask[i].ulScheduledIndex)
{
break;
}
}
free(pTimerTask);
if (i == tasknum)
{
printf("The timer_idx = %d is not exist!\n", timer_idx);
return -1;
}
// 若存在则关闭定时任务
...
timerTask.ulEnable = 0;
timerTask.ulBitmap = SCHEDULED_REBOOT_ATTR_CFG_MASK_ENABLE;
rsp = igdCmConfSet(IGD_SCHEDULED_REBOOT_TAB, (unsigned char *)&timerTask, sizeof(IgdwwScheduleTab));
...
// 执行重启
sleep(5);//等待mib保存
system("reboot");
return 0;
}wifi_switch. c
负责关闭所有 wifi 以及开启所有 wifi,通过 CM 接口写 mib 表:
HI_OS_MEMSET_S(&confSsidInfo, sizeof(confSsidInfo), 0, sizeof(confSsidInfo));
confSsidInfo.ulSSIDIndex = SSIDIndex;
confSsidInfo.ucSSIDEnable = WIFI_DISABLE;
confSsidInfo.ulBitmap = WLAN_SSID_CFG_ATTR_MASK_SSIDENABLE;
ret = igdCmConfSet(IGD_WLAN_SSID_CFG_ATTR_TAB, (void *)&confSsidInfo, sizeof(IgdWLANSsidAttrCfgTab));if (0 == strcmp(argv[1], "on"))
{
ap_enable = WIFI_ENABLE;
}
else if (0 == strcmp(argv[1], "off"))
{
ap_enable = WIFI_DISABLE;
}
...
switch (ap_enable)
{
case WIFI_DISABLE:
disable_wifi();
break;
case WIFI_ENABLE:
enable_wifi();
break;
default:
printf("The Control command error! ap_enable = %u\n", ap_enable);
break;
}wifi_timer_schedule. c
// 获取定时任务表
if (0 != get_entry_num(&tasknum))
{
return -1;
}
IgdwwScheduleTab *TimerTask = malloc(sizeof(IgdwwScheduleTab) * tasknum);
if (NULL == TimerTask)
{
DEBUG_PRINT("TimerTask malloc fail\n");
return -1;
}
if (get_all_timer_entry(TimerTask, tasknum))
{
free(TimerTask);
return -1;
}
// 取当天的时间戳
get_current_time(¤t);
// 执行定时任务
setup_wlan_schedule(TimerTask, tasknum, current);gateway/rootfs/etc/rc.d/rc.ww
...
#等待cm接口启动
CONFIGD_RUNFILE=/var/run/configd.pid
while [ ! -e "$CONFIGD_RUNFILE" ]
do
sleep 1
done
/etc/online_upgrade.sh
/etc/free_monitor.sh &
#上电1分钟内,若收到长度为113的ping包,则打开telnet(用于工厂生产)
/usr/bin/ping_hook &
/etc/rc.d/prepare_plugin.sh
/usr/bin/startup &
/usr/bin/wifi_timer_schedule &