M5Unit-RFID 0.2.0 git rev:79c1939
Loading...
Searching...
No Matches
m5::uhf::UHFLayer Class Reference

EPC Gen2 semantics layer for UHF-RFID reader units. More...

#include <uhf_layer.hpp>

Public Member Functions

 UHFLayer (m5::unit::UHFRFIDComponent &u)
 
 UHFLayer (const UHFLayer &)=delete
 
UHFLayeroperator= (const UHFLayer &)=delete
 
Detection
bool detect (std::vector< Tag > &tags, const uint32_t timeout_ms=1000U)
 Detect tags.
 
bool detect (Tag &tag, const uint32_t timeout_ms=500U)
 Detect one tag.
 
Target tag
bool select (const Tag &tag, const uint32_t access_password=0, const bool verify=true)
 Point subsequent tag operations at one tag.
 
bool select (const Epc &epc, const uint32_t access_password=0, const bool verify=true)
 Select by EPC, for a tag the caller holds nothing else of.
 
bool select (const Tid &tid, const uint32_t access_password=0, const bool verify=true)
 Select by TID.
 
bool deselect ()
 Stop addressing a single tag, and put polling back the way select() found it.
 
bool isSelected () const
 Is a tag being addressed?
 
const TagselectedTag () const
 The tag currently being addressed.
 
bool identify (Tag &tag)
 Read the selected tag's TID and fill in what it says about the tag.
 
Dump
bool dump ()
 Dump every memory bank of the selected tag.
 
bool dump (const Bank bank)
 Dump one memory bank.
 
bool dump (const Bank bank, const uint16_t word_address, const uint16_t words)
 Dump part of one memory bank.
 
Tag memory, addressed to the selected tag
Result readBank (std::vector< uint8_t > &out, const Bank bank, const uint16_t word_address, const uint16_t word_count)
 Read a memory bank.
 
Result readBank (uint8_t *out, uint16_t &out_len, const Bank bank, const uint16_t word_address, const uint16_t word_count)
 Read a memory bank into a buffer of the caller's own.
 
Result writeBank (const Bank bank, const uint16_t word_address, const uint8_t *data, const uint16_t data_len)
 Write a memory bank.
 
Result lock (const std::vector< LockSetting > &settings, const bool allow_permanent=false)
 Change the lock state of memory banks and passwords.
 
Result kill (const Tag &tag, const uint32_t kill_password)
 Kill a tag permanently.
 
Result readBlockPermalock (std::vector< uint8_t > &mask, const Bank bank, const uint16_t block_pointer=0, const uint8_t block_range=1)
 Read which blocks of a bank the selected tag has permanently locked.
 
Result blockPermalock (const Bank bank, const uint8_t *mask, const size_t mask_len, const bool allow_permanent, const uint16_t block_pointer=0, const uint8_t block_range=1)
 Permanently lock blocks of a bank on the selected tag.
 
Result readQTParameters (QTParameters &qt)
 Read the QT control word of the selected tag.
 
Result writeQTParameters (const QTParameters &qt, const bool persistent=false)
 Write the QT control word of the selected tag.
 
Result readNxpConfigWord (uint16_t &config)
 Read the Config-Word of an NXP UCODE G2X.
 
Result toggleNxpConfigWord (uint16_t &config, const uint16_t toggle)
 Invert bits of the Config-Word of an NXP UCODE G2X.
 
Result writeNxpEAS (const bool enable)
 Set or clear the Product Status Flag of an NXP UCODE G2X.
 
bool nxpEASAlarm (std::vector< uint8_t > &alarm)
 Ask the field whether any tag has its Product Status Flag asserted.
 
Result nxpReadProtect (const bool protect)
 Turn the read protection of an NXP UCODE G2X on or off.
 

Detailed Description

EPC Gen2 semantics layer for UHF-RFID reader units.

Holds semantics defined by the EPC Gen2 standard, which stay the same across reader chips. The frame level belongs to the unit class.

Warning
A tag's write range is shorter than its read range: writing takes more power, and these memories erase before they program, of which erasing is the cheaper half. A tag well within reading distance can therefore have a word erased and not programmed, leaving it neither as it was nor as it was asked to be. A command that goes unanswered is not a command that did nothing

Member Function Documentation

◆ blockPermalock()

Result m5::uhf::UHFLayer::blockPermalock ( const Bank bank,
const uint8_t * mask,
const size_t mask_len,
const bool allow_permanent,
const uint16_t block_pointer = 0,
const uint8_t block_range = 1 )

Permanently lock blocks of a bank on the selected tag.

Parameters
bankBank the blocks are in
maskOne bit per block, the first block in the most significant bit. A bit set locks that block; a bit clear leaves it as it is
mask_lenIts length in bytes, which is twice block_range
allow_permanentPermit the operation, which cannot be undone
block_pointerFirst block the mask covers, in units of sixteen
block_rangeWords of mask, each covering sixteen blocks
Returns
Nothing on success, or why it did not go through
Warning
A locked block is unwritable for the life of the tag. There is no way back, which is why this does nothing unless allow_permanent says otherwise

◆ detect() [1/2]

bool m5::uhf::UHFLayer::detect ( std::vector< Tag > & tags,
const uint32_t timeout_ms = 1000U )

Detect tags.

Parameters
[out]tagsDetected tags, deduplicated by EPC
timeout_msHow long to keep looking, in milliseconds
Returns
True if at least one tag was detected

The timeout is how long the field is watched, not a deadline: this always looks for the whole of it, because a tag that answers rarely is only found by looking again. Shortening it returns sooner and finds fewer tags

Note
Starts polling internally and restores the previous polling state on return

◆ detect() [2/2]

bool m5::uhf::UHFLayer::detect ( Tag & tag,
const uint32_t timeout_ms = 500U )

Detect one tag.

Parameters
[out]tagThe first tag that answers
timeout_msHow long to wait before giving up, in milliseconds
Returns
True if a tag was detected

Returns as soon as any tag answers rather than waiting the timeout out, so it costs one inventory round instead of the whole window. Unlike the form taking a vector the timeout is a deadline rather than a duration, and the default covers the longest a tag was measured to take to answer

Note
Which tag answers first is not something the caller decides: with several in the field this is whichever one won that round. Anything that goes on to address a tag wants to know there is only one of them, which the form taking a vector shows and this one cannot
Starts polling internally and restores the previous polling state on return

◆ dump() [1/3]

bool m5::uhf::UHFLayer::dump ( )

Dump every memory bank of the selected tag.

Returns
True when every bank was read

Prints the Reserved, EPC, TID and User banks. How much of each there is comes from the tag where it says so and from what its chip is known to hold where it does not; a bank whose size is not known either way is skipped and said to be so

Precondition
A tag must have been selected

◆ dump() [2/3]

bool m5::uhf::UHFLayer::dump ( const Bank bank)

Dump one memory bank.

Parameters
bankMemory bank
Returns
True if successful
Precondition
A tag must have been selected

◆ dump() [3/3]

bool m5::uhf::UHFLayer::dump ( const Bank bank,
const uint16_t word_address,
const uint16_t words )

Dump part of one memory bank.

Parameters
bankMemory bank
word_addressStart address in 16-bit words
wordsNumber of 16-bit words
Returns
True if successful
Precondition
A tag must have been selected

◆ identify()

bool m5::uhf::UHFLayer::identify ( Tag & tag)

Read the selected tag's TID and fill in what it says about the tag.

Parameters
[out]tagThe selected tag, with the identification half filled in
Returns
True if successful

Reads the two fixed TID words first, then the XTID if the tag carries one, since the header is what says how long the rest is. Chip, vendor, serial length and memory sizes all come out of that (TDS 16.1 and 16.2)

◆ kill()

Result m5::uhf::UHFLayer::kill ( const Tag & tag,
const uint32_t kill_password )

Kill a tag permanently.

Parameters
tagTag to kill, which must be the one selected
kill_passwordKill password; a tag whose kill password is zero refuses to be killed
Returns
Nothing on success, or why it did not go through
Warning
The tag stops answering for good. There is no way to revive it
Impinj M700 and M800 series chips hold one 32-bit password that answers at both the access and the kill address. Giving such a tag an access password gives it the same kill password, so a tag that was only meant to be protected becomes one that can be killed

◆ lock()

Result m5::uhf::UHFLayer::lock ( const std::vector< LockSetting > & settings,
const bool allow_permanent = false )

Change the lock state of memory banks and passwords.

Parameters
settingsWhat to change; anything left out keeps the state it has
allow_permanentPermit PermanentOpen and PermanentLock, neither of which can be undone
Returns
Nothing on success, or why it did not go through
Warning
PermanentLock leaves an area unwritable for the life of the tag and PermanentOpen leaves it impossible to ever lock. There is no way back from either
Impinj M700 and M800 series chips hold one 32-bit password that answers at both the access and the kill address, so locking one locks the other. They refuse a Lock that asks for anything else, and the two have to be given the same setting

◆ nxpEASAlarm()

bool m5::uhf::UHFLayer::nxpEASAlarm ( std::vector< uint8_t > & alarm)

Ask the field whether any tag has its Product Status Flag asserted.

Parameters
[out]alarmCode the tag backscattered, or empty when nothing answered
Returns
True when the question was put and an answer came back
Note
This asks the field rather than one tag, so it needs no selection. Nothing being flagged is an answer and not a failure, which is why an empty code is reported apart from whether the question could be asked at all

◆ nxpReadProtect()

Result m5::uhf::UHFLayer::nxpReadProtect ( const bool protect)

Turn the read protection of an NXP UCODE G2X on or off.

Parameters
protectTrue to protect, false to put it back
Returns
Nothing on success, or why it did not go through

Protected memory reads back as zeroes rather than falling silent, so the tag goes on answering an inventory round and can be addressed again to undo this

Warning
The EPC and the TID are protected together, so every protected tag reads back the same all-zero EPC and they can no longer be told apart. Undo this with one tag in the field
A tag whose access password is zero ignores this command outright. Give it one first

◆ readBank() [1/2]

Result m5::uhf::UHFLayer::readBank ( std::vector< uint8_t > & out,
const Bank bank,
const uint16_t word_address,
const uint16_t word_count )

Read a memory bank.

Parameters
[out]outBytes read
bankMemory bank
word_addressStart address in 16-bit words
word_countNumber of 16-bit words to read
Returns
Nothing on success, or why it did not go through
Note
Addresses and lengths count words because EPC Gen2 lays tag memory out in 16-bit words; a byte address off a word boundary means nothing to a tag

◆ readBank() [2/2]

Result m5::uhf::UHFLayer::readBank ( uint8_t * out,
uint16_t & out_len,
const Bank bank,
const uint16_t word_address,
const uint16_t word_count )

Read a memory bank into a buffer of the caller's own.

Parameters
[out]outBuffer, which has to hold word_count words
[in,out]out_lenIts size in bytes going in, and how many bytes arrived coming out
bankMemory bank
word_addressStart address in 16-bit words
word_countNumber of 16-bit words to read
Returns
Nothing on success, or why it did not go through
Note
A tag can answer with less than was asked for, which is what out_len reports. A buffer too small for word_count words is refused rather than filled part way

◆ readBlockPermalock()

Result m5::uhf::UHFLayer::readBlockPermalock ( std::vector< uint8_t > & mask,
const Bank bank,
const uint16_t block_pointer = 0,
const uint8_t block_range = 1 )

Read which blocks of a bank the selected tag has permanently locked.

Parameters
[out]maskOne bit per block, the first block in the most significant bit
bankBank the blocks are in
block_pointerFirst block the mask covers, in units of sixteen
block_rangeWords of mask to ask for, each covering sixteen blocks
Returns
Nothing on success, or why it did not go through
Note
How large a block is, is the chip's to decide: an Impinj Monza 4QT divides its user memory into four of 128 bits. chipPermalockBlockBits() is what says so where it is known
Gen2 leaves BlockPermalock optional. A tag without it answers with a failure

◆ readNxpConfigWord()

Result m5::uhf::UHFLayer::readNxpConfigWord ( uint16_t & config)

Read the Config-Word of an NXP UCODE G2X.

Parameters
[out]configWord the tag holds
Returns
Nothing on success, or why it did not go through

The word carries the read protection, the Product Status Flag and the range reduction among other things. Which bit is which is in the chip's own datasheet

◆ readQTParameters()

Result m5::uhf::UHFLayer::readQTParameters ( QTParameters & qt)

Read the QT control word of the selected tag.

Parameters
[out]qtWhat the tag answered
Returns
Nothing on success, or why it did not go through
Note
Impinj Monza 4QT only. A chip the table names and that does not have the command is turned away before anything is sent; one it does not name is tried

◆ select() [1/2]

bool m5::uhf::UHFLayer::select ( const Tag & tag,
const uint32_t access_password = 0,
const bool verify = true )

Point subsequent tag operations at one tag.

Parameters
tagTarget tag, whose EPC becomes the mask
access_passwordAccess password of the tag, 0 when it has none
verifyRead one word back to confirm that the tag answers
Returns
True if successful
Note
Polling stops here and stays stopped until deselect(), because an inventory round leaves the tag flagged as already counted and it then answers nothing
Selecting only stores a mask in the reader, so with verify off this succeeds even for a tag that is not in the field and the failure surfaces at the first access
The password is not checked here: an unlocked bank answers whatever it is. A wrong one shows up as a memory-locked error the first time a locked area is touched
Warning
Impinj M700 and M800 series chips hold one 32-bit password that answers at both the access and the kill address, so an access password is a kill password as well. See kill()

◆ select() [2/2]

bool m5::uhf::UHFLayer::select ( const Tid & tid,
const uint32_t access_password = 0,
const bool verify = true )

Select by TID.

Parameters
tidTID of the target tag
access_passwordAccess password of the tag, 0 when it has none
verifyRead one word back to confirm that the tag answers
Returns
True if successful

The TID is locked at manufacture, so a mask built from it keeps working across an EPC rewrite. The tag has to have been identified first, since detection alone only ever reveals an EPC

Warning
The TID has to reach past the three words that name the chip and carry something there, and this refuses one that does not: every tag of a model holds the same bytes in those words, so a mask built from them alone would address all of them at once. An extended TID is not required, since a chip can keep a serial without announcing one
Note
Reading that far is the caller's to do. identify() stops at the two fixed words on a tag whose XTID header says there is no serial, so a TID from it will be refused here

◆ toggleNxpConfigWord()

Result m5::uhf::UHFLayer::toggleNxpConfigWord ( uint16_t & config,
const uint16_t toggle )

Invert bits of the Config-Word of an NXP UCODE G2X.

Parameters
[out]configWord the tag holds afterwards
toggleBits to invert
Returns
Nothing on success, or why it did not go through

The word is toggled rather than assigned: a one inverts the bit it stands over and a zero leaves it alone, so sending the same bits twice puts the word back. That is the command the chip offers, and calling it a write would say something else

Warning
A tag answers a toggle from the secured state alone. From the open state it hands back the word it already had and changes nothing, and says nothing about having refused (UCODE G2iM SL3S1003_1013 Rev3.7 Table 12). Reading costs nothing either way
A bit inverted by mistake changes what the tag will answer at all

◆ writeBank()

Result m5::uhf::UHFLayer::writeBank ( const Bank bank,
const uint16_t word_address,
const uint8_t * data,
const uint16_t data_len )

Write a memory bank.

Parameters
bankMemory bank
word_addressStart address in 16-bit words
dataBytes to write
data_lenTheir length, which has to be a whole number of 16-bit words
Returns
Nothing on success, or why it did not go through
Note
A bank larger than one command can carry is written in several, so the caller does not have to know how much that is. A write that fails partway leaves everything before the failure already on the tag, and says in the log how far it got
Writing the EPC bank changes the very bytes an EPC mask matches on, so a selection made from an EPC is dropped afterwards and has to be made again
Warning
A write reported as done is not proof the memory could be written: a write of the value a word already holds comes back done even on locked and permalocked memory. It is the reader that decides that and not the tag, which is why it stops happening where the reader cannot read the word back. Only a write carrying something else says anything

◆ writeNxpEAS()

Result m5::uhf::UHFLayer::writeNxpEAS ( const bool enable)

Set or clear the Product Status Flag of an NXP UCODE G2X.

Parameters
enableTrue to assert the flag, false to clear it
Returns
Nothing on success, or why it did not go through

A tag whose flag is asserted answers nxpEASAlarm()

Warning
A tag whose access password is zero ignores this command outright. Give it one first

◆ writeQTParameters()

Result m5::uhf::UHFLayer::writeQTParameters ( const QTParameters & qt,
const bool persistent = false )

Write the QT control word of the selected tag.

Parameters
qtWhat to write
persistentTrue to write it where a loss of power does not undo it
Returns
Nothing on success, or why it did not go through

Switching to the public map hides the tag's user memory, the longer half of its EPC, and all of its TID but the first two words. The tag answers with an EPC of its own kept for that purpose, so afterwards it looks like a different tag

Warning
A volatile write lasts only while the tag is powered, which is until the reader stops transmitting: the map is back to what it was before the tag is next found. Anything meant to outlast that has to be persistent
Note
Either way the tag can be switched back, so neither is a one-way door