はじめに
LLRP は RFIDリーダのネットワークインターフェースを標準化したプロトコルです.
仕様や詳細については下記のリンクを参照してください.
LLRP Toolkit
LLRP仕様
タグのメモリ構造などは下記を参考にしてください
GS1 EPC/RFID標準
RFIDタグの構造について
似た例(大体, 同じ例)が Impinj Support にもあります.
Hello LLRP (Low-Level Reader Protocol)
環境
名称 | Version | 備考 | |
---|---|---|---|
言語 | C# | 5.0 | |
ライブラリ | libltknet-sdk | 10.34.0 | libltknet-sdk(LLRP) |
タグIDの取得
リーダに対して読込命令を発行し, 周辺にある RFID の EPC を取得します.
リーダへの接続
下記のコードでは, IPv4 アドレス 192.168.100.64
のリーダに対して 5084(LLRP)
で接続を行います.
既に何かしらのプログラムが LLRP に接続している場合は, エラーが発生します.
暗号化された通信を使用する場合は, ポート番号を 5085
, useTLS = true
に変更します.
ENUM_ConnectionAttemptStatusTypestatus;LLRPClientclient=newLLRPClient(5084);// レポートイベントの登録client.OnRoAccessReportReceived+=newdelegateRoAccessReport(_LLRPClientOnRoAccessReportReceived);boolisOpened=client.Open(/* llrpReaderName = */"192.168.100.64",/* timeout = */3000,/* useTLS = */false,/* out status = */outstatus);ResetToFactoryDefault(client);StopROSpec(client,0);DisableROSpec(client,0);DeleteROSpec(client,0);AddROSpec(client,1234);EnableROSpec(client,1234);StartROSpec(client,1234);Console.ReadLine();StopROSpec(client,1234);DisableROSpec(client,1234);DeleteROSpec(client,1234);client.Close();
レポートイベント
privatestaticvoid_LLRPClientOnRoAccessReportReceived(MSG_RO_ACCESS_REPORTmsg){foreach(PARAM_TagReportDatadatainmsg.TagReportData){stringcrc=null,pc=null;stringepc=null;if(data.AirProtocolTagData.Count>0){// CRC, PCfor(inti=0;i<data.AirProtocolTagData.Count;++i){IParameterairProtocol=data.AirProtocolTagData[i];if(airProtocolisPARAM_C1G2_CRC){crc=string.Format("{0:X4}",(airProtocolasPARAM_C1G2_CRC).CRC);}elseif(airProtocolisPARAM_C1G2_PC){pc=string.Format("{0:X4}",(airProtocolasPARAM_C1G2_PC).PC_Bits);}}}// EPCIParameterpEpc=data.EPCParameter[0];if(pEpcisPARAM_EPC_96){epc=(pEpcasPARAM_EPC_96).EPC.ToHexString();}elseif(pEpcisPARAM_EPCData){epc=(pEpcasPARAM_EPCData).EPC.ToHexString();}Console.Error.WriteLine("CRC: {0}, PC: {1}\t{2} {3} {4} {5} {6} {7}-{8}",(crc!=null)?crc:"(none)",(pc!=null)?pc:"(none)",epc,data.ROSpecID.ROSpecID,data.AntennaID.AntennaID,data.PeakRSSI.PeakRSSI,data.TagSeenCount.TagCount,data.FirstSeenTimestampUTC.Microseconds,data.LastSeenTimestampUTC.Microseconds);}}
任意: リーダの初期化
publicstaticvoidResetToFactoryDefault(LLRPClientclient){MSG_SET_READER_CONFIGmsg=newMSG_SET_READER_CONFIG();MSG_ERROR_MESSAGEmsgErr=null;msg.ResetToFactoryDefault=true;MSG_SET_READER_CONFIG_RESPONSEmsgResp=client.SET_READER_CONFIG(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
Reader Operation (ROSpec) の追加
Impinj 社製のリーダは 1つの ROSpec に対応していますが, リーダによっては複数の ROSpec を追加できるかもしれません.(そのようなリーダに対応したことがないため)
そのため, ROSpec を追加する前に ROSpec を削除をすることをおすすめします. 削除方法については後述します.
ROSpec スタートトリガ
ROSpec の開始方法として下記の種類があります.
- GPI
リーダが GPIO を備えている場合, 対象のポートが ON または OFF (電圧の印加もしくは 0[V] に接地)した際に ROSpec を開始させます. - Immediate
ROSpec を有効化した時に, ROSpec を開始します. - Null
ROSpec を明示的に開始する必要があります. - Periodic
指定した秒数後に ROSpec を開始します. LLRP の使用を見る限りでは, 開始時間を設定することができるようです.(未検証)
ROSpec 停止トリガ
- Duration
ROSpec が開始した後, 指定した秒数後に ROSpec を停止します. - GPI_With_Timeout
GPIO の対象のポートが ON または OFF した際に, ROSpec を停止します. Timeout で秒数も指定することが可能です. - Null
ROSpec を明示的に停止する必要があります.
ROReport Spec
リーダから発行するレポート(ROSpec のみの場合は, 検出したタグの情報)を設定します.
レポートを発行する方法として,
- Upon_N_Tags_Or_End_Of_AISpec
N 個のタグを検出もしくは AISpec の終了 - Upon_N_Tags_Or_End_Of_ROSpec
N 個のタグの検出もしくは, ROSpec の終了
の 2つのどちらかを指定することができます.
タグの個数 N は 0 を指定すると, タグの個数についての終了条件が無視されることになります. そのため, ROSpec もしくは AISpec が終了した際にレポートの発行されることになります.
publicstaticvoidAddROSpec(LLRPClientclient,uintroSpecID){PARAM_ROSpecroSpec=newPARAM_ROSpec();roSpec.CurrentState=ENUM_ROSpecState.Disabled;roSpec.Priority=0;// ROSpec の削除, 有効化, 無効化, 開始, 停止で同じ ROSpec ID を使用します.roSpec.ROSpecID=1234;roSpec.SpecParameter=newUNION_SpecParameter();// Boundary SpecPARAM_ROBoundarySpecboundarySpec=newPARAM_ROBoundarySpec();roSpec.ROBoundarySpec=boundarySpec;// Start triggerPARAM_ROSpecStartTriggerstartTrigger=newPARAM_ROSpecStartTrigger();boundarySpec.ROSpecStartTrigger=startTrigger;startTrigger.ROSpecStartTriggerType=ENUM_ROSpecStartTriggerType.Null;// Stop TriggerPARAM_ROSpecStopTriggerstopTrigger=newPARAM_ROSpecStopTrigger();boundarySpec.ROSpecStopTrigger=stopTrigger;stopTrigger.ROSpecStopTriggerType=ENUM_ROSpecStopTriggerType.Null;// Report specPARAM_ROReportSpecreportSpec=newPARAM_ROReportSpec();roSpec.ROReportSpec=reportSpec;// N個のタグを検出もしくは ROSpec の終了を条件にしてタグレポートを発行させる.// 0 にしているため, この場合では ROSpec の終了のみを条件としている.reportSpec.N=0;reportSpec.ROReportTrigger=ENUM_ROReportTriggerType.Upon_N_Tags_Or_End_Of_ROSpec;// Tag report content selectorPARAM_TagReportContentSelectortagReportContentSelector=newPARAM_TagReportContentSelector();reportSpec.TagReportContentSelector=tagReportContentSelector;// EPC 領域の先頭 2[word] CRC + PCBits の取得tagReportContentSelector.AirProtocolEPCMemorySelector=newUNION_AirProtocolEPCMemorySelector();tagReportContentSelector.AirProtocolEPCMemorySelector.Add(newPARAM_C1G2EPCMemorySelector(){EnableCRC=true,EnablePCBits=true});// タグ受信時のレポートに// アンテナID, 周波数ID, [初-末]検出時刻, 受信感度, ROSpecID, タグ検出回数// を含めるtagReportContentSelector.EnableAntennaID=true;tagReportContentSelector.EnableChannelIndex=true;tagReportContentSelector.EnableFirstSeenTimestamp=true;tagReportContentSelector.EnableLastSeenTimestamp=true;tagReportContentSelector.EnablePeakRSSI=true;tagReportContentSelector.EnableROSpecID=true;tagReportContentSelector.EnableTagSeenCount=true;// Antenna Inventory specPARAM_AISpecaiSpec=newPARAM_AISpec();roSpec.SpecParameter.Add(aiSpec);// AISpec stop triggerPARAM_AISpecStopTriggeraiSpecStopTrigger=newPARAM_AISpecStopTrigger();aiSpec.AISpecStopTrigger=aiSpecStopTrigger;aiSpecStopTrigger.AISpecStopTriggerType=ENUM_AISpecStopTriggerType.Null;// 使用するアンテナを指定aiSpec.AntennaIDs=newUInt16Array();// 0 を指定するとリーダが対応しているアンテナすべてを有効化できます.//aiSpec.AntennaIDs.Add(0);aiSpec.AntennaIDs.Add(1);aiSpec.AntennaIDs.Add(2);aiSpec.AntennaIDs.Add(3);aiSpec.AntennaIDs.Add(4);aiSpec.InventoryParameterSpec=newPARAM_InventoryParameterSpec[1];// Inventory parameter specPARAM_InventoryParameterSpecinventoryParameterSpec=newPARAM_InventoryParameterSpec();aiSpec.InventoryParameterSpec[0]=inventoryParameterSpec;inventoryParameterSpec.ProtocolID=ENUM_AirProtocols.EPCGlobalClass1Gen2;inventoryParameterSpec.InventoryParameterSpecID=1235;inventoryParameterSpec.AntennaConfiguration=newPARAM_AntennaConfiguration[4];// アンテナ設定for(ushorti=0;i<4;++i){PARAM_AntennaConfigurationantennaConfig=newPARAM_AntennaConfiguration();inventoryParameterSpec.AntennaConfiguration[i]=antennaConfig;antennaConfig.AntennaID=(ushort)(i+1);// RFReceiverPARAM_RFReceiverrfReceiver=newPARAM_RFReceiver();antennaConfig.RFReceiver=rfReceiver;// 受信感度 -80.00[dBm]rfReceiver.ReceiverSensitivity=1;// RFTransmitterPARAM_RFTransmitterrfTransmitter=newPARAM_RFTransmitter();antennaConfig.RFTransmitter=rfTransmitter;rfTransmitter.ChannelIndex=1;rfTransmitter.HopTableID=0;// 出力電力 30.00[dBm]rfTransmitter.TransmitPower=81;}MSG_ADD_ROSPECmsg=newMSG_ADD_ROSPEC();msg.ROSpec=roSpec;MSG_ERROR_MESSAGEmsgErr=null;MSG_ADD_ROSPEC_RESPONSEmsgResp=client.ADD_ROSPEC(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){// Success}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
ROSpec の削除
publicstaticvoidDeleteROSpec(LLRPClientclient,uintroSpecID){MSG_DELETE_ROSPECmsg=newMSG_DELETE_ROSPEC();msg.ROSpecID=roSpecID;MSG_ERROR_MESSAGEmsgErr=null;MSG_DELETE_ROSPEC_RESPONSEmsgResp=client.DELETE_ROSPEC(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){// Success}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
ROSpec の有効化
publicstaticvoidEnableROSpec(LLRPClientclient,uintroSpecID){MSG_ENABLE_ROSPECmsg=newMSG_ENABLE_ROSPEC();msg.ROSpecID=roSpecID;MSG_ERROR_MESSAGEmsgErr=null;MSG_ENABLE_ROSPEC_RESPONSEmsgResp=client.ENABLE_ROSPEC(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){// Success}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
ROSpec の無効化
publicstaticvoidDisableROSpec(LLRPClientclient,uintroSpecID){MSG_DISABLE_ROSPECmsg=newMSG_DISABLE_ROSPEC();msg.ROSpecID=roSpecID;MSG_ERROR_MESSAGEmsgErr=null;MSG_DISABLE_ROSPEC_RESPONSEmsgResp=client.DISABLE_ROSPEC(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){// Success}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
ROSpec の開始
publicstaticvoidStartROSpec(LLRPClientclient,uintroSpecID){MSG_START_ROSPECmsg=newMSG_START_ROSPEC();msg.ROSpecID=roSpecID;MSG_ERROR_MESSAGEmsgErr=null;MSG_START_ROSPEC_RESPONSEmsgResp=client.START_ROSPEC(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){// Success}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
ROSpec の停止
publicstaticvoidStopROSpec(LLRPClientclient,uintroSpecID){MSG_STOP_ROSPECmsg=newMSG_STOP_ROSPEC();msg.ROSpecID=roSpecID;MSG_ERROR_MESSAGEmsgErr=null;MSG_STOP_ROSPEC_RESPONSEmsgResp=client.STOP_ROSPEC(/* msg = */msg,/* out msgErr = */outmsgErr,/* timeout = */3000);if(msgResp!=null){// Success}elseif(msgErr!=null){Console.Error.WriteLine(msgErr.LLRPStatus.ErrorDescription);}else{// Timeout}}
リーダの切断
LLRP.Close
内で MSG_CLOSE_CONNECTION
を使用して切断処理を行っています.
client.Close();
実行結果
上述した設定に対応したリーダであれば, 下図のような結果になると思います.
この結果の例の FirstSeenTimestampUTC
LastSeenTimestampUTC
で取得された値は, Unix時間となっているため, そのままの値を DateTime に渡すと正しくない時間が取得されてしまいます.
小言
リーダによって対応するメッセージやパラメータが相違している場合があり,
例えば Impinj 社製のリーダを制御するプログラムを作成しても他社のリーダを動作させられないことがあります.
サンプル例としたコード中にコメントにて // Success
の場所がありますが, リーダによっては msgResp
内の LLRPStatus
にエラーメッセージおよびステータスコートが含まれている可能性があります.
その場合, msgErr
は常に null
になります.(ほんと, やめてほしい)