状态机的数据结构如下:
typedef struct _comm_fsm
{
COMM_STATE state;
COMM_STATE state_host_init_connect;
COMM_STATE state_eq_init_connect;
deque m_comm_ev_queue;
HANDLE m_comm_ev_handle;
HANDLE m_comm_estab_comm_timer;
unsigned int m_msgid_s1f13;
} COMM_FSM;
还要定义状态切换函数:
void comm_fsm_switch_state(COMM_FSM* fsm, COMM_STATE new_state, COMM_FSM_ACTION pfn_action, void* action_param)
在”start_gem_service”中启动GEM服务之前启动"start_comm_fsm"状态机维护线程。
接收数据的处理函数是” OnRecvMessage”,添加一个过程函数来处理接收到的消息:
unsigned long do_process_msg(
gem_impl::GEM_EQUIPMENT* eq_ptr,
DWORD msgid,
UCHAR stream,
UCHAR function,
RAPID_SECS_ITEM* ptr_item)
{
// normal process
int ret_code = RAPID_SECS_SUCCESS;
switch (stream) {
case 1: // ========= S1
switch (function) {
case 14: ret_code = on_s1_f14(eq_ptr, ptr_item); break; // S1,F14
default: ret_code = RAPID_SECS_ERROR_UFN; break;
}
break;
default:
ret_code = RAPID_SECS_ERROR_USN;
break;
}
return ret_code;
}
发送的数据需要按照SECS协议中规定的标准格式发送,这部分内容RapidSecs基础库已经封装好了,只需要调用相应的接口即可。下面来实现上面的”on_s1_f13”函数,来看看如何调用接口组装数据。
首先,SECS E5中规定的S1F14消息结构如下:
解释一下就是,该结构是一个具有2个成员的List,第一个成员表示对建立通信进行确认,而第二个成员也是一个包含2个成员的List,其中成员1是设备型号,成员2是软件版本。于是,函数实现如下:
static int on_s1_f13(gem_impl::GEM_EQUIPMENT* ptr_eq, RAPID_SECS_ITEM* ptr_item, DWORD msgid)
{
assert(ptr_item->format == E5_FC_LIST);
assert(ptr_item->list_size == 0); // Host should send zero-length list.
comm_fsm_send_event(&(ptr_eq->m_comm_fsm), gem_impl::RECV_S1_F13);
// reply S1,F14
// 首先,创建一个具有2个成员的根List,注意调用的接口,参数表示2个成员。
RAPID_SECS_ITEM* item_s1f14 = rapid_secs_create_item_list(2);
char bin[1]; bin[0] = 0; // accepted
// 创建此List的第一个成员COMMACK,要注意数据类型和长度。
RAPID_SECS_ITEM* item_commack = rapid_secs_create_item_binary(bin, 1);
// 将此成员追加到创建的List
rapid_secs_list_item_append(item_s1f14, item_commack);
// 创建此List的第二个成员List
RAPID_SECS_ITEM* item_sublist = rapid_secs_create_item_list(2);
// 创建成员MDLN,是ASCII类型。
RAPID_SECS_ITEM* item_mdln = rapid_secs_create_item_ascii(EQ_MDLN);
// 创建成员SOFTREV,是ASCII类型。
RAPID_SECS_ITEM* item_softrev = rapid_secs_create_item_ascii(EQ_SOFTREV);
// 追加到成员List
rapid_secs_list_item_append(item_sublist, item_mdln);
rapid_secs_list_item_append(item_sublist, item_softrev);
// 追加到根List
rapid_secs_list_item_append(item_s1f14, item_sublist);
// 发送响应数据
rapid_secs_reply_msg_item(g_eq_ptr->hsms_session, 1, 14, item_s1f14, 0, msgid);
// 清除创建的数据
rapid_secs_del_item(item_s1f14);
return RAPID_SECS_SUCCESS;
}
到此,设备端Demo程序就可以与主机端测试工具建立正式的通讯连接了。响应消息如下图所示:
接口” do_process_msg”是用来处理数据响应的,也就是对收到的命令进行回复,比如收到S1F13,需要回复S1F14,收到S1F1,需要回复S1F2等等,那么本例中完整的接口内容如下:
unsigned long do_process_msg(
gem_impl::GEM_EQUIPMENT* eq_ptr,
DWORD msgid,
UCHAR stream,
UCHAR function,
RAPID_SECS_ITEM* ptr_item)
{
if (eq_ptr->m_comm_fsm.state == gem_impl::DISABLED) {
assert(FALSE);
return RAPID_SECS_SUCCESS;
}
// in NOT_COMMUNICATING state, only S1,F13/14 should be processed
if ((eq_ptr->m_comm_fsm.state == gem_impl::NOT_COMMUNICATING) &&
(stream != 1 || ((function != 13) && (function != 14)))) {
// notify 'equipment-initiated connect' substate
comm_fsm_send_event(&(eq_ptr->m_comm_fsm), gem_impl::RECV_NOT_S1_F13);
return RAPID_SECS_SUCCESS;
}
// normal process
int ret_code = RAPID_SECS_SUCCESS;
switch (stream) {
case 1: // ========= S1
switch (function) {
case 1: ret_code = on_s1_f1(eq_ptr, ptr_item, msgid); break; // S1,F1
case 3: ret_code = on_s1_f3(eq_ptr, ptr_item, msgid); break; // S1,F3
case 13: ret_code = on_s1_f13(eq_ptr, ptr_item, msgid); break; // S1,F13
case 14: ret_code = on_s1_f14(eq_ptr, ptr_item); break; // S1,F14
default: ret_code = RAPID_SECS_ERROR_UFN; break;
}
break;
case 2: // ========= S2
ret_code = RAPID_SECS_ERROR_USN;
break;
case 5: // ========= S5
switch (function) {
case 1: ret_code = on_s5_f1(eq_ptr, ptr_item, msgid); break; // S5,F1
case 2: break;
case 5: ret_code = on_s5_f5(eq_ptr, ptr_item, msgid); break; // S5,F5
default: ret_code = RAPID_SECS_ERROR_UFN; break;
}
break;
case 6: // ========= S6
switch (function) {
case 11: break; // S6,F11
case 12: break; // S6,F12
default: ret_code = RAPID_SECS_ERROR_UFN; break;
}
break;
case 14:
break;
default:
ret_code = RAPID_SECS_ERROR_USN;
break;
}
return ret_code;
}
设备端有一些消息需要主动上报,比如报警,当然前提也是需要主机端进行了使能。当设备产生报警信息时,需要主动的向主机发送S5F1消息进行上报,而主机在收到后需要回复S5F2消息进行确认。下面就来实现这个接口。
主动发送的消息采用在命令行中输入命令索引的方式,如下所示:
同样先来看一下S5F1消息的结构:
从结构上来看S5F1消息是由一个List构成,其带有3个成员ALCD、ALID和ALTX,结构很简单。具体的函数实现如下:
// 参数session是会话的句柄
unsigned long test_send_alarm(RSECSH session)
{
// 首先,创建一个具有3个成员的根List,注意调用的接口,参数表示3个成员。
RAPID_SECS_ITEM* ptr_item_s5f1 = rapid_secs_create_item_list(3);
char alcd = (char)0x80; // Alarm Set
// 创建成员ALCD
RAPID_SECS_ITEM* ptr_item_alcd = rapid_secs_create_item_binary(&alcd, 1);
// 创建成员ALID
RAPID_SECS_ITEM* ptr_item_alid = rapid_secs_create_item_u2(1000);
// 创建成员ALTX
RAPID_SECS_ITEM* ptr_item_altx = rapid_secs_create_item_ascii("a sample alarm text...");
// 追加到List
rapid_secs_list_item_append(ptr_item_s5f1, ptr_item_alcd);
rapid_secs_list_item_append(ptr_item_s5f1, ptr_item_alid);
rapid_secs_list_item_append(ptr_item_s5f1, ptr_item_altx);
// 发送消息
unsigned long msg_id = rapid_secs_send_msg_item(
session, 5, 1 , ptr_item_s5f1, RAPID_SECS_FLAG_NEED_REPLY);
// 清除
rapid_secs_del_item(ptr_item_s5f1);
return msg_id;
}
这样当在dos界面输入命令”1”后将发送S5F1消息到主机端,结果如下:
其它的主动上报命令也可参照此种方式实现,比如S2F23、S6F11等等。
综上,通过此种方式逐步的完善对各种命令的响应就可以实现GEM的全部功能。