Files
2026-03-09 09:01:17 -04:00

1922 lines
50 KiB
C
Executable File

/*
*****************************************************************************
* *
* IMPINJ CONFIDENTIAL AND PROPRIETARY *
* *
* This source code is the sole property of Impinj, Inc. Reproduction or *
* utilization of this source code in whole or in part is forbidden without *
* the prior written consent of Impinj, Inc. *
* *
* (c) Copyright Impinj, Inc. 2007,2008. All rights reserved. *
* *
*****************************************************************************/
/**
*****************************************************************************
**
** @file docsample1.c
**
** @brief Simple example of LTKC that talks to an actual reader
**
** This is sometimes called the "LLRP Hello World" program
**
** The steps:
** - Connect to the reader (TCP)
** - Make sure the connection status is good
** - Clear (scrub) the reader configuration
** - Add and enable a ROSpec that does a simple operation
** - Uses mostly default settings except for ROReportSpec
** - Uses all antennas
** - Starts on command
** - Runs for 5 seconds
** - Reports all accumulated tag observations
** - Run the ROSpec 5 times
** - Clear (scrub) the reader configuration
**
** This program can be run with zero, one, or two verbose options (-v).
** no -v -- Only prints the tag report and errors
** -v -- Also prints one line progress messages
** -vv -- Also prints all LLRP messages as XML text
**
** IMPORTANT:
** This example was written before best practices were determined.
** Most of the command messages (including subparameters) are
** composed using local (auto) variables with initializers.
** It has been determined that this approach has drawbacks.
** Best practice is to use the constructor and accessors.
**
** Function deleteAllROSpecs() was recoded to demonstrate
** good technique. Someday we might be able to fix the rest.
**
*****************************************************************************/
#include <stdio.h>
#include "ltkc.h"
#include "impinj_ltkc.h"
/*
* BEGIN forward declarations
*/
int
main (
int ac,
char * av[]);
void
usage (
char * pProgName);
int
run (
const char * pReaderHostName);
int
checkConnectionStatus (void);
int
scrubConfiguration (void);
int
enableImpinjExtensions (void);
int
resetConfigurationToFactoryDefaults (void);
int
deleteAllROSpecs (void);
int
addROSpec (void);
int
enableROSpec (void);
int
startROSpec (void);
int
awaitAndPrintReport (void);
void
printTagReportData (
LLRP_tSRO_ACCESS_REPORT * pRO_ACCESS_REPORT);
void
printOneTagReportData (
LLRP_tSTagReportData * pTagReportData);
void
handleReaderEventNotification (
LLRP_tSReaderEventNotificationData *pNtfData);
void
handleAntennaEvent (
LLRP_tSAntennaEvent * pAntennaEvent);
void
handleReaderExceptionEvent (
LLRP_tSReaderExceptionEvent * pReaderExceptionEvent);
int
checkLLRPStatus (
LLRP_tSLLRPStatus * pLLRPStatus,
char * pWhatStr);
LLRP_tSMessage *
transact (
LLRP_tSMessage * pSendMsg);
LLRP_tSMessage *
recvMessage (
int nMaxMS);
int
sendMessage (
LLRP_tSMessage * pSendMsg);
void
freeMessage (
LLRP_tSMessage * pMessage);
void
printXMLMessage (
LLRP_tSMessage * pMessage);
/*
* END forward declarations
*/
/*
* Global variables
*/
/** Verbose level, incremented by each -v on command line */
int g_Verbose;
/** Connection to the LLRP reader */
LLRP_tSConnection * g_pConnectionToReader;
/**
*****************************************************************************
**
** @brief Command main routine
**
** Command synopsis:
**
** dx201 READERHOSTNAME
**
** @exitcode 0 Everything *seemed* to work.
** 1 Bad usage
** 2 Run failed
**
*****************************************************************************/
int
main (
int ac,
char * av[])
{
char * pReaderHostName;
int rc;
/*
* Process comand arguments, determine reader name
* and verbosity level.
*/
if(ac == 2)
{
pReaderHostName = av[1];
}
else if(ac == 3)
{
char * p = av[1];
while(*p)
{
switch(*p++)
{
case '-': /* linux conventional option warn char */
case '/': /* Windows/DOS conventional option warn char */
break;
case 'v':
case 'V':
g_Verbose++;
break;
default:
usage(av[0]);
/* no return */
break;
}
}
pReaderHostName = av[2];
}
else
{
usage(av[0]);
/* no return */
}
/*
* Run application, capture return value for exit status
*/
rc = run(pReaderHostName);
printf("INFO: Done\n");
/*
* Exit with the right status.
*/
if(0 == rc)
{
exit(0);
}
else
{
exit(2);
}
/*NOTREACHED*/
}
/**
*****************************************************************************
**
** @brief Print usage message and exit
**
** @param[in] nProgName Program name string
**
** @return none, exits
**
*****************************************************************************/
void
usage (
char * pProgName)
{
printf("Usage: %s [-v] READERHOSTNAME\n", pProgName);
printf("\n");
printf("Each -v increases verbosity level\n");
exit(1);
}
/**
*****************************************************************************
**
** @brief Run the application
**
** The steps:
** - Instantiate connection
** - Connect to LLRP reader (TCP)
** - Make sure the connection status is good
** - Clear (scrub) the reader configuration
** - Configure for what we want to do
** - Run inventory operation 5 times
** - Again, clear (scrub) the reader configuration
** - Disconnect from reader
** - Destruct connection
**
** @param[in] pReaderHostName String with reader name
**
** @return 0 Everything worked.
** -1 Failed allocation of type registry
** -2 Failed construction of connection
** -3 Could not connect to reader
** 1 Reader connection status bad
** 2 Cleaning reader config failed
** 3 Enable Impinj Extensions failed
** 4 Adding ROSpec failed
** 5 Enabling ROSpec failed
** 6 Something went wrong running the ROSpec
**
*****************************************************************************/
int
run (
const char * pReaderHostName)
{
LLRP_tSTypeRegistry * pTypeRegistry;
LLRP_tSConnection * pConn;
int rc;
/*
* Allocate the type registry. This is needed
* by the connection to decode.
*/
pTypeRegistry = LLRP_getTheTypeRegistry();
if(NULL == pTypeRegistry)
{
printf("ERROR: getTheTypeRegistry failed\n");
return -1;
}
/*
* Enroll impinj extension types into the
* type registry, in praparation for using
* Impinj extension params.
*/
LLRP_enrollImpinjTypesIntoRegistry(pTypeRegistry);
/*
* Construct a connection (LLRP_tSConnection).
* Using a 32kb max frame size for send/recv.
* The connection object is ready for business
* but not actually connected to the reader yet.
*/
pConn = LLRP_Conn_construct(pTypeRegistry, 32u*1024u);
if(NULL == pConn)
{
printf("ERROR: Conn_construct failed\n");
return -2;
}
/*
* Open the connection to the reader
*/
if(g_Verbose)
{
printf("INFO: Connecting to %s....\n", pReaderHostName);
}
rc = LLRP_Conn_openConnectionToReader(pConn, pReaderHostName);
if(0 != rc)
{
printf("ERROR: connect: %s (%d)\n", pConn->pConnectErrorStr, rc);
LLRP_Conn_destruct(pConn);
return -3;
}
/*
* Record the pointer to the connection object so other
* routines can use it.
*/
g_pConnectionToReader = pConn;
if(g_Verbose)
{
printf("INFO: Connected, checking status....\n");
}
/*
* Commence the sequence and check for errors as we go.
* See comments for each routine for details.
* Each routine prints messages per verbose level.
*/
rc = 1;
if(0 == checkConnectionStatus())
{
rc = 2;
if(0 == scrubConfiguration())
{
rc = 3;
if(0 == enableImpinjExtensions())
{
rc = 4;
if(0 == addROSpec())
{
rc = 5;
if(0 == enableROSpec())
{
int i;
rc = 6;
for(i = 1; i <= 5; i++)
{
printf("INFO: Starting run %d ================\n", i);
if(0 != startROSpec())
{
/* already tattled */
break;
}
if(0 != awaitAndPrintReport())
{
/* already tattled */
break;
}
}
if(i == 5)
{
rc = 0;
}
}
}
}
/*
* After we're done, try to leave the reader
* in a clean state for next use. This is best
* effort and no checking of the result is done.
*/
if(g_Verbose)
{
printf("INFO: Clean up reader configuration...\n");
}
scrubConfiguration();
}
}
if(g_Verbose)
{
printf("INFO: Finished\n");
}
/*
* Close the connection and release its resources
*/
LLRP_Conn_closeConnectionToReader(pConn);
LLRP_Conn_destruct(pConn);
/*
* Done with the registry.
*/
LLRP_TypeRegistry_destruct(pTypeRegistry);
/*
* When we get here all allocated memory should have been deallocated.
*/
return rc;
}
/**
*****************************************************************************
**
** @brief Await and check the connection status message from the reader
**
** We are expecting a READER_EVENT_NOTIFICATION message that
** tells us the connection is OK. The reader is suppose to
** send the message promptly upon connection.
**
** If there is already another LLRP connection to the
** reader we'll get a bad Status.
**
** The message should be something like:
**
** <READER_EVENT_NOTIFICATION MessageID='0'>
** <ReaderEventNotificationData>
** <UTCTimestamp>
** <Microseconds>1184491439614224</Microseconds>
** </UTCTimestamp>
** <ConnectionAttemptEvent>
** <Status>Success</Status>
** </ConnectionAttemptEvent>
** </ReaderEventNotificationData>
** </READER_EVENT_NOTIFICATION>
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
checkConnectionStatus (void)
{
LLRP_tSMessage * pMessage;
LLRP_tSREADER_EVENT_NOTIFICATION *pNtf;
LLRP_tSReaderEventNotificationData *pNtfData;
LLRP_tSConnectionAttemptEvent *pEvent;
/*
* Expect the notification within 10 seconds.
* It is suppose to be the very first message sent.
*/
pMessage = recvMessage(10000);
/*
* recvMessage() returns NULL if something went wrong.
*/
if(NULL == pMessage)
{
/* recvMessage() already tattled. */
goto fail;
}
/*
* Check to make sure the message is of the right type.
* The type label (pointer) in the message should be
* the type descriptor for READER_EVENT_NOTIFICATION.
*/
if(&LLRP_tdREADER_EVENT_NOTIFICATION != pMessage->elementHdr.pType)
{
goto fail;
}
/*
* Now that we are sure it is a READER_EVENT_NOTIFICATION,
* traverse to the ReaderEventNotificationData parameter.
*/
pNtf = (LLRP_tSREADER_EVENT_NOTIFICATION *) pMessage;
pNtfData = pNtf->pReaderEventNotificationData;
if(NULL == pNtfData)
{
goto fail;
}
/*
* The ConnectionAttemptEvent parameter must be present.
*/
pEvent = pNtfData->pConnectionAttemptEvent;
if(NULL == pEvent)
{
goto fail;
}
/*
* The status in the ConnectionAttemptEvent parameter
* must indicate connection success.
*/
if(LLRP_ConnectionAttemptStatusType_Success != pEvent->eStatus)
{
goto fail;
}
/*
* Done with the message
*/
freeMessage(pMessage);
if(g_Verbose)
{
printf("INFO: Connection status OK\n");
}
/*
* Victory.
*/
return 0;
fail:
/*
* Something went wrong. Tattle. Clean up. Return error.
*/
printf("ERROR: checkConnectionStatus failed\n");
freeMessage(pMessage);
return -1;
}
/**
*****************************************************************************
**
** @brief Scrub the reader configuration
**
** The steps:
** - Try to reset configuration to factory defaults,
** this feature is optional and may not be supported
** by the reader.
** - Delete all ROSpecs
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
scrubConfiguration (void)
{
if(0 != resetConfigurationToFactoryDefaults())
{
return -1;
}
if(0 != deleteAllROSpecs())
{
return -2;
}
return 0;
}
/**
*****************************************************************************
**
** @brief Enable Impinj extensions using IMPINJ_ENABLE_EXTENSIONS message
**
** This routine sends an IMPINJ_ENABLE_EXTENSIONS message. It then waits for
** the IMPINJ_ENABLE_EXTENSIONS_RESPONSE message and checks everything is OK.
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
enableImpinjExtensions (void)
{
LLRP_tSIMPINJ_ENABLE_EXTENSIONS Cmd = {
.hdr.elementHdr.pType = &LLRP_tdIMPINJ_ENABLE_EXTENSIONS,
.hdr.MessageID = 103,
};
LLRP_tSMessage * pRspMsg;
LLRP_tSIMPINJ_ENABLE_EXTENSIONS_RESPONSE *pRsp;
/*
* Send the message, expect the response of certain type
*/
pRspMsg = transact(&Cmd.hdr);
if(NULL == pRspMsg)
{
/* transact already tattled */
return -1;
}
/*
* Cast to a LLRP_tSIMPINJ_ENABLE_EXTENSIONS_RESPONSE message.
*/
pRsp = (LLRP_tSIMPINJ_ENABLE_EXTENSIONS_RESPONSE *) pRspMsg;
/*
* Check the LLRPStatus parameter.
*/
if(0 != checkLLRPStatus(pRsp->pLLRPStatus, "enableImpinjExtensions"))
{
/* checkLLRPStatus already tattled */
freeMessage(pRspMsg);
return -1;
}
/*
* Done with the response message.
*/
freeMessage(pRspMsg);
/*
* Tattle progress, maybe
*/
if(g_Verbose)
{
printf("INFO: ImpinjExtension enabled\n");
}
/*
* Victory.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Send a SET_READER_CONFIG message that resets the
** reader to factory defaults.
**
** NB: The ResetToFactoryDefault semantics vary between readers.
** It might have no effect because it is optional.
**
** The message is:
**
** <SET_READER_CONFIG MessageID='101'>
** <ResetToFactoryDefault>1</ResetToFactoryDefault>
** </SET_READER_CONFIG>
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
resetConfigurationToFactoryDefaults (void)
{
LLRP_tSSET_READER_CONFIG Cmd = {
.hdr.elementHdr.pType = &LLRP_tdSET_READER_CONFIG,
.hdr.MessageID = 101,
.ResetToFactoryDefault = 1
};
LLRP_tSMessage * pRspMsg;
LLRP_tSSET_READER_CONFIG_RESPONSE *pRsp;
/*
* Send the message, expect the response of certain type
*/
pRspMsg = transact(&Cmd.hdr);
if(NULL == pRspMsg)
{
/* transact already tattled */
return -1;
}
/*
* Cast to a SET_READER_CONFIG_RESPONSE message.
*/
pRsp = (LLRP_tSSET_READER_CONFIG_RESPONSE *) pRspMsg;
/*
* Check the LLRPStatus parameter.
*/
if(0 != checkLLRPStatus(pRsp->pLLRPStatus,
"resetConfigurationToFactoryDefaults"))
{
/* checkLLRPStatus already tattled */
freeMessage(pRspMsg);
return -1;
}
/*
* Done with the response message.
*/
freeMessage(pRspMsg);
/*
* Tattle progress, maybe
*/
if(g_Verbose)
{
printf("INFO: Configuration reset to factory defaults\n");
}
/*
* Victory.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Delete all ROSpecs using DELETE_ROSPEC message
**
** Per the spec, the DELETE_ROSPEC message contains an ROSpecID
** of 0 to indicate we want all ROSpecs deleted.
**
** The message is
**
** <DELETE_ROSPEC MessageID='102'>
** <ROSpecID>0</ROSpecID>
** </DELETE_ROSPEC>
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
** IMPORANT:
** The coding of this function demonstrates best practices.
** Please see IMPORTANT comment at the top of this file.
**
*****************************************************************************/
int
deleteAllROSpecs (void)
{
LLRP_tSDELETE_ROSPEC * pCmd;
LLRP_tSMessage * pCmdMsg;
LLRP_tSMessage * pRspMsg;
LLRP_tSDELETE_ROSPEC_RESPONSE *pRsp;
/*
* Compose the command message
*/
pCmd = LLRP_DELETE_ROSPEC_construct();
pCmdMsg = &pCmd->hdr;
LLRP_Message_setMessageID(pCmdMsg, 102);
LLRP_DELETE_ROSPEC_setROSpecID(pCmd, 0); /* All */
/*
* Send the message, expect the response of certain type
*/
pRspMsg = transact(pCmdMsg);
/*
* Done with the command message
*/
freeMessage(pCmdMsg);
/*
* transact() returns NULL if something went wrong.
*/
if(NULL == pRspMsg)
{
/* transact already tattled */
return -1;
}
/*
* Cast to a DELETE_ROSPEC_RESPONSE message.
*/
pRsp = (LLRP_tSDELETE_ROSPEC_RESPONSE *) pRspMsg;
/*
* Check the LLRPStatus parameter.
*/
if(0 != checkLLRPStatus(pRsp->pLLRPStatus, "deleteAllROSpecs"))
{
/* checkLLRPStatus already tattled */
freeMessage(pRspMsg);
return -1;
}
/*
* Done with the response message.
*/
freeMessage(pRspMsg);
/*
* Tattle progress, maybe
*/
if(g_Verbose)
{
printf("INFO: All ROSpecs are deleted\n");
}
/*
* Victory.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Add our ROSpec using ADD_ROSPEC message
**
** This ROSpec waits for a START_ROSPEC message,
** then takes inventory on all antennas for 5 seconds.
**
** The tag report is generated after the ROSpec is done.
**
** This example is deliberately streamlined.
** Nothing here configures the antennas, RF, or Gen2.
** The current defaults are used. Remember we just reset
** the reader to factory defaults (above). Normally an
** application would be more precise in configuring the
** reader and in its ROSpecs.
**
** Experience suggests that typical ROSpecs are about
** double this in size.
**
** The message is
**
** <ADD_ROSPEC MessageID='201'>
** <ROSpec>
** <ROSpecID>123</ROSpecID>
** <Priority>0</Priority>
** <CurrentState>Disabled</CurrentState>
** <ROBoundarySpec>
** <ROSpecStartTrigger>
** <ROSpecStartTriggerType>Null</ROSpecStartTriggerType>
** </ROSpecStartTrigger>
** <ROSpecStopTrigger>
** <ROSpecStopTriggerType>Null</ROSpecStopTriggerType>
** <DurationTriggerValue>0</DurationTriggerValue>
** </ROSpecStopTrigger>
** </ROBoundarySpec>
** <AISpec>
** <AntennaIDs>0</AntennaIDs>
** <AISpecStopTrigger>
** <AISpecStopTriggerType>Duration</AISpecStopTriggerType>
** <DurationTrigger>5000</DurationTrigger>
** </AISpecStopTrigger>
** <InventoryParameterSpec>
** <InventoryParameterSpecID>1234</InventoryParameterSpecID>
** <ProtocolID>EPCGlobalClass1Gen2</ProtocolID>
** </InventoryParameterSpec>
** </AISpec>
** <ROReportSpec>
** <ROReportTrigger>Upon_N_Tags_Or_End_Of_ROSpec</ROReportTrigger>
** <N>0</N>
** <TagReportContentSelector>
** <EnableROSpecID>0</EnableROSpecID>
** <EnableSpecIndex>0</EnableSpecIndex>
** <EnableInventoryParameterSpecID>0</EnableInventoryParameterSpecID>
** <EnableAntennaID>0</EnableAntennaID>
** <EnableChannelIndex>0</EnableChannelIndex>
** <EnablePeakRSSI>0</EnablePeakRSSI>
** <EnableFirstSeenTimestamp>0</EnableFirstSeenTimestamp>
** <EnableLastSeenTimestamp>0</EnableLastSeenTimestamp>
** <EnableTagSeenCount>0</EnableTagSeenCount>
** <EnableAccessSpecID>0</EnableAccessSpecID>
** </TagReportContentSelector>
** </ROReportSpec>
** </ROSpec>
** </ADD_ROSPEC>
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
addROSpec (void)
{
LLRP_tSROSpecStartTrigger ROSpecStartTrigger = {
.hdr.elementHdr.pType = &LLRP_tdROSpecStartTrigger,
.eROSpecStartTriggerType = LLRP_ROSpecStartTriggerType_Null,
};
LLRP_tSROSpecStopTrigger ROSpecStopTrigger = {
.hdr.elementHdr.pType = &LLRP_tdROSpecStopTrigger,
.eROSpecStopTriggerType = LLRP_ROSpecStopTriggerType_Null,
.DurationTriggerValue = 0 /* n/a */
};
LLRP_tSROBoundarySpec ROBoundarySpec = {
.hdr.elementHdr.pType = &LLRP_tdROBoundarySpec,
.pROSpecStartTrigger = &ROSpecStartTrigger,
.pROSpecStopTrigger = &ROSpecStopTrigger,
};
llrp_u16_t AntennaIDs[1] = { 0 }; /* All */
LLRP_tSAISpecStopTrigger AISpecStopTrigger = {
.hdr.elementHdr.pType = &LLRP_tdAISpecStopTrigger,
.eAISpecStopTriggerType = LLRP_AISpecStopTriggerType_Duration,
.DurationTrigger = 5000,
};
LLRP_tSInventoryParameterSpec InventoryParameterSpec = {
.hdr.elementHdr.pType = &LLRP_tdInventoryParameterSpec,
.InventoryParameterSpecID = 1234,
.eProtocolID = LLRP_AirProtocols_EPCGlobalClass1Gen2,
};
LLRP_tSAISpec AISpec = {
.hdr.elementHdr.pType = &LLRP_tdAISpec,
.AntennaIDs = {
.nValue = 1,
.pValue = AntennaIDs
},
.pAISpecStopTrigger = &AISpecStopTrigger,
.listInventoryParameterSpec = &InventoryParameterSpec,
};
LLRP_tSTagReportContentSelector TagReportContentSelector = {
.hdr.elementHdr.pType = &LLRP_tdTagReportContentSelector,
.EnableROSpecID = 0,
.EnableSpecIndex = 0,
.EnableInventoryParameterSpecID = 0,
.EnableAntennaID = 0,
.EnableChannelIndex = 0,
.EnablePeakRSSI = 0,
.EnableFirstSeenTimestamp = 0,
.EnableLastSeenTimestamp = 0,
.EnableTagSeenCount = 0,
.EnableAccessSpecID = 0,
};
LLRP_tSROReportSpec ROReportSpec = {
.hdr.elementHdr.pType = &LLRP_tdROReportSpec,
.eROReportTrigger =
LLRP_ROReportTriggerType_Upon_N_Tags_Or_End_Of_ROSpec,
.N = 0,
.pTagReportContentSelector = &TagReportContentSelector,
};
LLRP_tSROSpec ROSpec = {
.hdr.elementHdr.pType = &LLRP_tdROSpec,
.ROSpecID = 123,
.Priority = 0,
.eCurrentState = LLRP_ROSpecState_Disabled,
.pROBoundarySpec = &ROBoundarySpec,
.listSpecParameter = &AISpec.hdr,
.pROReportSpec = &ROReportSpec,
};
LLRP_tSADD_ROSPEC Cmd = {
.hdr.elementHdr.pType = &LLRP_tdADD_ROSPEC,
.hdr.MessageID = 201,
.pROSpec = &ROSpec,
};
LLRP_tSMessage * pRspMsg;
LLRP_tSADD_ROSPEC_RESPONSE *pRsp;
/*
* Send the message, expect the response of certain type
*/
pRspMsg = transact(&Cmd.hdr);
if(NULL == pRspMsg)
{
/* transact already tattled */
return -1;
}
/*
* Cast to a ADD_ROSPEC_RESPONSE message.
*/
pRsp = (LLRP_tSADD_ROSPEC_RESPONSE *) pRspMsg;
/*
* Check the LLRPStatus parameter.
*/
if(0 != checkLLRPStatus(pRsp->pLLRPStatus, "addROSpec"))
{
/* checkLLRPStatus already tattled */
freeMessage(pRspMsg);
return -1;
}
/*
* Done with the response message.
*/
freeMessage(pRspMsg);
/*
* Tattle progress, maybe
*/
if(g_Verbose)
{
printf("INFO: ROSpec added\n");
}
/*
* Victory.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Enable our ROSpec using ENABLE_ROSPEC message
**
** Enable the ROSpec that was added above.
**
** The message we send is:
** <ENABLE_ROSPEC MessageID='202'>
** <ROSpecID>123</ROSpecID>
** </ENABLE_ROSPEC>
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
enableROSpec (void)
{
LLRP_tSENABLE_ROSPEC Cmd = {
.hdr.elementHdr.pType = &LLRP_tdENABLE_ROSPEC,
.hdr.MessageID = 202,
.ROSpecID = 123,
};
LLRP_tSMessage * pRspMsg;
LLRP_tSENABLE_ROSPEC_RESPONSE *pRsp;
/*
* Send the message, expect the response of certain type
*/
pRspMsg = transact(&Cmd.hdr);
if(NULL == pRspMsg)
{
/* transact already tattled */
return -1;
}
/*
* Cast to a ENABLE_ROSPEC_RESPONSE message.
*/
pRsp = (LLRP_tSENABLE_ROSPEC_RESPONSE *) pRspMsg;
/*
* Check the LLRPStatus parameter.
*/
if(0 != checkLLRPStatus(pRsp->pLLRPStatus, "enableROSpec"))
{
/* checkLLRPStatus already tattled */
freeMessage(pRspMsg);
return -1;
}
/*
* Done with the response message.
*/
freeMessage(pRspMsg);
/*
* Tattle progress, maybe
*/
if(g_Verbose)
{
printf("INFO: ROSpec enabled\n");
}
/*
* Victory.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Start our ROSpec using START_ROSPEC message
**
** Start the ROSpec that was added above.
**
** The message we send is:
** <START_ROSPEC MessageID='202'>
** <ROSpecID>123</ROSpecID>
** </START_ROSPEC>
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
startROSpec (void)
{
LLRP_tSSTART_ROSPEC Cmd = {
.hdr.elementHdr.pType = &LLRP_tdSTART_ROSPEC,
.hdr.MessageID = 202,
.ROSpecID = 123,
};
LLRP_tSMessage * pRspMsg;
LLRP_tSSTART_ROSPEC_RESPONSE *pRsp;
/*
* Send the message, expect the response of certain type
*/
pRspMsg = transact(&Cmd.hdr);
if(NULL == pRspMsg)
{
/* transact already tattled */
return -1;
}
/*
* Cast to a START_ROSPEC_RESPONSE message.
*/
pRsp = (LLRP_tSSTART_ROSPEC_RESPONSE *) pRspMsg;
/*
* Check the LLRPStatus parameter.
*/
if(0 != checkLLRPStatus(pRsp->pLLRPStatus, "startROSpec"))
{
/* checkLLRPStatus already tattled */
freeMessage(pRspMsg);
return -1;
}
/*
* Done with the response message.
*/
freeMessage(pRspMsg);
/*
* Tattle progress, maybe
*/
if(g_Verbose)
{
printf("INFO: ROSpec started\n");
}
/*
* Victory.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Receive and print the RO_ACCESS_REPORT
**
** Receive messages until an RO_ACCESS_REPORT is received.
** Time limit is 7 seconds. We expect a report within 5 seconds.
**
** This shows how to determine the type of a received message.
**
** @return ==0 Everything OK
** !=0 Something went wrong
**
*****************************************************************************/
int
awaitAndPrintReport (void)
{
int bDone = 0;
int retVal = 0;
/*
* Keep receiving messages until done or until
* something bad happens.
*/
while(!bDone)
{
LLRP_tSMessage * pMessage;
const LLRP_tSTypeDescriptor *pType;
/*
* Wait up to 7 seconds for a message. The report
* should occur within 5 seconds.
*/
pMessage = recvMessage(7000);
if(NULL == pMessage)
{
/*
* Did not receive a message within a reasonable
* amount of time. recvMessage() already tattled
*/
retVal = -2;
bDone = 1;
continue;
}
/*
* What happens depends on what kind of message
* received. Use the type label (pType) to
* discriminate message types.
*/
pType = pMessage->elementHdr.pType;
/*
* Is it a tag report? If so, print it out.
*/
if(&LLRP_tdRO_ACCESS_REPORT == pType)
{
LLRP_tSRO_ACCESS_REPORT *pNtf;
pNtf = (LLRP_tSRO_ACCESS_REPORT *) pMessage;
printTagReportData(pNtf);
bDone = 1;
retVal = 0;
}
/*
* Is it a reader event? This example only recognizes
* AntennaEvents.
*/
else if(&LLRP_tdREADER_EVENT_NOTIFICATION == pType)
{
LLRP_tSREADER_EVENT_NOTIFICATION *pNtf;
LLRP_tSReaderEventNotificationData *pNtfData;
pNtf = (LLRP_tSREADER_EVENT_NOTIFICATION *) pMessage;
pNtfData =
LLRP_READER_EVENT_NOTIFICATION_getReaderEventNotificationData(
pNtf);
if(NULL != pNtfData)
{
handleReaderEventNotification(pNtfData);
}
else
{
/*
* This should never happen. Using continue
* to keep indent depth down.
*/
printf("WARNING: READER_EVENT_NOTIFICATION without data\n");
}
}
/*
* Hmmm. Something unexpected. Just tattle and keep going.
*/
else
{
printf("WARNING: Ignored unexpected message during monitor: %s\n",
pType->pName);
}
/*
* Done with the received message
*/
freeMessage(pMessage);
}
return retVal;
}
/**
*****************************************************************************
**
** @brief Helper routine to print a tag report
**
** The report is printed in list order, which is arbitrary.
**
** TODO: It would be cool to sort the list by EPC and antenna,
** then print it.
**
** @return void
**
*****************************************************************************/
void
printTagReportData (
LLRP_tSRO_ACCESS_REPORT * pRO_ACCESS_REPORT)
{
LLRP_tSTagReportData * pTagReportData;
unsigned int nEntry = 0;
/*
* Loop through and count the number of entries
*/
for(
pTagReportData = pRO_ACCESS_REPORT->listTagReportData;
NULL != pTagReportData;
pTagReportData = (LLRP_tSTagReportData *)
pTagReportData->hdr.pNextSubParameter)
{
nEntry++;
}
printf("INFO: %u tag report entries\n", nEntry);
/*
* Loop through again and print each entry.
*/
for(
pTagReportData = pRO_ACCESS_REPORT->listTagReportData;
NULL != pTagReportData;
pTagReportData = (LLRP_tSTagReportData *)
pTagReportData->hdr.pNextSubParameter)
{
printOneTagReportData(pTagReportData);
}
}
/**
*****************************************************************************
**
** @brief Helper routine to print one tag report entry on one line
**
** @return void
**
*****************************************************************************/
void
printOneTagReportData (
LLRP_tSTagReportData * pTagReportData)
{
const LLRP_tSTypeDescriptor *pType;
char aBuf[100*1024];
/*
* Print the EPC. It could be an 96-bit EPC_96 parameter
* or an variable length EPCData parameter.
*/
if(NULL != pTagReportData->pEPCParameter)
{
char * p = aBuf;
llrp_u8_t * pValue = NULL;
unsigned int n, i;
pType = pTagReportData->pEPCParameter->elementHdr.pType;
if(&LLRP_tdEPC_96 == pType)
{
LLRP_tSEPC_96 * pE96;
pE96 = (LLRP_tSEPC_96 *) pTagReportData->pEPCParameter;
pValue = pE96->EPC.aValue;
n = 12u;
}
else if(&LLRP_tdEPCData == pType)
{
LLRP_tSEPCData *pEPCData;
pEPCData = (LLRP_tSEPCData *) pTagReportData->pEPCParameter;
pValue = pEPCData->EPC.pValue;
n = (pEPCData->EPC.nBit + 7u) / 8u;
}
if(NULL != pValue)
{
for(i = 0; i < n; i++)
{
if(0 < i && i%2 == 0)
{
*p++ = '-';
}
sprintf(p, "%02X", pValue[i]);
while(*p) p++;
}
}
else
{
strcpy(aBuf, "---unknown-epc-data-type---");
}
}
else
{
strcpy(aBuf, "---missing-epc-data---");
}
printf("%-32s", aBuf);
/*
* End of line
*/
printf("\n");
}
/**
*****************************************************************************
**
** @brief Handle a ReaderEventNotification
**
** Handle the payload of a READER_EVENT_NOTIFICATION message.
** This routine simply dispatches to handlers of specific
** event types.
**
** @return void
**
*****************************************************************************/
void
handleReaderEventNotification (
LLRP_tSReaderEventNotificationData *pNtfData)
{
LLRP_tSAntennaEvent * pAntennaEvent;
LLRP_tSReaderExceptionEvent *pReaderExceptionEvent;
int nReported = 0;
pAntennaEvent =
LLRP_ReaderEventNotificationData_getAntennaEvent(pNtfData);
if(NULL != pAntennaEvent)
{
handleAntennaEvent(pAntennaEvent);
nReported++;
}
pReaderExceptionEvent =
LLRP_ReaderEventNotificationData_getReaderExceptionEvent(pNtfData);
if(NULL != pReaderExceptionEvent)
{
handleReaderExceptionEvent(pReaderExceptionEvent);
nReported++;
}
/*
* Similarly handle other events here:
* HoppingEvent
* GPIEvent
* ROSpecEvent
* ReportBufferLevelWarningEvent
* ReportBufferOverflowErrorEvent
* RFSurveyEvent
* AISpecEvent
* ConnectionAttemptEvent
* ConnectionCloseEvent
* Custom
*/
if(0 == nReported)
{
printf("NOTICE: Unexpected (unhandled) ReaderEvent\n");
}
}
/**
*****************************************************************************
**
** @brief Handle an AntennaEvent
**
** An antenna was disconnected or (re)connected. Tattle.
**
** @return void
**
*****************************************************************************/
void
handleAntennaEvent (
LLRP_tSAntennaEvent * pAntennaEvent)
{
LLRP_tEAntennaEventType eEventType;
llrp_u16_t AntennaID;
char * pStateStr;
eEventType = LLRP_AntennaEvent_getEventType(pAntennaEvent);
AntennaID = LLRP_AntennaEvent_getAntennaID(pAntennaEvent);
switch(eEventType)
{
case LLRP_AntennaEventType_Antenna_Disconnected:
pStateStr = "disconnected";
break;
case LLRP_AntennaEventType_Antenna_Connected:
pStateStr = "connected";
break;
default:
pStateStr = "?unknown-event?";
break;
}
printf("NOTICE: Antenna %d is %s\n", AntennaID, pStateStr);
}
/**
*****************************************************************************
**
** @brief Handle a ReaderExceptionEvent
**
** Something has gone wrong. There are lots of details but
** all this does is print the message, if one.
**
** @return void
**
*****************************************************************************/
void
handleReaderExceptionEvent (
LLRP_tSReaderExceptionEvent * pReaderExceptionEvent)
{
llrp_utf8v_t Message;
Message = LLRP_ReaderExceptionEvent_getMessage(pReaderExceptionEvent);
if(0 < Message.nValue && NULL != Message.pValue)
{
printf("NOTICE: ReaderException '%.*s'\n",
Message.nValue, Message.pValue);
}
else
{
printf("NOTICE: ReaderException but no message\n");
}
}
/**
*****************************************************************************
**
** @brief Helper routine to check an LLRPStatus parameter
** and tattle on errors
**
** Helper routine to interpret the LLRPStatus subparameter
** that is in all responses. It tattles on an error, if one,
** and tries to safely provide details.
**
** This simplifies the code, above, for common check/tattle
** sequences.
**
** @return ==0 Everything OK
** !=0 Something went wrong, already tattled
**
*****************************************************************************/
int
checkLLRPStatus (
LLRP_tSLLRPStatus * pLLRPStatus,
char * pWhatStr)
{
/*
* The LLRPStatus parameter is mandatory in all responses.
* If it is missing there should have been a decode error.
* This just makes sure (remember, this program is a
* diagnostic and suppose to catch LTKC mistakes).
*/
if(NULL == pLLRPStatus)
{
printf("ERROR: %s missing LLRP status\n", pWhatStr);
return -1;
}
/*
* Make sure the status is M_Success.
* If it isn't, print the error string if one.
* This does not try to pretty-print the status
* code. To get that, run this program with -vv
* and examine the XML output.
*/
if(LLRP_StatusCode_M_Success != pLLRPStatus->eStatusCode)
{
if(0 == pLLRPStatus->ErrorDescription.nValue)
{
printf("ERROR: %s failed, no error description given\n",
pWhatStr);
}
else
{
printf("ERROR: %s failed, %.*s\n",
pWhatStr,
pLLRPStatus->ErrorDescription.nValue,
pLLRPStatus->ErrorDescription.pValue);
}
return -2;
}
/*
* Victory. Everything is fine.
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Wrapper routine to do an LLRP transaction
**
** Wrapper to transact a request/resposne.
** - Print the outbound message in XML if verbose level is at least 2
** - Send it using the LLRP_Conn_transact()
** - LLRP_Conn_transact() receives the response or recognizes an error
** - Tattle on errors, if any
** - Print the received message in XML if verbose level is at least 2
** - If the response is ERROR_MESSAGE, the request was sufficiently
** misunderstood that the reader could not send a proper reply.
** Deem this an error, free the message.
**
** The message returned resides in allocated memory. It is the
** caller's obligtation to free it.
**
** @return ==NULL Something went wrong, already tattled
** !=NULL Pointer to a message
**
*****************************************************************************/
LLRP_tSMessage *
transact (
LLRP_tSMessage * pSendMsg)
{
LLRP_tSConnection * pConn = g_pConnectionToReader;
LLRP_tSMessage * pRspMsg;
/*
* Print the XML text for the outbound message if
* verbosity is 2 or higher.
*/
if(1 < g_Verbose)
{
printf("\n===================================\n");
printf("INFO: Transact sending\n");
printXMLMessage(pSendMsg);
}
/*
* Send the message, expect the response of certain type.
* If LLRP_Conn_transact() returns NULL then there was
* an error. In that case we try to print the error details.
*/
pRspMsg = LLRP_Conn_transact(pConn, pSendMsg, 5000);
if(NULL == pRspMsg)
{
const LLRP_tSErrorDetails *pError = LLRP_Conn_getTransactError(pConn);
printf("ERROR: %s transact failed, %s\n",
pSendMsg->elementHdr.pType->pName,
pError->pWhatStr ? pError->pWhatStr : "no reason given");
if(NULL != pError->pRefType)
{
printf("ERROR: ... reference type %s\n",
pError->pRefType->pName);
}
if(NULL != pError->pRefField)
{
printf("ERROR: ... reference field %s\n",
pError->pRefField->pName);
}
return NULL;
}
/*
* Print the XML text for the inbound message if
* verbosity is 2 or higher.
*/
if(1 < g_Verbose)
{
printf("\n- - - - - - - - - - - - - - - - - -\n");
printf("INFO: Transact received response\n");
printXMLMessage(pRspMsg);
}
/*
* If it is an ERROR_MESSAGE (response from reader
* when it can't understand the request), tattle
* and declare defeat.
*/
if(&LLRP_tdERROR_MESSAGE == pRspMsg->elementHdr.pType)
{
const LLRP_tSTypeDescriptor *pResponseType;
pResponseType = pSendMsg->elementHdr.pType->pResponseType;
printf("ERROR: Received ERROR_MESSAGE instead of %s\n",
pResponseType->pName);
freeMessage(pRspMsg);
pRspMsg = NULL;
}
return pRspMsg;
}
/**
*****************************************************************************
**
** @brief Wrapper routine to receive a message
**
** This can receive notifications as well as responses.
** - Recv a message using the LLRP_Conn_recvMessage()
** - Tattle on errors, if any
** - Print the message in XML if verbose level is at least 2
**
** The message returned resides in allocated memory. It is the
** caller's obligtation to free it.
**
** @param[in] nMaxMS -1 => block indefinitely
** 0 => just peek at input queue and
** socket queue, return immediately
** no matter what
** >0 => ms to await complete frame
**
** @return ==NULL Something went wrong, already tattled
** !=NULL Pointer to a message
**
*****************************************************************************/
LLRP_tSMessage *
recvMessage (
int nMaxMS)
{
LLRP_tSConnection * pConn = g_pConnectionToReader;
LLRP_tSMessage * pMessage;
/*
* Receive the message subject to a time limit
*/
pMessage = LLRP_Conn_recvMessage(pConn, nMaxMS);
/*
* If LLRP_Conn_recvMessage() returns NULL then there was
* an error. In that case we try to print the error details.
*/
if(NULL == pMessage)
{
const LLRP_tSErrorDetails *pError = LLRP_Conn_getRecvError(pConn);
printf("ERROR: recvMessage failed, %s\n",
pError->pWhatStr ? pError->pWhatStr : "no reason given");
if(NULL != pError->pRefType)
{
printf("ERROR: ... reference type %s\n",
pError->pRefType->pName);
}
if(NULL != pError->pRefField)
{
printf("ERROR: ... reference field %s\n",
pError->pRefField->pName);
}
return NULL;
}
/*
* Print the XML text for the inbound message if
* verbosity is 2 or higher.
*/
if(1 < g_Verbose)
{
printf("\n===================================\n");
printf("INFO: Message received\n");
printXMLMessage(pMessage);
}
return pMessage;
}
/**
*****************************************************************************
**
** @brief Wrapper routine to send a message
**
** Wrapper to send a message.
** - Print the message in XML if verbose level is at least 2
** - Send it using the LLRP_Conn_sendMessage()
** - Tattle on errors, if any
**
** @param[in] pSendMsg Pointer to message to send
**
** @return ==0 Everything OK
** !=0 Something went wrong, already tattled
**
*****************************************************************************/
int
sendMessage (
LLRP_tSMessage * pSendMsg)
{
LLRP_tSConnection * pConn = g_pConnectionToReader;
/*
* Print the XML text for the outbound message if
* verbosity is 2 or higher.
*/
if(1 < g_Verbose)
{
printf("\n===================================\n");
printf("INFO: Sending\n");
printXMLMessage(pSendMsg);
}
/*
* If LLRP_Conn_sendMessage() returns other than LLRP_RC_OK
* then there was an error. In that case we try to print
* the error details.
*/
if(LLRP_RC_OK != LLRP_Conn_sendMessage(pConn, pSendMsg))
{
const LLRP_tSErrorDetails *pError = LLRP_Conn_getSendError(pConn);
printf("ERROR: %s sendMessage failed, %s\n",
pSendMsg->elementHdr.pType->pName,
pError->pWhatStr ? pError->pWhatStr : "no reason given");
if(NULL != pError->pRefType)
{
printf("ERROR: ... reference type %s\n",
pError->pRefType->pName);
}
if(NULL != pError->pRefField)
{
printf("ERROR: ... reference field %s\n",
pError->pRefField->pName);
}
return -1;
}
/*
* Victory
*/
return 0;
}
/**
*****************************************************************************
**
** @brief Wrapper to free a message.
**
** All it does is cast pMessage and let
** LLRP_Element_destruct() do the work.
**
** @param[in] pMessage Pointer to message to destruct
**
** @return void
**
*****************************************************************************/
void
freeMessage (
LLRP_tSMessage * pMessage)
{
LLRP_Element_destruct(&pMessage->elementHdr);
}
/**
*****************************************************************************
**
** @brief Helper to print a message as XML text
**
** Print a LLRP message as XML text
**
** @param[in] pMessage Pointer to message to print
**
** @return void
**
*****************************************************************************/
void
printXMLMessage (
LLRP_tSMessage * pMessage)
{
char aBuf[100*1024];
/*
* Convert the message to an XML string.
* This fills the buffer with either the XML string
* or an error message. The return value could
* be checked.
*/
LLRP_toXMLString(&pMessage->elementHdr, aBuf, sizeof aBuf);
/*
* Print the XML Text to the standard output.
*/
printf("%s", aBuf);
}