| 
				 一、微软的BizTalk Server 2006 R2 
1.BizTalk Server 2006 R2介绍 
微软公司BizTalk Server 的最新版本为 BizTalk Server 2006 R2,其在BizTalk Server业务流程管理平台上新增RFID和EDI功能,以增强BizTalk对应用开发商的支持,为RFID技术的应用、推广提供了一个功能强大的平台。其提供了基于XML标准和Web Services的开放式接口,它含有RFID硬件设备的标准接入协议及管理工具,方便软硬件合作伙伴在本平台上进行开发、应用。作为微软的一个平台级软件,微软RFID开发服务平台不仅能和微软的其他产品进行良好的集成,而且能与其他厂商的产品进行良好的集成。BizTalk RFID R2组件作为BizTalk Server 2006产品的一个重要组件,已于2007年9月正式发布。而作为RFID设备硬件提供商,只要按照微软公司的RFID Services的架构,实现DSPI接口部分就可以很容易地把自己的RFID设备无缝地连接到BizTalk Server 2006 产品中去。 
2.DSPI 结构的介绍 
 
  
  
 
图1 微软中间件产品结构图 
  
DSPI (设备提供程序应用接口)是微软和全球四十家RFID硬件合作伙伴制定的一套标准接口,其结构如图1所示。所有支持DSPI的各种(RFID、条码、IC卡等)设备在Microsoft Windows 上即插即用。对于软件合作伙伴,微软RFID开发服务平台提供了OM/API's(对象模型/应用程序访问接口),这是为上层的各类软件解决方案服务的。OM/API's可以使用各种Managed Code(比如C#等)来实现。 
二、DSPI的关键类 
在微软的BizTalk2006 RFID 组件架构中,为DSPI接口预留了很多的类和方法。作为RFID硬件设备提供商,要想使自己的RFID硬件产品无缝地连接到BizTalk2006产品中,只需要根据微软的BizTalk2006 RFID 组件为DSPI接口预留的类或方法,及自己RFID硬件产品的特性和操作方法,把相应的功能编程实现(如:最基本的设备连接),便可以把自己的RFID硬件设备无缝地连接到BizTalk2006 平台中去。下面就把实现DSPI时需要关注的主要类与方法做简要的介绍。 
1.    DeviceProvider类 
在此类中主要实现了DSPI接口连接硬件设备与BizTalk2006 时需要一些方法。DeviceProvider类继承自DeviceListener,是DSPI接口的入口点,所有硬件设备提供商实现的DSPI接口都必须提供这些方法。关于这些方法的更详细说明,可以参考Microsoft的帮助文档。 
  public abstract class DeviceProvider : DeviceListener { 
public static ProviderMetadata GetProviderMetadata();        
        public abstract event EventHandler<NotificationEventArgs> ProviderNotificationEvent; 
        public abstract void Init(RfidProviderContext providerContext, string providerName, PropertyProfile providerInitParameters); 
        public abstract void Shutdown(); 
        public abstract PhysicalDeviceProxy GetDevice(ConnectionInformation connectionInformation); 
        public abstract SerializableValue GetProviderProperty(string groupName, string propertyName); 
        public abstract void SetProviderProperty(EntityProperty property); 
    } 
2.    PhysicalDeviceProxy类 
在此类中主要定义消息和通信层的方法。在这里列出的方法中,有些是不需要硬件设备提供商再实现的,如:SendMessage(Command command)等。在微软提供的DSPI接口架构中,微软公司已经帮助RFID硬件设备提供商实现了其功能,作为RFID硬件设备提供商只须知道如何调用即可。在下文中会有简单演示,具体的使用方法可以参考Microsoft的帮助文档。 
    public abstract class PhysicalDeviceProxy { 
public abstract DeviceInformation DeviceInformation { get; } 
        public abstract void SendMessage(Command command); 
        public abstract void SendMessage(string sourceName, Command command); 
        public abstract void SetupConnection(AuthenticationInformation authenticationInformation); 
        public abstract void Close(); 
        public abstract Collection<DeviceCapability> GetDeviceCapabilities(); 
        public abstract bool IsConnectionAlive(); 
        public abstract Dictionary<string, PropertyProfile> GetSources(); 
        public abstract Collection<string> GetPropertyGroupNames(); 
        public abstract Dictionary<PropertyKey, RfidEntityMetadata> GetPropertyMetadata(string propertyGroupName); 
        public abstract void ResetToFactorySettings(); 
        public abstract void Reboot();         
        public abstract event EventHandler<NotificationEventArgs> DeviceNotificationEvent;    } 
3.    命令格式 
作为RFID硬件设备提供商可以根据自己设备的特性及这些命令的格式,见下表,具体地实现相应的命令功能。需要注意的是,这里列出来的命令,并非要全部实现的,但一些主要的命令必须实现。如:读电子标签的EPC号命令(标签ID号)、读电子标签用户区数据命令、写电子标签的EPC号命令、写电子标签用户区数据命令等。通常这些命令是RFID硬件设备提供商的RFID硬件设备必须支持的命令。对于这些命令的具体实现方法,笔者在后文中有比较详细的演示,更详细的使用说明与实现方法,可以参考Microsoft的帮助文档。 
 
  
  
三、DSPI接口程序 
图2所示是Microsoft公司提供的一个程序框架。在这里只截取了解决方案资源管理器部分以方便说明。作为RFID硬件设备供应商,基本可以不用关心Framework 、obj、Providerlibary 里的内容。但需要特别注意的是,DSPI目录下的所有文件,及UDPTransport、VendorHelper、RaiFuCommand、RaifuError这四个文件,这些相关的文件是整个DSPI实现的关键部分(文件名开发者可以随意定义),核心的实现代码均包含在这些文件之中。同时,读者也需要注意,每个“.cs”文件的头部都必须引用Microsoft公司提供的DSPI的相关引用库文件等。下面我们就具体来实现、并尽可能地详细解释各个文件,读者在实际实现过程中,可以参考Microsoft的帮助文档。 
 
  
 
  
  
  
图2 工程代码结构 
  
1.UdpTransport.cs 文件 
在这个文件中,主要实现了DSPI与设备连接时的初始化、同步工作方式与异步工作方式的切换、断开连接等。在这里重点需要关心的是,同步工作方式与异步工作方式的切换,同步工作方式即通常说的命令工作方式,一次命令,一次应答;异步工作方式即通常说的定时工作方式,收到定时命令后,一直工作,直到接受到停止命令。同时,由于此文件内实现的相关功能的重要性,笔者也尽可能多地列出相关的代码,并作了简要说明,核心代码如下: 
namespace Microsoft.Rfid.Reader.Driver.Acme 
{ 
    internal class UdpTransport 
{    
//  …  (省略部分,此处为一些变量声明) 
        public void Init(TcpTransportSettings settings,ILogger logger, AcmeReaderLayer readerLayer) 
        { 
            lock (this) 
            { 
                try 
                { 
       //  …  (省略部分,为构造实现) 
        internal byte[] SendCommand(byte[] cmd,int antenna) 
        { 
            lock (this) 
   {   
//  …  (省略部分,此处为一些变量声明) 
                try 
                { 
                    asynEvent.Reset();                     
                    this.StopAsynReadCommand(); 
                    //设置天线 
                    IPEndPoint remoteEndPoint1 = new IPEndPoint(IPAddress.Parse(host), port);                    
                    client.Send(RfCmd.RfSetAntenna(antenna), RfCmd.RfSetAntenna(antenna).Length, remoteEndPoint1); 
                    temp = client.Receive(ref remoteEndPoint1); 
                    //发送命令 
                    IPEndPoint remoteEndPoint2 = new IPEndPoint(IPAddress.Parse(host), port); 
                    client.Send(cmd, cmd.Length, remoteEndPoint2); 
                    response = client.Receive(ref remoteEndPoint2); 
                } 
                finally 
                { 
                    this.StartAsynReadCommand(); 
                    asynEvent.Set(); 
                } 
//  …  (省略部分,为异常处理) 
                return response; 
            } 
        } 
        // 断开与设备的连接 
        internal void Close() 
        { 
//  …  (省略部分,为断开设备连接的代码) 
        } 
        //开始异步工作方式 
        internal void StartAsynReadCommand() 
        { 
            try 
            { 
                IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port); 
                asynClient.Send(RfCmd.RfSynchronousStart(), RfCmd.RfSynchronousStart().Length, remoteEndPoint); 
                byte[] temp = asynClient.Receive(ref remoteEndPoint); 
            } 
     //  …  (省略部分,为异常处理) 
        } 
        //停止异步工作方式 
        internal void StopAsynReadCommand() 
        { 
            try 
            {                
                IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(host), port); 
                asynClient.Send(RfCmd.RfSynchronousEnd(), RfCmd.RfSynchronousEnd().Length, remoteEndPoint); 
                byte[] temp = asynClient.Receive(ref remoteEndPoint); 
  
                Thread.Sleep(100); 
            } 
 //  …  (省略部分,为异常处理) 
        } 
        //开始异步读线程 
        internal void StartAsynReadThread() 
        { 
            asynReceiveThread = new Thread(new ThreadStart(AsynRecevieTagID)); 
            asynReceiveThread.IsBackground  = true; 
            asynReceiveThread.Start(); 
        } 
//  …  (省略部分,此处的其他命令处理方法一致) 
    } 
} 
2.VendorHelper.cs 文件 
在这个文件里主要实现了设备支持的命令,同样,由于硬件设备支持的命令较多,而且不同的硬件设备提供商的设备,提供的命令也不一样,故在下面列出的关键代码中,只列出一两条命令的处理方法,以供参考。同时,因为其他命令的处理方法都基本类似,故笔者都作了省略处理,不再赘述。并且在相关代码处都做了比较详细的说明。核心代码如下: 
namespace Microsoft.Rfid.Reader.Driver.Acme 
{     
    public class VendorHelper 
    { 
//  …  (省略部分,为变量、构造声明) 
        // 函数名:GetCommandBytes,根据传入的参数,判别命令类型,以执行相应的命令; 
        internal byte[] GetCommandBytes(Command command) 
        { 
            //访问码,默认都设为“0000” 
            byte[] AccessPassword ={ 0x0, 0x0, 0x0, 0x0 };       
            RaiFuCommand RfCmd = new RaiFuCommand(); 
           // 功能说明:根据命令类型,发送相应的命令,此为读卡命令 
            if (command is GetTagsCommand) 
            { 
                return RfCmd.RfListTagId(); 
            }               
//  …  (省略部分,此处的其他命令处理方法一致) 
            // 功能说明:根据命令类型,发送相应的命令,此为读用户数据区数据命令             
             if (command is GetPartialTagDataCommand) 
             {                                     
                 GetPartialTagDataCommand getTagData = (GetPartialTagDataCommand)command;                  
                 byte word = (byte)(getTagData.GetTagId().Length / 4); 
                 byte[] id = getTagData.GetTagId(); 
                 byte mem = 3;                  
                 byte addr =(byte)getTagData.Offset; 
                 byte len = (byte)getTagData.Length; 
                  
                 return RfCmd.RfReadDataBlock(word, id, mem, addr, len, AccessPassword); 
             } 
        //函数名:ParseResponse,根据传入的参数,判别命令类型,以执行相应的命令; 
           internal static Response ParseResponse(byte[] reply ,Command command) 
        { 
//  …  (省略部分) 
            switch (reply[2]) 
            { 
                //获得电子EPC号命令 
                case 0xEE: 
                    Collection<TagReadEvent> tags = new Collection<TagReadEvent>(); 
                    GetTagsResponse response = new GetTagsResponse(RfCmd.ParseTags(reply)); 
                    return response; 
//  …  (省略部分,此处的其他命令处理方法一致) 
                //得到用户数据区数据命令 
                case 0xEC:                    
                    GetPartialTagDataResponse getPartialTagDataResponse = new GetPartialTagDataResponse(RfCmd.PareseTagData(reply)); 
                    return getPartialTagDataResponse;               
            } 
            return null; 
        } 
        // 功能描述:根据传入错误码参数,抛出错误提示信息; 
        internal static void throwCommandProcessingException(byte  errorCodes) 
        { 
//  …  (省略部分,为异常、错误等信息处理) 
        }         
//  …  (省略部分,为EPC号信息的处理过程) 
} 
} 
3.GetTags.cs文件 
读者需要特别注意的是,在图2所示的DSPI目录里,所有*.cs 文件里的内容基本一样,即每个文件对应一条具体的命令,或者说一条命令就要建立一个文件。因此,在处理这些命令时,只需在一个文件的基础上做简单的修改,便可以很容易地完成另一条命令文件。下面就以一条具体的命令来做示范-读卡命令,由于其他命令的实现方法类似,只需要修改几个关键字便可完成另一条命令。实现的核心代码如下: 
namespace Microsoft.Rfid.Reader.Driver.Acme.RSPI 
{ 
       internal class GetTags : IRSPIFunction 
    { 
//  …  (省略部分,为变量、构造声明等) 
        public Type Command 
        { 
            get 
            { 
                //若是其他命令时,只需修改 GetTagsCommand 
                return typeof(GetTagsCommand); 
            } 
        } 
        public Command handleCommand(Command command, IReaderLayer reader, Command2SynchronousCallsConverter callsConverter,ResponseManager responseManager) 
        { 
//  …  (省略部分,为构造声明) 
            try 
            { 
            //若是其他命令时,只需修改下面三句,具体的可以参考微软帮助文档 
                response = ((AcmeReaderLayer)reader).GetResponse(command) as GetTagsResponse; 
                if (response == null) 
                    response = new GetTagsResponse(new Collection<TagReadEvent>()); 
            } 
            //… (省略部分,为根据错误码,处理相关的异常、错误等提示信息的代码) 
    }   
} 
4.RaiFuCommand.cs文件 
主要实现命令格式的封装,以方便在其他地方调用,用户可以根据自己硬件设备的命令协议格式完成封装工作。由于命令比较多,如图3所示,每类命令集合展开后都有很多的命令格式封装。在此,作者只列出一条命令的封装格式,即读电子标签EPC号的命令。实现的核心代码如下: 
namespace Microsoft.Rfid.Reader.Driver.Acme 
 { 
 internal class RaiFuCommand 
    { 
        public byte[] RfListTagId() 
        { 
            byte[] RaifuCmd = new byte[8]; 
            RaifuCmd[0] = 0x06; 
            RaifuCmd[1] = 0xEE; //读卡命令,后四个为读卡条件参数 
            RaifuCmd[2] = 0x01; 
            RaifuCmd[3] = 0x00; 
            RaifuCmd[4] = 0x00; 
            RaifuCmd[5] = 0x00; 
            RaifuCmd = RfPacketCmd(RaifuCmd); 
            return RaifuCmd; 
        } 
//  …  (省略部分,此处的其他命令处理方法一致,根据自己的命令进行封装)  
} 
} 
5.RaifuError.cs文件 
主要做了产品错误提示信息的处理,同命令格式封装文件里一样,不同的硬件提供商的产品,其错误提示信息会有差异,但处理方法基本也是一样的。其提示信息主要依赖硬件产品出现异常或错误时返回的异常或错误信息。实现的核心代码如下: 
namespace Microsoft.Rfid.Reader.Driver.Acme.Framework 
{ 
    public class RaifuError 
    { 
        public const int ERROR_CONN_FAIL = 0x01; 
        public const int ERROR_NO_TAG = 0x02;      
        //… (省略部分,为其他错误码的定义) 
        public  string GetErrorString(int ErrCode) 
        { 
            string RaiFuErrString = ""; 
            switch (ErrCode) 
            { 
                case ERROR_CONN_FAIL: 
                    RaiFuErrString = "Connect antenna fail"; 
                    break; 
                case ERROR_NO_TAG: 
                    RaiFuErrString = "No Tag"; 
                    break;      
   //… (省略部分,为其他错误码的处理)               
                default: 
                    RaiFuErrString = "Unbeknown Error"; 
                    break; 
            } 
            return RaiFuErrString; 
        } 
    } 
} 
    至此,基本完成了一个DSPI接口程序开发流程,当把这个DSPI配置到微软公司BizTalk 的RFID Manager 平台时,就可以通过RFID Manager平台控制RFID硬件设备读取电子标签的EPC号了。用户可以根据上面介绍的方法,把自己的硬件设备所支持的命令等都实现后,便完成一个功能全面的DSPI接口。这样就可以实现自己的RFID硬件设备与微软公司的BizTalk Server 2006平台的无缝连接。 
若想更方便地开发DSPI接口程序,建议用户先熟悉微软DSPI结构中所定义的命令格式。例如,在实现DSPI接口程序过程中,遇到一个问题,即在RFID Manager平台上,在通过DSPI来获取硬件设备的参数时,要是按原来提供的命令,需要两条命令才能完成,而这样需要消耗更多的系统资源。为此,我们调整了原来的命令,即把原来的两条命令,通过修改RFID硬件设备上的固件程序,合并成一条命令。这样不但节省了系统资源,而且使DSPI接口程序的结构更清晰,更容易实现了。 
四、DSPI调试 
在开发DSPI 接口程序时,调试方法需要特别注意,因为在工程里不可以直接加断点调试。其在调试时,要依赖RFID Manager平台,因此,必须做相关的设置。下面就具体介绍在开发DSPI接口程序时的调试方法与技巧。 
首先要运行RFID Manager 程序,并把DSPI工程文件编译后的*.dll,配置到RFID Manager这个平台,添加成功后的结果,如图3示。 
这里的*.dll文件,通常和工程名字一致,如工程文件名为: AcmeReaderProvider,而且假设此工程文件夹名也为AcmeReaderProvider,并放在桌面上,则编译后的AcmeReaderProvider.dll文件,在桌面的AcmeReaderProvider 文件夹里,其路径为:AcmeReaderProvider\bin\Debug\AcmeReaderProvider.dll里。 
 
  
 
图3配置DSPI后的RFID Manager 
在VS2005开发环境下,选择tools菜单,选择 Attach to Processes,这时出现如图5的界面,在这里按图4示双击w3wp.exe,把其添加到进程中即可,然后关闭对话框。 
 
 
  
图4选择要调试进程的对话框 
在调试程序时,每次编译DSPI接口程序后,都需要重新把*.dll文件配置一遍,哪怕程序没有任何修改,只是简单地重新编译一遍。同时这样重复配置的操作也比较麻烦,为此可以写个批处理文件来简化调试时的工作。假设工程文件名为:AcmeReaderProvider,而且此工程文件夹名也为AcmeReaderProvider,并放在桌面上。 
在文本编辑工具里实现如下的内容,并另存为Copy.bat (文件名可随意定义)(注意:下面文本中的“-”为空格)。 
"C:\Program Files\BizTalk RFID\bin\RfidClientConsole.exe" StopProvider RuiFuProvider 
Copy-"C:\Documents-and-Settings\Administrator\Desktop\AcmeReaderProvider\bin\Debug\AcmeReaderProvider.dll" -"C:\Program Files\BizTalk RFID\Providers\RuiFuProvider\bin" 
Copy-"C:\Documents-and-Settings\Administrator\Desktop\AcmeReaderProvider\bin\Debug\AcmeReaderProvider.pdb"- "C:\Program Files\BizTalk RFID\Providers\RuiFuProvider\" 
"C:\Program Files\BizTalk RFID\bin\RfidClientConsole.exe" StartProvider RuiFuProvider 
pause 
至此,按照上面的方法设置完成后,在DSPI接口工程里设置断点,然后在图4中,用鼠标右击RuiFuProvider,在弹出的菜单选择“start”,就可以看见程序停在了设置断点的地方。若重新编译DSPI接口程序后,只要双击桌面的Copy.bat文件(假设其放在桌面上)即完成重新配置。 
五、结语 
    本文介绍的DSPI接口程序,于2007年2月15日在微软顺利PCT测试,此举为国内首家,这标志着我们的RFID硬件设备可以无缝地连接到微软的BizTalk Server 2006 服务平台。希望通过笔者的介绍,对读者在BizTalk服务平台上进行DSPI接口开发能起到抛砖引玉的作用。同时笔者也相信,随着RFID标准的逐步完善,电子标签成本的不断降低,RFID技术的应用必将得到持续、广泛的应用。 
  
  
  
  
  			
				 |