oFono是一个开源免费的电话协议栈软件,它遵循3GPP27.007等通信标准,通过AT命令与modem进行交互,以实现各种电话功能(Voicecall, GPRS, SMS, Stk等等)。由于在oFono之中没有完全实现TS 27.007的AT指令,所以在开发之中我们会遇到将一些功能加入到oFono系统之中。

一、在oFono中增加消息监听(以SRVCC为例说明):

关于oFono中的消息传递可分为两类:一类是AP侧将请求传递给modem,即主动请求;另一类是modem有事件变化后将消息主动上报到AP侧,即主动上报。我们先来看一下后者。

这里以Srvcc为例说明如何在oFono中添加modem主动上报的逻辑。所谓Srvcc指的是当Volte通话由于网络变化等原因从PS域回落到CS域了,这种情况下modem会上报通知到应用层。关于Srvcc的AT Command是CIREPH,关于该指令的具体描述请参考TS 27.007的8.64 IMS network reporting +CIREP章节。

+CIREPH: <srvcch> Provides PS to CS SRVCC, PS to CS vSRVCC and CS to PS SRVCC handover information.

在增加Code之前,我们先看一下oFono系统的层次结构,主要包含接口层与驱动层(PS:应用层与Modem属于oFono的外围模块,oFono起到沟通上下层的作用),具体参考下图:

  • Application Layer(应用层) : 负责与oFono通过DBus进行通信,是用户层代码。oFono源码中ofono/test目录下使用Python语言编写了很多Demo程序,这一部分就属于应用层。
  • Interface Layer(接口层) : 负责与应用层沟通,接收应用层下发的请求与上报消息到应用层。该层主要是为了屏蔽驱动层的差异,为应用层提供统一的接口(DBus)。
  • Driver Layer(驱动层) : 在驱动层中包含了很多类型的modem目录,比如有atmodem、rilmodem、huaweimodem和ztemodem等,目的是区分不同厂商底层的差异。这里需要提到的是不同类型的modem是可以配置的。
  • Modem : 对于Driver层配置不同的modem类型,这一层的属性是不同的,比如如果Driver配置成atmodem,则这一层为CP侧,oFono通过串口发送at指令与CP直接通信;如果Dirver层配置成为rilmodem,则这一层是与rild直接通信的,使用的是Ril的相关事件进行通信的(下面的文章都会以rilmodem来进行讲解)。该层的代码不在oFono之中。

基于以上的信息,我们可以发现如果在oFono中增加无论主动上报还是主动下发,需要扩展接口和驱动层的代码,当然用户层的代码也需要增加,并且Modem需要支持该主动上报和主动下发的相关事件。

下面我们从接口层和驱动层分别叙述如何扩展Modem主动上报的消息通知应用层:

1. 修改驱动层的Code

首先,在Android的telephony/ril.h之中,定义了关于SRVCC的相关事件ID。

/*** RIL_UNSOL_SRVCC_STATE_NOTIFY** Called when Single Radio Voice Call Continuity(SRVCC)* progress state has changed** "data" is int ** ((int *)data)[0] is of type const RIL_SrvccState**/#define RIL_UNSOL_SRVCC_STATE_NOTIFY 1039

oFono为了接收RIL的该时间,我们需要在gril/ril_constants.h中定义与RIL相同的消息,且Message ID( 1039 )必须与RIL保持一致。其次,因为Srvcc属于通话模块的范畴,所以需要将监听放到voicecall模块之中,在voicecall之中,监听底层的消息都在ril_delayed_register之中发起注册的。

static gboolean ril_delayed_register(gpointer user_data) {/* Unsol when call state changes */g_ril_register(vd->ril, RIL_UNSOL_RESPONSE_CALL_STATE_CHANGED,ril_call_state_notify, vc);
... .../* Unsol when srvcc changed */g_ril_register(vd->ril, RIL_UNSOL_SRVCC_STATE_NOTIFY,ril_srvcc_state_notify, vc);
... ...
}

我们可以仿照监听call状态变化的代码来编写Srvcc的变化。使用g_ril_register接口函数进行监听,其中第二个参数需要传入RIL的Message ID(RIL_UNSOL_SRVCC_STATE_NOTIFY),第三个参数需要传入func函数指针,func是需要自己实现的,当oFono收到RIL的消息后,就会执行到func所指向的具体函数中,这里我们将Srvcc的callback定义为ril_srvcc_state_notifiy

guint g_ril_register(GRil *ril, const int req,GRilNotifyFunc func, gpointer user_data);

对于自定义的func函数,我们主要处理两件事情,首先将来自RIL的有效信息通过读取Parcel解析出来,其次将消息上报到接口层,这里的消息上报是通过调用接口层相对应的函数(ofono_voicecall_srvcc_state_notifiy)来通知的。

void ril_srvcc_state_notify(struct ril_msg *message, gpointer user_data)
{struct ofono_voicecall *vc = user_data;struct ril_voicecall_data *vd = ofono_voicecall_get_data(vc);struct parcel p;int srvcc_state;g_ril_init_parcel(message, &p);if (parcel_r_int32(&p) > 0) {//从Parcel中解析出state信息srvcc_state = parcel_r_int32(&p);//将消息通知到接口层ofono_voicecall_srvcc_state_notify(vc, srvcc_state);}
}

至此,Driver层的代码就已经修改好了。

2. 修改接口层的Code

关于接口层的我们需要继续将由底层传递过来的消息,传递给应用层。接口层与应用层是通过DBus的方式来进行通信的,所以接口层需要完成两步修改:

第一步:增加Srvcc事件,为上层提供相应的监听事件。

上层如果想监听oFono的某一个事件(Signal),必须将相应的事件定义在GDBusSignalTable中,在接口层中每一个模块都有对应的GDBusSignalTable,因为Srvcc属于通话模块,我们将其定义在src/voicecall.c之中。

static const GDBusSignalTable manager_signals[] = {{ GDBUS_SIGNAL("Forwarded", GDBUS_ARGS({ "type", "s" })) },
... ...//增加Srvcc事件的定义{ GDBUS_SIGNAL("Srvcc", GDBUS_ARGS({ "state", "i" })) },
};

第二步:将Dirver层传递的消息继续上报到应用层。

我们上一节,提到的func函数指针指向的函数(ril_srvcc_state_notify)的具体实现中,最终会调用到接口层的如下方法(ofono_voicecall_srvcc_state_notify)中,在该方法中我们会将状态继续以DBus的形式使用g_dbus_emit_signal接口将消息发送到应用层。

void ofono_voicecall_srvcc_state_notify(struct ofono_voicecall *vc, int state) {DBusConnection *conn = ofono_dbus_get_connection();const char *path = __ofono_atom_get_path(vc->atom);g_dbus_emit_signal(conn, path, OFONO_VOICECALL_MANAGER_INTERFACE,"Srvcc",DBUS_TYPE_INT32, &state,DBUS_TYPE_INVALID);
}

对于g_dbus_emit_signal函数有两点说明,首先是interface和name接口,对于应用层如果想监听oFono的事件,应用层需要将interface和name和接口层的GDBusSignalTable的name和发送消息(g_dbus_emit_signal)中的interface对应上,对于oFono中的全部interface都定义在include/ofono/dbus.h之中了。

gboolean g_dbus_emit_signal(DBusConnection *connection,const char *path, const char *interface,const char *name, int type, ...);

其次是传递给上层的type信息,这个数据需要与RIL的类型保持一致。

int radio::srvccStateNotifyInd(int slotId,int indicationType, int token, RIL_Errno e, void *response,size_t responseLen) {
... ...int32_t state = ((int32_t *) response)[0];
... ...Return<void> retStatus = radioService[slotId]->mRadioIndication->srvccStateNotify(convertIntToRadioIndicationType(indicationType), (SrvccState) state);radioService[slotId]->checkReturnStatus(retStatus);
... ...
}

最终,我们就可以在应用层连接到oFono的相应事件,来实现监听操作了。

srvcc = dbus.Interface(bus.get_object('org.ofono', path),'org.ofono.VoiceCallManager')//srvcc_changed为应用层的处理函数
srvcc.connect_to_signal("Srvcc", srvcc_changed)

二、在oFono中增加接口调用(以获取Ims注册状态为例说明):

在oFono中增加接口调用与增加消息监听的流程相同,我们也需要修改接口层和驱动层的代码,并且前提需要找到Modem是否有想对应的AT指令或RIL Message。

对于查询Ims注册状态的AT指令为CIREG,详细的介绍请参考8.71 IMS registration information +CIREG。对于telephony/ril.h中有如下事件与之相对应。所以我们可以在oFono中连接该消息,达到查询Ims注册状态的目的。PS:虽然oFono对于主动查询已经有了相关的接口(GetProperties),这里为了说明接口调用,所以我在ims中又增加了一个类似的方法。下面将分别从应用层、接口层和驱动层三个部分说明。

/*** RIL_REQUEST_IMS_REGISTRATION_STATE** This message is DEPRECATED and shall be removed in a future release (target: );* instead, provide IMS registration status via an IMS Service.** Request current IMS registration state** "data" is NULL** "response" is int ** ((int *)response)[0] is registration state:*              0 - Not registered*              1 - Registered** If ((int*)response)[0] is = 1, then ((int *) response)[1]* must follow with IMS SMS format:** ((int *) response)[1] is of type RIL_RadioTechnologyFamily** Valid errors:*  SUCCESS*  RADIO_NOT_AVAILABLE*  INTERNAL_ERR*  NO_MEMORY*  NO_RESOURCES*  CANCELLED*  INVALID_MODEM_STATE*  REQUEST_NOT_SUPPORTED*/
#define RIL_REQUEST_IMS_REGISTRATION_STATE 112

1. 应用层:获取Ims相应的Interface,调用接口层对应的函数。

对于应用层,我们先获取到Ims对应的Interface,同样在include/ofono/dbus.h中定义的,所以应用层通过'org.ofono.Ims'获取到相应的Interface。另外,获得了Interface之后,我们需要调用到接口层的相关函数,这里我将接口定义为getImsState。

#define OFONO_SERVICE    "org.ofono"#define OFONO_IMS_INTERFACE OFONO_SERVICE ".Ims"

应用层相关代码:

manager = dbus.Interface(bus.get_object('org.ofono', path),'org.ofono.Ims')manager.getImsState()

2. 接口层:为应用层提供函数接口,为驱动层提供统一的方法并将消息继续下发到驱动层。

第一步:为应用层提供函数接口。我们在应用层中调用了getImsState,所以需要首先在接口层的GDBusMethodTable中增加相应的成员定义。

static const GDBusMethodTable ims_methods[] = {{ GDBUS_METHOD("GetProperties",NULL, GDBUS_ARGS({ "properties", "a{sv}" }),ims_get_properties) },{ GDBUS_METHOD("getImsState",NULL, GDBUS_ARGS({ "state", "ai" }),ims_get_state) },{ }
};

关于GDBUS_METHOD的定义,包含四个成员,分别为:

  • name:为应用层提供的函数名称。
  • in_args:传递到驱动层的参数,由于获取Ims注册状态不需要输入参数,所以我们这里传入的信息为NULL。
  • out_args:驱动层返回的信息的名称和数据类型。我们这里使用的是整形数组类型,关于数据类型的具体定义,可以参考如下文章:Dbus-glib使用心得
  • function:具体执行的函数,当应用层执行到getImsState后,我们在接口层首先会调用到ims_get_state方法中。
#define GDBUS_METHOD(_name, _in_args, _out_args, _function) \.name = _name, \.in_args = _in_args, \.out_args = _out_args, \.function = _function

对于ims_get_state方法的实现,我们需要通过dirver结构体指针调用到驱动层相应的方法(ims_state)中,目的是为了屏蔽底层不同的modem的差异,为接口层提供统一的方法。另外,还需要将callback函数指针传递到驱动层,目的是当底层反馈消息后,可以使用该函数指针通知接口层。

static DBusMessage *ims_get_state(DBusConnection *conn,DBusMessage *msg, void *data)
{struct ofono_ims *ims = data;if(ims->driver != NULL) {ims->driver->ims_state(ims, get_ims_state_callback, ims);}return NULL;
}

对于ims_state中传入的callback,我们需要在实现函数中获取到底层的Ims注册信息(status),并将该信息反馈给应用层。至此,接口层的处理就已经修改完毕。

static void get_ims_state_callback(const struct ofono_error *error,int* status, void *data)
{struct ofono_ims *ims = data;DBusMessage *reply;if (error->type != OFONO_ERROR_TYPE_NO_ERROR) {reply = __ofono_error_failed(ims->pending);__ofono_dbus_pending_reply(&ims->pending, reply);return;}reply = dbus_message_new_method_return(ims->pending);dbus_message_append_args(reply, DBUS_TYPE_ARRAY, &status,DBUS_TYPE_INVALID);__ofono_dbus_pending_reply(&ims->pending, reply);
}

3.驱动层:接收来自接口层的请求并传递给Modem,并将Modem反馈的信息传递给接口层。(以rilmodem为例)

首先,在driver/rilmodem/ims.c中的driver结构体中,需要增加相应的成员(ims_state),目的是为接口层提供统一的方法调用。另一方面,将ims_state函数指针,指向具体的实现方法(ril_ims_state)中。

static struct ofono_ims_driver driver = {.name              = RILMODEM,.probe              = ril_ims_probe,.remove                = ril_ims_remove,.registration_status      = ril_ims_registration_status,.ims_state                      = ril_ims_state,
};

当接口层的方法调用到驱动层后,首先会执行到ril_ims_state方法之中。该方法的作用主要有两个,一方面是将请求发送给RILD,另一方面是当RILD反馈消息后,会调用到驱动层的ril_ims_state_call这个callback之中。

static void ril_ims_state(struct ofono_ims *ims,ofono_ims_state_cb_t cb,void *data)
{struct ims_data *id = ofono_ims_get_data(ims);struct cb_data *cbd;if (cb == NULL)goto error;if (g_ril_send(id->ril, RIL_REQUEST_IMS_REGISTRATION_STATE, NULL,ril_ims_state_cb, cbd, g_free) > 0) {return;}
error:g_free(cbd);CALLBACK_WITH_FAILURE(cb, -1, -1, data);
}

最终,我们从RILD获取到信息后,会调用到callback函数之中,在这里面我们会解析Parcel,并将解析出来的信息传递到接口层之中。

static void ril_ims_state_cb(struct ril_msg *message, gpointer user_data)
{struct cb_data *cbd = user_data;ofono_ims_status_cb_t cb = cbd->cb;struct ims_data *id = cbd->user;struct parcel rilp;int len;int * state = (int *)malloc(sizeof(int) * 2);DBG("");if (message->error != RIL_E_SUCCESS) {ofono_error("%s: failed to pull ims registration state",__func__);goto error;}g_ril_init_parcel(message, &rilp);len = parcel_r_int32(&rilp);if (len != 2) {ofono_error("%s invalid response %d", __func__, len);goto error;}*state = parcel_r_int32(&rilp);*(state + 1) = parcel_r_int32(&rilp);CALLBACK_WITH_SUCCESS(cb, state, cbd->data);return;error:CALLBACK_WITH_FAILURE(cb, NULL, cbd->data);
}

oFono学习笔记(一):oFono中增加消息与接口相关推荐

  1. linux添加自己的库,Linux学习笔记——例叙makefile 增加自定义共享库

    Linux学习笔记--例说makefile 增加自定义共享库 0.前言 从学习C语言开始就慢慢开始接触makefile,查阅了很多的makefile的资料但总感觉没有真正掌握makefile,如果自己 ...

  2. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...

  3. vue文件里在style的样式需要什么loader_Vue学习笔记之Webpack中css、less、图片等文件处理...

    一.webpack中使用css文件: loader是webpack中一个非常核心的概念,去转化webpack不能转化或打包的文件. 安装loader: 官网介绍: 安装: cnpm install - ...

  4. Linux中常用的文件目录,Linux学习笔记2——Linux中常用文件目录操作命令

    ls 显示文件和目录列表 -l 列出文件的详细信息 -a 列出当前目录所有文件,包含隐藏文件 mkdir 创建目录 -p 父目录不存在情况下先生成父目录 cd 切换目录 touch 生成一个空文件 e ...

  5. python的messagebox的用法_Python GUI编程学习笔记之tkinter中messagebox、filedialog控件用法详解...

    本文实例讲述了Python GUI编程学习笔记之tkinter中messagebox.filedialog控件用法.分享给大家供大家参考,具体如下: 相关内容: messagebox 介绍 使用 fi ...

  6. 中移物联网onenet入门学习笔记2:中移物联的通信格式

    中移物联网onenet入门学习笔记2:中移物联的通信格式 中移物联网硬件接入协议:LWM2M协议,EDP协议,MQTT协议,HTTP协议,TCP透传,MODBUS协议,JT/T808协议,RCMP协议 ...

  7. SpringBoot学习笔记(4)----SpringBoot中freemarker、thymeleaf的使用

    1. freemarker引擎的使用 如果你使用的是idea或者eclipse中安装了sts插件,那么在新建项目时就可以直接指定试图模板 如图: 勾选freeMarker,此时springboot项目 ...

  8. Hadoop学习笔记—11.MapReduce中的排序和分组

    Hadoop学习笔记-11.MapReduce中的排序和分组 一.写在之前的 1.1 回顾Map阶段四大步骤 首先,我们回顾一下在MapReduce中,排序和分组在哪里被执行: 从上图中可以清楚地看出 ...

  9. 【theano-windows】学习笔记十七——梯度中的consider_constant

    前言 主要是在写玻尔兹曼机相关的theano时, 在计算梯度grad的时候发现一个参数名字叫做consider_constant,来看看这个到底做了什么事情 参考博客: using consider_ ...

  10. 【theano-windows】学习笔记十一——theano中与神经网络相关函数

    前言 经过softmax和MLP的学习, 我们发现thenao.tensor中除了之前的博客[theano-windows]学习笔记五--theano中张量部分函数提到的张量的定义和基本运算外, 还有 ...

最新文章

  1. 毕业后的第二个月的一点思绪
  2. 参数数组长度_JS数组操作方法总结(二)——pop、shift、push、unshift
  3. 【转载】tkinter多线程防假死
  4. 《阿里巴巴 Java 开发手册》读书笔记
  5. 学习 Android O HIDL
  6. Velocity浅析及与Jsp、Freemarker对比
  7. Git协助方式:Fork项目开发新功能并使用Pull-Request把新特性推送给原项目
  8. 【解决】U盘装系统(Win7/Win8) 装双系统
  9. Latex插入项目符号和编号{itemize}和{enumerate}
  10. python matplotlib画图实例
  11. 对期货大赛获奖者杨宏斌、陈伟的采访
  12. ad转3d视图快捷键_AD详细快捷键按键
  13. 【SAP】在制品报表 查询及结算余额查询
  14. 我是如何成为一名少儿编程竞赛老师的
  15. Cloud 80% 客制化键盘分享,模块拼色设计
  16. Java选择题(十)
  17. 聚点 内部 内点 导集
  18. 数学建模学习笔记-概况
  19. ThinkPHP5之图片下载
  20. Spring:项目国际化

热门文章

  1. 是否有无穷多组基本勾股数
  2. 图像灰度共生矩阵cooc_feature_image.hdev
  3. 设置电脑 保护视力 还有桌面默认颜色
  4. 什么是 docker?docker和虚拟机有什么差别和不同?
  5. vlan是什么?如何划分vlan?如何实现vlan?使用vlan的优点!
  6. HDC1080介绍与使用
  7. 一只喵的西行记-12 灯火阑珊处那娃在哭
  8. LeetCode.1033-移动石头直到连续(Moving Stones Until Consecutive)
  9. 奶爸日记21 - 探险乐园
  10. Android 集成友盟统计