VIA Selle SMS 系列之—存储篇

在短信菜单的设置选项中,有一项是 Save Sent message,如果选为开启,则会保存发送短信,否则则不会保存所发送短信。

在做Sms Service的时候,要求不保存所发送短信,但其它正常的短信仍得能存储。
所以要实现针部部分短信不存储的设置。

在发送短信前,会执行一个函数:
bool SmsControllerC::CheckStorageBeforeSendMsg(bool &HasOpenDialog,bool IsFromEditMsg,bool IsForEditAddress)
{
……
if (mModelP->GetSettings()->mAutoSave) //mAutoSave为设置是否保存的参数
{
//Check if Uim valid when sms storage is uim only.

uint32 Result = mModelP->CheckStorageAndDoAutoDelete(RecordNum);
……
}
……
}
此时如果把这里的IF条件置为FASLE,则就不会存储。针对特殊短信,加一条件判断即可,如:
if ((mModelP->GetSettings()->mAutoSave)&&(mModelP->notSmsServiceNum))

但现在的问题是为什么在这里置为False则不会存储,这里并没有存储的动作啊!并且,此时短信还没有发送,也不可能在这时候就存储下来。

这里折腾了很久,用模拟器跟发现mAutoSave在发送过程中影响三个地方,第一处如上,第二处是发送短信的时候: AslSmsResultT Result = AslSmsSendMessage(*SmsRecordP, &RecordId, &MessageId, GetSettings()->mAutoSave,IsLMS);
第三处是,短信发送成功反馈的时候:
bool SmsModelC::MessageSent(AslSmsSendStatusT *StatusP)
发送短信的时候没有明显进行保存短信的动作。
于是问题定位在第三个函数(先前一直没有注意到它,调试许久:!)

bool SmsModelC::MessageSent(AslSmsSendStatusT *StatusP)
{
……
AslSmsReplaceAddMessage(*SmsRecordP);//这里进行了保存动作,但没那么简单
……
}

AslSmsResultT AslSmsReplaceAddMessage(SmsRecordC &SmsRecord)
{
#ifdef FEATURE_LSM
VectorC SmsRecList;

SmsRecord.Split(&SmsRecList);

for(int i=0; i<SmsRecList.GetSize();i++)
{
SmsRecordC *TempRec = (SmsRecordC *)SmsRecList.GetElement(i);
if (AslGetIdFromReplaceIdArray(&recid)) // 问题的最终原因在此!!!
{
result = (AslSmsResultT)ValSmsReplaceTxtMessage(*TempRec,recid);
}
delete TempRec;
}
#endif
return result;
}

bool AslGetIdFromReplaceIdArray(uint16* RecId)
{

if( IsEqual(0, mReplaceIdArray.GetSize()) )
{
return FALSE; //若在CheckStorageBeforeSendMsg时,把那条IF置为False,此在这里返回
}
……
return TRUE;
}

现在大致的流程清晰了许多,首先在发送前在CheckStorageBeforeSendMsg会判断mAutoSave的状态,若为TRUE,则预留一个空间用来保存此短信,若为FALSE,则不预留。在短信成功发送后,进行保存的时候,MessageSent中的AslSmsReplaceAddMessage会判断是否有预留空间,没有的话,无论mAutoSave怎么设置,则都不会保存。

针对Sms service的疑虑解决了,但预留的方式没有进一步的深入,如何申请预留空间的,如何添加删除等。留给读者自己深入分析吧:)

VIA Selle Sms系列的文章,到此告一段落,回顾这段时间在整SMS相关,一言以蔽之短信模块博大精深,这个系列的文章也只是水面之上冰山一角。但应该是个引子,顺着这些大的线索深入下去,终有一日能深入到冰山之底的
 

VIA Selle SMS 系列之—设置篇

Sms的相关设置问题曾经有过介绍,比较简略,这次以在编写短信过程中设置发送属性的过程,再来讨论下SMS中复杂消息的处理过程,示例的操作步骤如下:
Message—>Write
Message—>Options—>Sending options[A]—>(Mark
One)[B]—>Done[C]
这里主要说的是从A到C的过程。
A.在Options菜单中选择Sending
Options的选项:
触发消息:
MessageID APPMSG_SMS_NEWMSG
      Parameters:APPMSG_SMS_PARAM_NEWMSG_OPTION_SENDING_OPTION
 响应消息:
    case APPMSG_SMS_NEWMSG:
      return
HandleMessage(
SmsMessageHandlerNewmsgTable, MsgId, Param1,
               APPMSG_SMS_PARAM_NEWMSG_MAX);
由“APPMSG_SMS_NEWMSG”这条消息,会继续查找一个表,这个表中有相对于触发消息所带参数的具体动作。
const SmsMessageHandlerT SmsMessageHandlerNewmsgTable[] =
{
 { //APPMSG_SMS_PARAM_NEWMSG_OPTION_SENDING_OPTION
   
APPSMS_TABLE_ITEM(
APPMSG_SMS_PARAM_NEWMSG_OPTION_SENDING_OPTION)// 这条消息告诉我们要
   
SMS_CONTROLLER_COMMAND_OPEN_DIALOG,
//  
打开一个Dialog
    APPSMS_DIALOGID_NEWMSG_OPTIONS_SENDING_OPTIONS, //   所打开的DialogID
   
SMS_CONTROLLER_COMMAND_CLOSE_DIALOG,
//  
关闭一个Dialog
   
APPSMS_DIALOGID_NEWMSG_OPTIONS_MENU,
// 所关闭DialogID
   
APPSMS_DIALOGID_NULL,
// 要运行Dialog定义里的SetDataDialogID ,此时为空
   
&SmsControllerC::HandleNewMsgOptionSendingOption
// 执行触发消息的额外函数
 },
}
总的来说,在Options菜单中选择Sending
Options选项,会打开一个Dialog,关闭此Options的Dialog,并执行一个函数,设置此编缉短信的Sending
Options的始初化状态,即从SmsEditMsgManagerC中具体的短信对象的状态来设置SmsEditMsgManagerC当中的几个表示状态的临时变量。
B.在打开Options的Dialog时所执行的动作:
//dialog information table
const SmsDialogIdInfoT
SmsDialogIdInfoTable[] =
{
 {
   APPSMS_TABLE_ITEM(APPSMS_DIALOGID_NEWMSG_OPTIONS_SENDING_OPTIONS)
  
IDD_DLG_SMS_NEWMSG_OPTIONS_SENDING_OPTIONS,
   FALSE,
  
&SmsViewC::UpdateViewNewMsgOptionSendingOption,
 // 打开此Dialog后,更新此Dialog视图的函数,立即执行
  
&SmsViewC::SetDataNewMsgOptionSendingOption,
// 针对此Dialog的设置数据的函数,非立即执行,由其它函数调用
  
&SmsViewC::HandleWinNotifyCheckBoxMenuSettingDlg
// 处理Dialog中控件发生变化时的响应函数,当控件状态变化时调用此函数
 },
}
在更新视图的函数中,会根据之前的设置项(SmsEditMsgManagerC中临时的成员变量)(在上面的执行触发消息的额外函数中初始化的状态),来改变Dialog的显示内容。
当前Sending
Options里的Dialog中有两项,改变是否选中的状态,就会调用“HandleWinNotifyCheckBoxMenuSettingDlg”并在UI上显示变化,即选中,没选中,SoftKeyBar显示发生变化,即标记,未标记
C.改变完状态后,按“Done”所执行的动作:
触发消息:
          MessageID
:APPMSG_SMS_NEWMSG
         
Parameters
APPMSG_SMS_PARAM_NEWMSG_OPTION_SENDING_OPTION_DONE
 响应消息:
case APPMSG_SMS_NEWMSG:
      return
HandleMessage(
SmsMessageHandlerNewmsgTable, MsgId, Param1,
               APPMSG_SMS_PARAM_NEWMSG_MAX);
同第一步的过程,但这里的参数不一样,对应的MessageHandlerTable有变化:
 { //APPMSG_SMS_PARAM_NEWMSG_OPTION_SENDING_OPTION_DONE
   
APPSMS_TABLE_ITEM(
APPMSG_SMS_PARAM_NEWMSG_OPTION_SENDING_OPTION_DONE)
// 这条消息告诉我们要
   
SMS_CONTROLLER_COMMAND_OPEN_POPUP,
//  
打开一个Pop
up
的窗口
   
APPSMS_DIALOGID_POPUP_SETTINGS_SAVED,
//  
所打开的Pop
up
窗口的ID
   
SMS_CONTROLLER_COMMAND_CLOSE_DIALOG,
//  
关闭一个Dialog
   
APPSMS_DIALOGID_NEWMSG_OPTIONS_SENDING_OPTIONS,
// 所关闭DialogID
   
APPSMS_DIALOGID_NEWMSG_OPTIONS_SENDING_OPTIONS,
// 运行此Dialog定义里的SetData函数    &SmsControllerC::HandleNewMsgOptionSendingOptionDone// 执行触发消息的额外函数
 }
即按“Done”后,关闭此Sending
Options的窗口,打开一个Pop UP的窗口,提示改变成功,然后调用Sending Options的窗口的SetData函数,最后再执行触发消息的额外函数。
 1.
SetData函数:把用户对选项的选择内容保存临时的变量当中
 2. 额外函数:执行具体的保存动作,即把临时变量当中的内容保存到当前所编辑短信的具体对象当中
最后返回短信内容的编辑界面,完成设置动作。
补充一点:
SmsEditMsgManagerC中的短信对象即mEditMsg,并不是在其构造函数中就初始化的,而是在SetEditMsgText时进行初始化,并且读取当前对短信选项的总体设置来初始此单条短信。
bool
SmsEditMsgManagerC::SetEditMsgText(const StringC &StrText)
{
 EnableEditMsg();// 从短信的总体选项设置来初始化此条短信
 mEditMsg.SetUserData(StrText);
 return TRUE;
}
编写新短信时,这个动作是在SmsViewC::SetDataNewMsg(DialogC *DlgP, uint32 MsgId, uint32
Param)里执行的,在Edit Message新建短信窗口按Send键,或Options键都可触发此消息.
 

再补充一点:
       针对类型为
MENU_LIST_CHECKBOX 的MENU风格,当按下时会改变选中状态,此时会立即收到HandleWinNotify消息,SoftKeyBar中间键的自定义消息将不起作用(当系统判断为Checkbox时SoftKeyBar的中间键消息被拦截,默认为选择CheckBox的消息)。
        针对类型为
MENU_LIST_RADIOBOX 的MENU风格,当按下时不改变选中状态,此时不会收到HandleWinNotify消息,SoftKeyBar中定义的消息将会起作用。
可在以UPdateDialogView时,设置一个属性使在MenuItem项中滚动时,可以触发HandleWindNotify消息。
     MenuP->SetNotifyScrollEvent(TRUE);
一个Dialog中,若即有MenuItem项对应的消息,又有此Dialog的SoftKeyBar对应的消息。
若SoftKeyBar的消息不为:WINMSG_SOFT_SELECT_KEY则,以SoftKeyBar中的消息为准执行,而不会执行MenuItem对应的消息。
若SoftKeyBar的消息为:WINMSG_SOFT_SELECT_KEY则实际会执行MenuItem对应的消息。
若SoftKeyBar的消息为空:则什么都不会执行。
有CheckBox的时候按键消息的传递过程:
按下Dialog中包含CheckBox的SoftKeyBar中的按键后的处理过程:
                
1.
当前APP的HandleKey //如果直接返回则按键消息将不会被执行
                 2.
ApplicationC::HandleKey //所有APP的基类按键消息
                 3.
接着消息传递如下(略)
MailClientC::SendMail
MailServerC::SendMail
MailServerC::DispatchMail
WinManagerC::ProcessMail
WindowC::ProcessMail
MailClientC::ProcessMail
MailClientC::MailMapProc
                 4.
DialogC::OnKeyPress //will let dialog’s child control handle key press
first
                 5.
DialogC::PreOnKey //will get the right child to handle the key
message
6.接着消息传递如下(略)
WindowC::ProcessMail
MailClientC::ProcessMail
MailClientC::MailMapProc
    
7.
MenuCtrlC::OnKeyPress // 如果的这里返回则按键消息也不会被执行,CheckBox则在这里Return True;不继续往下处理消息,所以自定义的SoftKeyBar消息将不会被执行,在这里返回
    
8.
MenuCtrlC::CheckItem   //change the check box
status
9.MenuCtrlC::SendNotify //触发CheckBoxHandlWinNotify消息
                   
……
再之后没有继续跟下去,应该是把定义的按键消息发送到Task进行分发处理的操作。
总之,短信方面的处理比较复杂,也是出问题比较集中的地方。

 

VIA Selle SMS 系列之—发送篇

上一篇文章由Duplic
Message引出了SMS的接受过程,这篇文章则由Page Message引出SMS的发送过程,同样是基于Selle1.0的。
二,短信的发送过程
这里只讲到从新建短信,添加联系人,然后发送的典型过程,不包括调用接口直接发送短信的情况,稍有不同。
1.UI层新建短信,编辑内容,选择发送:
编辑内容不为空时,此时按Send键,会调用:(或者在Edit Message的窗口中选择Options选项,也会调用:)
     SmsViewC::SetDataNewMsg(DialogC *DlgP, uint32 MsgId, uint32
Param)
      {
  EditCtrlC *EditCtrlP =
(EditCtrlC*)DlgP->GetControlByType(CTRL_TYPE_EDIT);
         StringC
StrText;
  EditCtrlP->GetText(StrText);
SmsEditMsgManagerC *EditMsgManagerP =
((SmsModelC*)mModelP)->GetEditMsgManager();
           //set StrText
to Model
          if(MsgId ==
APPMSG_SMS_NEWMSG)
         {
           EditMsgManagerP->SetEditMsgText(StrText); 

//激活 SmsEditMsgManagerC中具体的短信对象即mEditMsg,并调用默认的短信设置选项来进行初始化设置!

          }
      }
这里EditMsgManagerP是一个临时的对象,包括你在新建短信时输入的内容,与之后添加的收件人,及其相关设置。
编辑内容为空时,此时Send键不起做用,Selle默认好像是不支持空短信的,在smscust中配置设置。
const SmsCustSettingsT
gSmsCustSettings =
{
 TRUE,//FALSE didn’t allow to send blank sms
 TRUE,
  "10010"
};
判断的方法如下:   
//if can not send empty message, then
delete Send and group menu item
if(
!SmsGetCustSettings()->mSendEmptyMsg )
{
    //为空时不删除Seng等按键
}
2.UI层编辑内容后,添加联系人,选择发送:
这里以直接输入联系人电话号码为例,输入对方联系人手机号,点击发送后,会调用:
SmsViewC::SetDataNewMsgEditAddress(DialogC
*DlgP, uint32 MsgId, uint32 Param)
{
    SmsEditMsgManagerC
*EditMsgManagerP = ((SmsModelC*)mModelP)->GetEditMsgManager();
    StringC
StrAddress;
AddressEditCtrlP->GetText(StrAddress);
   
//……
省略了对多联系人及联系人列表中出现";"的处理
EditMsgManagerP->ClearEditGroupAddress();
 EditMsgManagerP->SetEditGroupAddressNumber(StrAddress);//把输入的联系人放到临时的编写短信的对象中
 EditMsgManagerP->SaveEditGroupAddress();
}
在支持空短信的处理中,我在这后面添加了从临时对象中取出短信内容,并添加类似这样的内容: "Call:123456789"
    SmsRecordC
*SmsRecordP;
  SmsRecordP = new SmsRecordC;
   SmsRecordP->CopyFrom(*(EditMsgManagerP->GetEditMsg())); //从临时对象中实例化一个SmsRecordP的对象
 StringC Content;
 SmsRecordP->GetUserData(Content);
 if(Content.GetLength()<1)
 {
    Content.Append("Call:");
    Content.Append(StrAddress);
   
EditMsgManagerP->SetEditMsgText(Content);
 }
其实这一步可以在第一步输入为空时进行操作,但在这里还要做合并号码的动作,所以就在这个地方一起处理了。
在执行完SetData的动作后,还会同时执行发送动做,因为根据输入的内容不为空,即有号码输入时:
if(Event ==
EDIT_EVENT_DATA_EMPTY) //no data in edit control, set left softkey bar to
‘search’
{
         
SoftkeybarP->SetButtonTitle(1,IDS_SMS_SEND);
         
SoftkeybarP->SetButtonMessageId(1, APPMSG_SMS_NEWMSG);
          if(
NEWMSGEDITADDRESS==EditType )
//为编辑号码状态
          {
           
SoftkeybarP->SetButtonMessageParam(1,APPMSG_SMS_PARAM_NEWMSG_SEND);
          }
}
所以按Send的同时会执行(跟踪此消息可查知,不再详细列出其消息映射关系)
3.APP层处理发送过程:
    首先会处理,APPMSG_SMS_NEWMSGAPPMSG_SMS_PARAM_NEWMSG_SEND的消息:
int
SmsControllerC::HandleNewMsgSend(uint32 MessageId, uint32 Param)
{
 //check if previous message sending
finish
 if
(!mModelP->IsPreviousMessageSendingFinish())
 {
   
GetView()->OpenPopup(APPSMS_DIALOGID_POPUP_SENDING_PREVIOUS_MESSAGE);
    return -1;
 }
 //add message to send
 if(
IsEqual(ASLSMS_RESULT_INVALID_PARAMETER, mModelP->AddEditMsgGroupToPending()) )//A
 {
   
GetView()->OpenPopup(APPSMS_DIALOGID_POPUP_ADDRESS_EMPTY);
    return 0;
 }
 //send message
 SendMessage(HasMemoryFullDialog);//B
}
其上面A处AddEditMsgGroupToPending()的做用是把编写短信时的临时对象EditMsgManagerP中的内容添加到一个代发短信列表后面,即LinkedListC
mPendingMsgList;大致的过程为:
AslSmsResultT
SmsModelC::AddEditMsgGroupToPending()
{
 int i=0;
 SavePendingMsg();//clear outbox message这里我的理解为,把PendingList中的第一条内容存储到Storage当中
    {
    SmsRecordC *SmsRecordP;
    SmsRecordP = new SmsRecordC;
    if( IsNull(SmsRecordP) )
    {
      return
ASLSMS_RESULT_OUT_OF_MEMORY;
    }
   
SmsRecordP->CopyFrom(*mEditMsgManager.GetEditMsg());
//zjj add 20090916 —begin 添加空短信时的特殊处理
#ifdef FEATURE_PAGEMESSAGE   StringC
Content;
 StringC CallContent("Call:");
 SmsRecordP->GetUserData(Content);
 if((Content.SubString(0,5).GetLength()==5)&&(IsEqual(Content.SubString(0,5),
CallContent)))
 {
   
SmsRecordP->SetTeleserviceID(AslSmsGetTeleserviceIdByType(ASLSMS_TELESERVICE_TYPE_PAGE));
//这里判断为空短信时,要把短信的TeleserviceID设置了4097,接收方跟据此标志在HandleMessageIncoming里来做特殊的显示处理
 }
#endif 
//zjj add 20090916 —end
    SmsAddressC *AddressP =
(SmsAddressC*)(*AddressArrayP)[i];
    UIASSERT( !IsNull(AddressP)
);
   
SmsRecordP->SetSmsAddress(*AddressP);
   
AddToPendingMsg(SmsRecordP);
//把当前短信添加到发PendingList的尾部,等待发送。
 }
 return ASLSMS_RESULT_SUCCESS;
}
然后是B处,SendMessage(HasMemoryFullDialog)的处理:
void SmsControllerC::SendMessage(bool
HasMemoryFullDialog,bool IsFromEditMsg,bool IsForEditAddress)
{
 //Check if is in airplane mode.
 #ifdef SYS_OPTION_RUIM
 //check if have uim card or not
 #endif
 //check if has network.
 if (
IsEqual(AslGetServiceInfo()->ServiceStatus, VAL_PSW_NO_SERVICE))
 {
   
OpenPopup(APPSMS_DIALOGID_POPUP_NO_NETWORK);
    //clear pending list
    mModelP->ClearAll();
    return;
 }
 //check storage 强调的一点是这里处理有关是否存储发送短信的相关操作,在下一篇中再介绍
 if(
!CheckStorageBeforeSendMsg(HasMemoryFullDialog,IsFromEditMsg,IsForEditAddress)
)
 {
    //clear pending list
    mModelP->ClearAll();
    return;
 }
 AslSmsResultT Result =
mModelP->SendPendingMsg() ;//具体的发送过程在这里
 APPSMS_PRINTF((char*)"SmsControllerC::SendMessage
Result = %d", Result);
 if( (Result == ASLSMS_RESULT_SUCCESS)
|| (Result == ASLSMS_RESULT_SENT_BUT_SAVE_FAILED) )
}
SendPendingMsg()的大致流程如下,省略了与主题无关的代码:
AslSmsResultT
SmsModelC::SendPendingMsg()
{
   //no message to send, then return
success
 if(mPendingMsgList.GetCount() == 0
)
 {
    return
ASLSMS_RESULT_SUCCESS;
 }
 // 设置发送频道
 if(IsEqual(1, GetSendChannelSidb()))
 {
    
APPSMS_PRINTF((char*)"SmsModelC::SendPendingMsg: set channel to TC");
   
ValSmsSetChannel(VAL_SMS_CHANNEL_TC);
 }
 else
 {
   
APPSMS_PRINTF((char*)"SmsModelC::SendPendingMsg: set channel to Default");
   
ValSmsSetChannel(VAL_SMS_CHANNEL_DEFAULT);
 }
 SmsRecordC *SmsRecordP =
(SmsRecordC*)mPendingMsgList.GetHead();//获取待发送列表中的第一条
 uint16 RecordId=0;
 uint16 MessageId=0;
 AslSmsResultT Result =
AslSmsSendMessage(*SmsRecordP, &RecordId, &MessageId,
GetSettings()->mAutoSave);//调用ASL层的函数继续处理发送过程
 if( (Result == ASLSMS_RESULT_SUCCESS)
|| (Result == ASLSMS_RESULT_SENT_BUT_SAVE_FAILED) )
 return Result;
}
4.ASL层的发送处理过程:
主要处理是根据是否为自注册短信,而选择存储或不存储此发送短信
AslSmsResultT
AslSmsSendMessage(SmsRecordC &SmsRecord, uint16 *RecordIdP, uint16
*MessageIdP, bool ToSave)
{
 AslSmsResultT result ;
#ifdef FEATURE_SMS_AUTO_REGISTER
 if(SmsRecord.GetAutoRegisterFlag())
 {
    //not
need save if is AutoRegister message.
    result = (AslSmsResultT)ValSmsSendTxtMessage(gSmsRegisterId, RecordIdP,
SmsRecord, MessageIdP, FALSE);
 }
 else
#endif
 { 
    result = (AslSmsResultT)ValSmsSendTxtMessage(gSmsRegisterId, RecordIdP,
SmsRecord, MessageIdP, ToSave);
 }
 gSmsUnReadCount = -1;//Unread sms
number should be update
 return result;
}
5.VAL层的发送过程:
ValSmsSendTxtMessage 不再详细介绍,其间是一些发送设置,最重要是继续调用ValSmsSendMessage来发送
ValSmsSendTxtMessage …
{
 if ( 0 != ValSmsSendMessage( SmsTxtMsgP ) ) //SmsTxtMsgP 为发送内容
 {
    ValSmsSendInfo[RegId].Acked =
TRUE; /* send failed, then clear the flag of ack */
    return
VAL_SMS_ERR_MSG_FORMAT;
 }
}
这里只以新建短信的发送过程为例:
int ValSmsSendMessage( ValSmsMessageT *MessageP )
{
 ValSmsTeleMsgT MsgType;
 MsgType =
MessageP->TeleMsgType;
 switch(MsgType)
 {
    case
VAL_SMS_MSG_TYPE_ORIGINATION_SUBMIT:
      i = SendSubmitMsg(MessageP);
      break;
       ……
      }
}
SendSubmitMsg函数中有更为复杂的参数设置,如跟基站交互时发送地址的格式问题等,这里就不一一介绍了(如果你对这里的设置很熟悉,欢迎跟我交流,一起研究下,现在也不是很明白:),但关键有一处,如下:
static int SendSubmitMsg(
ValSmsMessageT* MessageP )
{
 /* send ack data message to PSW
*/
 ExeMsgSend(EXE_PSW_ID,
PSW_MAILBOX_CMD, PSW_SMS_SUBMIT_DATA_MSG,
             (void *)DataMsgP,
sizeof(PswSmsUserDataMsgT));
 ——-or—————————————————————-
 /* send sms
parameter message to PSW
*/
 ExeMsgSend( EXE_PSW_ID,
               PSW_MAILBOX_CMD,
              
PSW_SMS_SUBMIT_PARMS_MSG,
  
(void*)ParmMsgP,
   sizeof(
PswSmsSubmitParmsMsgT ) );
}
把短信发送到协议层,然后就真的发出去了,至于PSW层的发送处理,唉,搞协议层暂还没纳入规划:)
6.总结:
以上实现了以空短信为例,从最上层的UI—>APP—>ASL—>VAL—>PSW层的处理过程,即短信发送的大致过程。
下一篇准备介绍有关短信是否存储的相关设置问题,哈哈,希望比这篇要短很多。

 

VIA Selle SMS 系列之—接收篇

最近一段时间都在整VIA的SMS相关需求,有Page
Message,Duplic Message,Message Service等,积累了一些经验与大家分享,是基于Selle1.0的版本。
(由于与SMS关于代码分布在PS,VAL,UI三层,并且每层都有复杂的处理过程,这里只以某些功能的实现为主线串一下相关过程,不太可能覆盖的很全面,有兴趣的同行们可以补充:)
一,短信的接收过程
短信由基站发送到手机,首先应该是协议层接收到,然后会通知VAL层,再然后到ASL层,最后到APP,UI层。
具体点:
1.协议层会发送给VAL层消息,通知有SMS到来:
The messages received from PSW have
following kinds:
VAL_SMS_DELIVER_IND_PARMS_MSG: //短信的头,包含地址,设置等信息
VAL_SMS_DELIVER_IND_DATA_MSG: 
//短信的内容,如果是空短信,内容为空,不会处理此消息
VAL_SMS_ERROR_MSG:
VAL_SMS_BCAST_IND_PARMS_MSG:
VAL_SMS_BCAST_IND_DATA_MSG:
VAL_SMS_BCAST_ERROR_MSG:
VAL_SMS_CAUSE_CODE_STATUS_MSG
这里以普通短信类型为例,Task会找到对应此消息的处理函数为:
message hangler for messages from PSW
task
bool ValProcessSmsMsg( uint32
MsgId,void* MsgBuf,uint32 MsgLen )
{
         ……
    case VAL_SMS_DELIVER_IND_DATA_MSG:
      ValSmsTstSendSpy( MsgBuf, MsgLen
);
      ValSmsProcessDeliverDataMsg( MsgBuf, MsgLen );
      break;
}
在函数ValSmsProcessDeliverDataMsg(
MsgBuf, MsgLen )中对收到的短信进行复杂的处理,看相应的代码应该可以明白,这里只说几处关键的地方:
int8 ValSmsProcessDeliverDataMsg(
void* MsgBuf, uint16 MsgLen )
{
ValSmsIncomingMsgT SmsIncomingMsg;
 /* initialize */
 RcvMsgP =
(ValSmsUserDataMsgT*)MsgBuf;
 /* fill-in the buffer with the
message received */
    datalen =
RcvMsgP->NumFields;
    for (i = 0; i < datalen;
i++)
    {
     
TxtMessage.UserData[NumUserDataRcv].Data[i] = RcvMsgP->Char[i];
    }
 /* send the message to UI */
 if (
TxtMessage.TeleSrvId==VAL_SMS_TS_ID_CATPT)  
 {
           /* For UTK
message */
 }
 else
 {
        if
(TxtMessage.TeleSrvId==VAL_SMS_TS_ID_VOICE_MAIL)/*VMN, just save into the
previous position */
    {
             /*For Voice Mail*/
     }
         else if
         {
           /* check if is
immediate message */
         }
         else
         {
               /*
other teleservice than VMN */
            if(ValSmsJudgeDupMsg(&TxtMessage,&recid,&ValSmsDupPolicy))//判断SMS重复的条件,可以是内容,地址或是MsgID
            {
             
SmsIncomingMsg.
isDuplicateSms =
TRUE;//此字段是后来加上的,可以把自己添加一些特殊信息,传送到UI
              //result =
ValSmsWriteTxtMessage( &TxtMessage, &recid );
              result =
ValSmsUpdateTxtMessage( &TxtMessage, recid );
             
//SendMsgWriteAck(VAL_SMS_SUCCESS, RcvMsgP->SeqNum);
   //return 0;        
            }       
            else
            {
             
SmsIncomingMsg.isDuplicateSms = FALSE;//zjj add 20091022
              if(!bStoreInUIM)
              {
                result =
((*CheckSaveOneMessageFunc)(&TxtMessage, &recid));            
              }
              else
              {
                result =
ValSmsWriteTxtMessage( &TxtMessage, &recid );
              }            
            }
                  if(result !=
VAL_SMS_SUCCESS)
                  {
              SendMsgWriteAck(result,
RcvMsgP->SeqNum);
             
SendMsgWriteErr(device,result);
              return -2;           
                  }
            TxtMessage.SmsMsgRecId =
recid;
            SmsIncomingMsg.recid =
recid;
         }
      }
   ProcessEvt(VAL_SMS_EVENT_INCOMING_MESSAGE,
TxtMessage.TeleSrvId, (void *)&SmsIncomingMsg);
   }
ProcessEvt会调用注册消息VAL_SMS_EVENT_INCOMING_MESSAGE对应的asl层的回调函数:
注册的地方在:
/*!
\brief initialize SMS Data: register
callback function, register teleservice id
bool AslSmsInitialize (void)
{
 gSmsRegisterId = ValSmsRegister(SMSMessageListener);
}
2.接到收此消息后,轮到ASL层的处理;
/*
 SMSMessageListener: to process event
from VAL
*/
static void SMSMessageListener( RegIdT RegId, ValSmsEventIdT
Event, void *MsgP)
{
    ……
 switch (Event)
 {
 case VAL_SMS_EVENT_INCOMING_MESSAGE:
    SmsListenerIncomingMessage(MsgP);
    break;
 case
VAL_SMS_EVENT_SEND_STATUS_MESSAGE:
    SmsListenerSendStatus(MsgP);
    break;
       ……
 }
}
static void SmsListenerIncomingMessage(void *MsgP)
{
 UiGetMailServer()->PostMail(APPMSG_SMS_MESSAGE_LISTEN_INCOMING,
WIN_HANDLE_NULL, (uint32)MsgP, sizeof(ValSmsIncomingMsgT), BY_ADDRESS);
}
void AslSmsListenerIncomingMessage(void *MsgP)
{
     ……
 ValSmsIncomingMsgT *SmsIncomingMsgP =
(ValSmsIncomingMsgT*)MsgP;//这样就收到了在VAL层自己添加的信息
 VsmsRecordId =
SmsIncomingMsgP->recid;
 bool duplicateSms=SmsIncomingMsgP->isDuplicateSms;
    ……
     
UiGetMailServer()->PostMail(APPMSG_SMS_MESSAGE_INCOMING, WIN_HANDLE_NULL,
(uint32)VsmsRecordId, (uint32)
duplicateSms, BY_VALUE); //这里原来第二个参数为空,但也可以添加想传到UI层的自定义数据。
}
以上为SMS在ASL层的流程,从代码可知其意,过渡到App,UI之上。
3.ASL层发送消息,映射函数到APP层接着处理:
ON_MSG_VOID_WORDLONG(APPMSG_SMS_MESSAGE_INCOMING,SmsAppC::OnMessageIncoming)//这里要注意的是,由于自己添加了一个参数,所以映身类型从ON_MSG_VOID_WORD改为了ON_MSG_VOID_WORDLONG即传递了两个参数。
void SmsAppC::OnMessageIncoming(uint32
Param,uint32 ParamB)
{
 mControllerP->HandleMessageIncoming(Param,ParamB);
}
下面这个函数,负责处理SMS在UI上的显示,如是直显短信就直接显示内容,是普通短信则打开提示窗口,是重复短信则跳出来我加上去的窗口
void
SmsControllerC::HandleMessageIncoming(uint32 Param,uint32 ParamB)
{
 ……
 if(ParamB) //if ParamB is True ,it is
duplicate Sms
   {
      SmsViewMsgManagerC
*ViewMsgManagerP = ((SmsModelC*)mModelP)->GetViewMsgManager();
      UIASSERT(
!IsNull(ViewMsgManagerP) );
     
ViewMsgManagerP->SetViewMsg(NewMsg);
//这里把Controller层的新短信数据,传到UI层可以读取并显示
     
if(!IsTrue(GetView()->IsDialogOpened(APPSMS_DIALOGID_DISPLAY_DUPLICATE_MSG))
)
      {
        
GetView()->OpenDialog(APPSMS_DIALOGID_DISPLAY_DUPLICATE_MSG);
//打开自定义的重复窗口
      }
      else
      {
        
GetView()->UpdateView(APPSMS_DIALOGID_DISPLAY_DUPLICATE_MSG);
      }
      //update menu info
     
GetView()->UpdateSmsListInfoOnMsgModify();
      //start alert
     
GetView()->StartAlert();
     
GetView()->UpdateAlert();
      return;
   }
 ……
}
4.总结:
以上实现了在VAL判断是否是重复短信,把结果放在一参数中,传到UI,跟据此参数来显示不同内容的大致方法,即短信接收的大致流程。
本来想写成一篇,看来这篇简介己经够长了,所以拆分成了一个个之一了
🙂
下次介绍Page Message的实现,亦即短信发送的大致过程。

 

制作软件镜像时的步骤及注意事项

  1. 制作镜像前的准备:
    1.软件的Boot.rom
            
    2.
    软件的Cp.rom    ! 软件要关闭 MonPrintf Log信息!
             3.对应的用户数据文件
    data.bin
             4.对应软件的脚本文件
    product.srp
            
    5.
    软件对应的JT文件
     硬件包括:
             1.至少两片目标板
             2.Trace32
             3.夹具
            
    4.
    转换头(链接Trace32与夹具)
  2. 镜像的制作步骤:
  3. Trace32 Erase 一遍目标板的Flash
          
    1.
    连接好Trace32,放入目标板
           2.启动Trace32。
           3. do reset   
    //
    在命令行输入
           4.do init
           5.do
    __jf
           6.flash.erase all
           7.do init
           8.
    d.l //
    检查是否为全部写为#
  4. Download Boot
           1.do reset
       2.do init
       3.do load-boot boot.rom //注意修改load-boot对应的版本的地址范围
  5. ETS Download CP(注意用ETS的对应版本,如软件是0.19.0的就要用0.19.0ETS
    (最好加电池来Download)
  6. ETS Download UserData
    (最好加电池来Download)
  7. 加电池开机
    Download
    脚本
  8. 做一次恢复出厂设置
    (因应我们自己改动的原因,很多设置需要初始化)
  9. 检查一下软件的版本号,是否对应
  10. 再制作镜像
          1.do reset
          2.do init
          3.do save-img &filename.img //注意修改
    save-img对应的版本的地址范围
  11. 使用此镜像再重新写一块板子来检证
         写入img
         1.do reset
         2. do
    load-img
    &filename.img //注意修改
    load-img对应的版本的地址范围
    用这个板子给硬件进行校验,测试部走测试流程即可。

 

VIA Selle 新建Task方法

 尝试了在Selle 平台上建立一个新的Task来执行一些操作.Selle 0.17以后的版本可以试用,之前的没有试过.

具体的步骤如下:
1.在exeapi.h中新建一个定义,并指定其优先级 如:EXE_ZJJ_ID=36
,
2. 在exepowr.c中声明一个Task
/*——————————————————
* ZJJ Task
Declarations
*——————————————————-
*/
#define
ZJJ_TASK_CB_P
&Exe
ZjjTaskCb
extern void
ZjjTask(uint32 argc, void
*argv);
ExeTaskCbT ExeZjjTaskCb;
extern uint8
ZjjTaskStack[];
static const
MailQueueT
ZjjMailQueueTable[] = {ZJJ_TASK_MAIL_QUEUE_1,
EXE_MAILBOX_1_ID,
ZJJ_TASK_MAIL_QUEUE_2,
EXE_MAILBOX_2_ID};
/*——————————————————
在以下函数中添加一CB的定义:
const ExeTaskCbT *ExeTaskCb[] ={
 ZJJ_TASK_CB_P,
}
在TaskInitTableT中添加此Task的初始化表:
static const TaskInitTableT
TaskInitTable[] = {
,
 "ZjjTask", "ZjjEvGrp", "ZjjQue",
&Exe
ZjjTaskCb, ZjjTask,
ZJJ_TASK_STACK, ZJJ_TASK_PRI, EXE_ZJJ_ID,
  
sizeof(
ZjjMailQueueTable)/sizeof(MailQueueT), ZjjMailQueueTable,
   TaskCreateOptions,
TaskQueueCreateOptions, TaskMemoryAllocateOptions,
   (void
*)
ZjjTaskStack
}
3.
ZJJ_TASK_PRI的定义在exepowr.h 中                  #define ZJJ_TASK_PRI                36
并在此定义ZJJ_TASK_MAIL_QUEUE的大小:
 #defineZJJ_TASK_MAIL_QUEUE_1          20 *
EXE_MAIL_QUEUE_REC_SIZE
 #defineZJJ_TASK_MAIL_QUEUE_2          20 *
EXE_MAIL_QUEUE_REC_SIZE
 定义EXE_ZJJ_ID 与 ZJJ_TASK_PRI 的优先级应该是一致的
4.在exetaskstack.c中添加定义:
            uint8 ZjjTaskStack[ZJJ_TASK_STACK];
5.在其它文件中实现ZjjTask的功能,接收到的消息,针对不同消息的分别处理:
我添加了一个示例如下:
/*zjj add for a task*/
typedef enum
{
/*tts msgs*/
ZJJ_NEW_TASK_FIRST_MONPRINTF_MSG
= 0,
ZJJ_NEW_TASK_SECOND_MSG
}ZjjMsgIdT;
/* TTS_SYNTH_START_MSG msg data
structure */
typedef PACKED
struct
{
   int age;
}ZjjTaskMsgT;
ZjjTaskMsgT *msgp; //发送消息的格式很关键,我只试成功了typedef PACKED struct多消息结构,直接传送Int型的好像不行
msgp = (ZjjTaskMsgT *) ExeMsgBufferGet
(sizeof (ZjjTaskMsgT));
msgp->age=26;
//往这个Task发送消息的格式如下:
ExeMsgSend(EXE_ZJJ_ID, EXE_MAILBOX_1_ID,
ZJJ_NEW_TASK_FIRST_MONPRINTF_MSG, (void*)
msgp,sizeof(ZjjTaskMsgT));
extern "C" void ZjjTask(uint32
/*argc*/, void * /*argv*/)
{
ExeEventWaitT EvtStatus;
bool          
MsgStatus;
uint32        
MsgId;
uint32        
MsgSize;
void*         
MsgDataPtr = NULL;
while (ValStatusGet()
== FALSE)
{
ExeEventWait(
EXE_ZJJ_ID, EXE_SIGNAL_FALSE, EXE_MESSAGE_FALSE, ExeCalMsec(100) );
}
/* Initialize task
*/
/*MonPrintf("TtsTask
begin");*/
/*———————–*
* Event processing
loop *
*————————*/
while(TRUE)
{
/* Wait on mail
*/
EvtStatus =
ExeEventWait(EXE_ZJJ_ID, EXE_SIGNAL_TRUE, EXE_MESSAGE_TRUE, EXE_TIMEOUT_FALSE);
/* Check all signal
flags */
if (EvtStatus &
EXE_SIGNAL_TYPE)
{
;
}
/* Check for message in
mailbox */
if (EvtStatus &
EXE_MESSAGE_TYPE)
{
/* tts Message Handler
for TTS_API_MAILBOX*/
if (EvtStatus &
EXE_MAILBOX_1)
{
/* Read message */
MsgStatus =
ExeMsgRead(EXE_ZJJ_ID, EXE_MAILBOX_1_ID, &MsgId, &MsgDataPtr,
&MsgSize);
/* If message received
process it */
if (MsgStatus)
{
/* Process normal
operation messages */
                  switch(MsgId)
                  {
                      case
ZJJ_NEW_TASK_FIRST_MONPRINTF_MSG:
                        
MonPrintf("***********Debug By ZJJ***** ZJJ TASK IS RUNNING");
                        
MonPrintf("***********Debug By ZJJ***** ZJJ TASK IS RUNNING");
                        
MonPrintf("***********Debug By ZJJ***** ZJJ TASK IS RUNNING");
                        
MonPrintf("***********Debug By ZJJ***** ZJJ TASK IS
RUNNING:%d",((ZjjTaskMsgT*)MsgDataPtr)->age);                        
                        
MonPrintf("***********Debug By ZJJ***** ZJJ TASK IS RUNNING");
                        
MonPrintf("***********Debug By ZJJ***** ZJJ TASK IS
RUNNING");                        
                     
        break;
                      default:
                     
        break;
                  }
/*MonFault(MON_MMDL_FAULT_UNIT,
TTS_ERR_UNKNOWN_CMD, MsgId, MON_CONTINUE);*/
}         
}
/* tts Message Handler
for EXE_MAILBOX_2*/                        
if (EvtStatus &
EXE_MAILBOX_2)
{
;
}
/* Free message data
buffer */
if (MsgDataPtr)
{
ExeMsgBufferFree(
(void*)MsgDataPtr);
MsgDataPtr = NULL;
}
}
} /* End While(TRUE)
*/
}
有些声名在这里可能没有列出,参照别的Task请自行添加即可,我本地己编译通过,可以正常执行.
希望本文对你有所帮助.

 

VIA 输入法 处理流程

 

VIA
的Selle版本中集成了EZI的输入法核心,但外围的实现,如按键,候选,翻页等,都是由VIA自己实现的,其过程也比较复杂,现就移植盲人输入法所总结的与大家分享.
(以下以En输入法为例来说明,代码均为伪代码,仅为说明意图,最后有补充的IME的相关基本概念可参考)
[A]从按键到得到候选字的过程.
1.首先由ImeManagerC的KeyHandler来接收按键消息.
ImeManagerC::KeyHandler(uint32 KeyCode, uint32 KeyMsg){
  ImeC * ImeP = GetCurrentIme();
  if (ImeP->KeyHandler(KeyCode, KeyMsg))
             }
ImeP->KeyHandler多态地实现了不同Ime调用不同的keyHandler.如EN则会调用
ImeCandidateEnLowerC 这个类的实现,又由于ImeCandidateEnLowerC 没有实现KeyHandler 的处理,而是由他的基类 CandidateImeC 来实现的.
2.基类 CandidateImeC
实现了从ImeManagerC接收到的KeyHandler处理.
   bool
CandidateImeC::KeyHandler(uint32 KeyCode, uint32 KeyMsg){
        ImeEngineC *
EngineP = ImeManagerC::GetImeEngine();
        if
(
EngineP->AddElement(KeyCode))
        {
          if
(EngineP->IsElementBufferEmpty())
          {
           
CloseCandidate();
          }
          else
          {
           
ElementAdded();
          }
        }
从这里可以看出:真正由按键到候选的映射由有ImeEngine来实现的,这里的为EZI(当然还有别的其它Engine如T5),AddElement()的过程在这里不做更进一步的说明,其核心就是,把一个按键消息及其它相关变量映射出一组候选,并把此组候选放到一个Vector里.
 当按键结束后会就会调用CandidateImeC
的子类,来区别处理按键结束后的动作,这里即为,在屏幕上显示一个候选框,把在Vector里的内容,显示在此候选框中.
3.ElementAdded()的处理:
void
ImeCandidateEnLowerC::ElementAdded()
{
 GetCandidate(mSubCandidate,
0);
 bool NewCandidate =
ShowCandidate(CANDIDATE_MODE_NORMAL);
 mCandidateCtrlP->SetCandidates(&mSubCandidate,
1);
 if (!NewCandidate)
 {
   
DrawCandidate();
 }
}
GetCandidate() 实现了把由ImeEngine生成的候选Vector的内容赋值到mSubCandidate结构体中.
ShowCandidate()实现了在屏幕上显示候选区域的相关操作.
SetCandidates() 把mSubCandidate中的候选内容显示在屏幕上的候选区.
DrawCandidate()
调用DC具体实现在屏幕上的绘制工作.
至些,由按键到候选字的过程完成.下面是在候选区中选择,移动候选字的过程.
[B]选择一个候选字,切换候选字,候选字的翻页功能的实现.
1.候选区的操作与上有些类似,由ImeManagerC到CandidateImeC::KeyHandler来具体实现:
 if
(CandidateShowed())
 {
    if (KeyMsg ==
KEYMSG_PRESS)
    {
      return
HandleCandidateKey(KeyCode, KEYMSG_PRESS);
    }
    return TRUE;
    }
HandleCandidateKey()中主要实现了两个功能,第一是,处理在候选区的按键,其二是,跟据候选区移动的状态,更新候选区.
 (1)处理候选区的按键:
                    bool Ret = mCandidateCtrlP->OnKeyPress(KeyCode);
这里处理如Select按键,在候选区左右上下移动光标等操作.
 if
((KeyCode == KP_LEFT_KEY)
    ||
(KeyCode == KP_RIGHT_KEY)
    ||
(KeyCode == KP_UP_KEY)
    ||
(KeyCode == KP_DOWN_KEY)
    ||
(KeyCode == MAIN_CANIDIDATE_NAV_KEY))
 {
    return HandleNavigateKey(KeyCode);
 }
 else if
(KeyCode == KP_CENTER_SOFT_KEY)
 {
    return HandleSelectKey();
 }
如果是Select消息,则HandleSelectKey()会把一个状态变量
   mState.mType =
CANDIDATE_STATE_SELECTED;
          在HandleCandidateKey()
 if
(State.mType == CANDIDATE_STATE_SELECTED)
 {
CandidateSelected(State.mArea, State.mIndex);
   
return TRUE;
 }
CandidateSelected会处理把候选显示到屏幕上的相关操作,会回调CandidateImeC::InsertString()来处理显示,察看相关代码即可知.
这里着重说一下,在候选区移动光标,翻页的实现.以KP_RIGHT_KEY为例.首先会调用HandleNavigateKey()—->UpdateSelRect(mSubCandidateinfo, KeyCode))
UpdateSelRect()
这个函数是处理相关操作的关键,如在候选区中选右方向键,选择右边的候选字,此时要判断当前的位置,当前行,当前列(即在一行的中位置),判断是否超出此行,是否超出了此候选区中所有的内容,即在候选区中最后一位继续向后移动,显示更多候选内容.这时,会有状态变量来记录此时的变化.
     mState.mType =
CANDIDATE_STATE_NEED_MORE_DATA;
    
(
2).跟据候选区移动的状态,更新候选区.
上一步中说到若超出候选区,需要翻页,则状态变量为 CANDIDATE_STATE_NEED_MORE_DATA;
HandleCandidateKey()中会加以判断:
 if
(State.mType == CANDIDATE_STATE_NEED_MORE_DATA)
 {
    
GetCandidate(mSubCandidate, State.mIndex);
    
mCandidateCtrlP->SetCandidates(&mSubCandidate, 1);
 }
 DrawCandidate();
 这里更新了候选区的内容.
当然,这只是最大致步骤,具体会有很多变量记录候选区的内容,容量,行数与列数,在CandidateInfoT这个结构体中有记录.会读取更新这里的值,记录当前位置及相它相关操作.内容很多,这里不一一说明,具体的跟一下代码可知.
在盲人输入法中,利用第三方的API自己模拟了一个Engine来实现按键与候选的对应,然后移植并更动了上述的实现过程,实现了盲人输入法基本的功能.希望对你能有所帮助.
补充:基本概念
一个IME提供某种语言一个输入方式,它由输入法引擎(第三方提供)类ImeEngine,输入法类ImeCandidateEnLowerC,候选显示类CandidateImeC
(可选)构成.输入法引擎类Porting某个第三方的输入法引擎,实现按键序列到候选词的转换.输入法类指定输入语言,输入方法,并按它们配置输入法引擎,接收按键序列,和输入法引擎交互获取候选词,在用户选定候选词后负责向Edit输入字符串.候选显示类CandidateCtrl,以候选框的方式显示待选词.Edit Control使用IME
Manager来切换当前输入法,总汇处理各种按键分派到各输入法,各种输入法通过实现IME接口并在IME Manager中注册集成到VIA
VTUI2平台中

 

ADS库文件的制作及可能出现的问题

       
最近在VIA平台上移植盲人输入法,但第三方发过来的库文件,只要引用,就无法正常开机,甚至连Bootload都不进,开机没一点反应,后来他们给了只有一个空函数的库文件,手机依然无法开机,定位问题出在了库的制作上.但问题出在哪呢,于是自己动手写了一个空函数,然后做到库文件加进去
 
   
ADS1.2编译选项是: (例如,有一个mylib.c的文件)

    1> tcc -c -ansi -fpu softVFP
-W+np -Wn -O2 -Ospace -g- -fa -apcs /interwork  mylib.c

    2> armar –create mylib.a
mylib.o
   

生成的mylib.a就是最后的库文件,放到手机中,正常开机,可以正常调用我自己写的函数,更准确地定位是他们tcc的编译选项跟我们手机平台不一致造成的.
如果选项不对的话,如没有 -apcs /interwork  这个参数,在混合C/C++的时候,就会出错:Error: L6239E:Cannot call ARM symbol …

参考资料:
              <1> ADS1.2下建立自己的Lib库文件
            
          
<2>ARM
Infomation Center

 

VIA 移植 讯飞的TTS 编码与多线程

VIA平台上移植讯飞的TTS,在移植讯飞提供的Sample时,遇到在Syntext死机重启的问题,一直没能解决,后来从VIA公司拿到他们早些年移植后的版本,移到我们的D300中是好用的.

移植过程按说明步骤就好,下面就几点困扰我多时的问题与大家讨论:
一. 编码问题:
如果你对
UNICODE,GBK,UTF-8的区别不是很明白的话,还是读一读这篇文章. UNICODE,GBK,UTF-8区别
,
讯飞的TTS支持近10种的编码,默认的为GBK,但VIA平台上中文默认的为Unicode,英文默认的为ASCII.要想讯飞的TTS与VIA一起能正常工作,就需要统一编码,这里选择的是UTF-8.
提取VIA控件中的文字资源,加上自己定义的文字,合并后用TTS播放:
MenuItemC *item = new MenuItemC();
……
           StringC Name =
item->GetText(); 
           uint32
length=Name.GetLength()/2*3
+3;———-A
           char*
wstrResult=new char[length];
          
memset(wstrResult, 0, length);
          
int32 Chars1 =
Name.ToUtf8Str(wstrResult,length);————–B
           char
words[
3]={0};
          
words[
0]=0xE4;————-C
          
words[
1]=0xB9;
          
words[
2]=0x8B;
          
strcat(wstrResult,words);
 TtsInterSoundTst4(wstrResult);—————D
           delete
[] wstrResult;
———————E    
           wstrResult =
NULL;
就其中的某些部分详细说明一下. [A] Name.GetLength()得到的是控件中文本的长度,Unicode编码的,每个汉字占两个Bytes
(即两个Char),而UTF-8中每个汉字是由三个Bytes组成的,所以要除2乘3得到转换UTF-8后所需要的空间,加3是因为在后面加上了一个UTF-8的汉字占三个Bytes.
[B]是StringC的一个方法,可以输出UTF-8格式的内容,这里就把VIA默认的Unicode转换为了UTF-8.[C]Words[3]
一个UTF-8汉字的空间,[D]调用讯飞的TTS接口进行播放.此时wstrResult应该是UFT-8编码的字节表示,可以用MonPrintf((char*)wstrResult)打印出来查看.
二. 多线程的问题:
如果TtsInterSoundTst4的实现这么来写:
int
TtsInterSoundTst4(char* p)
{
MonPrintf("TtsSynthStartWithPlay!–
strlen(ChsTxtbP) = %d", strlen(ChsTxtbP));
TtsUiCallbackReg(TtsUiCallbackProc,
NULL);
TtsSynthStartWithPlay(p, strlen(p), NULL);—————–
F
return
0;
}
则TTS无法正常播放,起初以为是编码的问题,折腾了许久,后来才发现是多线程与指针的使用有问题,因为TtsSynthStartWithPlay是异步的,所以执行到F这里进,主线程会继续返回,执行E,而F由于优先级较低,所以会稍后执行,但在E的时候,指针指向的空间己被Delete,到TTS来播放时,己经找不到对应的内容了!所以更改了一下,在TtsInterSoundTst4进行了复制,在TTS播放完后,再释放空间.
int
TtsInterSoundTst4(char* p)
{
      content
= (char*) ValMalloc(j+1);
  memset(content, 0, j+1);
   memcpy(content,p,j);
   content[j]=’\0′;
MonPrintf("TtsSynthStartWithPlay!– strlen(content) = %d",
strlen(
content));
TtsUiCallbackReg(TtsUiCallbackProc,
NULL);
TtsSynthStartWithPlay(content, strlen(content), NULL);—————–
F
return
0;
}
至此TTS可以正常播放从VIA控件中获取的Unicode编码的内容,实现读屏.

 

VIA Flash 容量 划分

我们的项目D600,用的是16+8的Flash跟Rom,其中16M的Flash分为约12M的程序区,4M的用户数据区.但在加入大字体(20*20)的后,编译出错,提示:Load region FLASH size
(11557344 bytes) exceeds limit (11272192 bytes).

于是要把用户数据区减少,加大CP区的大小.
要修改两个文件,分别是Hwdflash.c cpflash_16_8.LNK(梅红色的为之前的大小)
Hwdflash.c中

static void
FlashInitSector(HwdFlashLowDevT * devP)

{

 if (devP->man_id ==
HWD_FLASH_MANID_AMD)

  {

#ifdef
FLASH_SPANSIONS71PL127N

    SectorLookupTable =
SectorLookupTable16MTB;

    FlashMaxSector = FLASH_SECTOR_69;

    SectionMapToSector[HWD_FLASH_CP_BOOT_CODE_SECTION].StartSector =
FLASH_SECTOR_0;

    SectionMapToSector[HWD_FLASH_CP_BOOT_CODE_SECTION].EndSector =
FLASH_SECTOR_0;

    SectionMapToSector[HWD_FLASH_CP_CODE_SECTION].StartSector = FLASH_SECTOR_4;

SectionMapToSector[HWD_FLASH_CP_CODE_SECTION].EndSector = FLASH_SECTOR_49;//FLASH_SECTOR_46// 49-4+1=46  A

    SectionMapToSector[HWD_FLASH_DSPM_CODE_SECTION].StartSector =
FlashMaxSector + 1; /*Realy no DSPM and DSPV section */

    SectionMapToSector[HWD_FLASH_DSPM_CODE_SECTION].EndSector =
FlashMaxSector + 1;

    SectionMapToSector[HWD_FLASH_DSPV_CODE_SECTION].StartSector =
FlashMaxSector + 1;

    SectionMapToSector[HWD_FLASH_DSPV_CODE_SECTION].EndSector =
FlashMaxSector + 1;

    SectionMapToSector[HWD_FLASH_CP2_CODE_SECTION].StartSector =
FlashMaxSector + 1;

    SectionMapToSector[HWD_FLASH_CP2_CODE_SECTION].EndSector = FlashMaxSector
+ 1;

    SectionMapToSector[HWD_FLASH_CP3_CODE_SECTION].StartSector =
FlashMaxSector + 1;

    SectionMapToSector[HWD_FLASH_CP3_CODE_SECTION].EndSector = FlashMaxSector
+ 1;

SectionMapToSector[HWD_FLASH_FSM_USER_SECTION].StartSector = FLASH_SECTOR_53;//50

    SectionMapToSector[HWD_FLASH_FSM_USER_SECTION].EndSector = FLASH_SECTOR_65;
    SectionMapToSector[HWD_FLASH_FSM_DATA_SECTION].StartSector = FLASH_SECTOR_1;
    SectionMapToSector[HWD_FLASH_FSM_DATA_SECTION].EndSector = FLASH_SECTOR_3;

    SectionMapToSector[HWD_FLASH_ALL_SECTION].StartSector =
FLASH_SECTOR_0;

    SectionMapToSector[HWD_FLASH_ALL_SECTION].EndSector = FlashMaxSector;

    SectionMapToSector[HWD_FLASH_PRI_SECTION].StartSector = FlashMaxSector +
1;

    SectionMapToSector[HWD_FLASH_PRI_SECTION].EndSector = FlashMaxSector +
1;

    SectionMapToSector[HWD_FLASH_FAULT_DATA_SECTION].StartSector =
FLASH_SECTOR_66;   /*64 k bytes is
ok.*/

    SectionMapToSector[HWD_FLASH_FAULT_DATA_SECTION].EndSector =
FLASH_SECTOR_69;

SectionMapToSector[HWD_FLASH_UI_WALL_PAPPER_SECTION].StartSector = FLASH_SECTOR_50;//47

SectionMapToSector[HWD_FLASH_UI_WALL_PAPPER_SECTION].EndSector = FLASH_SECTOR_50;//47

SectionMapToSector[HWD_FLASH_UI_MAIN_TOPIC_SECTION].StartSector = FLASH_SECTOR_51;//48

SectionMapToSector[HWD_FLASH_UI_MAIN_TOPIC_SECTION].EndSector = FLASH_SECTOR_52;//49

#endif
/*FLASH_SPANSIONS71PL127N */

     }
}

/* Top Bottom type
16M, Spansion S71PL127N, 128 + 64 */

#ifdef
FLASH_SPANSIONS71PL127N

static const uint32
SectorLookupTable16MTB[] = {

  /* 0 – 3 */

  0x00000000, 0x00010000,
0x00020000, 0x00030000, /* 4 blocks of 64K bytes size
= =  FLASH_SECTOR_0 */

  /* 4 – 10 */

  0x00040000, 0x00080000,
0x000C0000, 0x00100000, /* 62 blocks of 256k bytes size
=FLASH_SECTOR_1~4*/

  0x00140000, 0x00180000, 0x001C0000,
  /* 11 – 34 */

  0x00200000, 0x00240000, 0x00280000,
0x002C0000,

  0x00300000, 0x00340000, 0x00380000,
0x003C0000,

  0x00400000, 0x00440000, 0x00480000,
0x004C0000,

  0x00500000, 0x00540000, 0x00580000,
0x005C0000,

  0x00600000, 0x00640000, 0x00680000,
0x006C0000,

  0x00700000, 0x00740000, 0x00780000,
0x007C0000,

  /* 35 – 58 */

  0x00800000, 0x00840000, 0x00880000,
0x008C0000,

  0x00900000, 0x00940000, 0x00980000,
0x009C0000,

  0x00A00000, 0x00A40000,
0x00A80000, 0x00AC0000,

/
* FLASH_SECTOR_43 */A+2=48 == 0x00B40000

  0x00B00000, 0x00B40000, 0x00B80000, 0x00BC0000,

  0x00C00000, 0x00C40000, 0x00C80000,
0x00CC0000,

  0x00D00000, 0x00D40000, 0x00D80000,
0x00DC0000,

  /* 59 – 65 */

  0x00E00000, 0x00E40000, 0x00E80000,
0x00EC0000,

  0x00F00000, 0x00F40000, 0x00F80000,
  /* 66 – 69 */

  0x00FC0000, 0x00FD0000, 0x00FE0000,
0x00FF0000, /* 4 blocks of 64K bytes size */

  0x01000000, 0x01000000
};

#endif
/*FLASH_SPANSIONS71PL127N */

cpflash_16_8.LNK文件中:

FLASH  0x00040000 0x00B80000 // 0x00AC0000
{
   FLASH 0x00040000 0x00B40000
   {
      copy.o     (+RO, +FIRST)
      *          (+RO)
   }
}

Hwdflash.c 中貌似可以根据需要任意调整FLASH_SECTOR的大小(不要超过最大值),关键的是LNK文件中数值大小的确定:方法为

FLASH_SECTOR_49FLASH_SECTOR_4+1=46; (1是因为包含此分块) 所以在SectorLookupTable16MTB表中查找到第46块,然后再加2(因为0 – 3 对应了FLASH_SECTOR_1 ,4开始对应FLASH_SECTOR_2所以46对应了第48组数,即为0x00B40000,又因为LNK文件中的第二个数是大小,而非终止位置,所以要填第49组数0x00B80000)
以上只是以UI的观点来看驱动调整,不对之处还请指证:)