M5Unit-RFID 0.2.0 git rev:79c1939
Loading...
Searching...
No Matches
m100_frame.hpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
3 *
4 * SPDX-License-Identifier: MIT
5 */
12#ifndef M5_UNIT_RFID_UNIT_M100_FRAME_HPP
13#define M5_UNIT_RFID_UNIT_M100_FRAME_HPP
14
15#include <cstddef>
16#include <cstdint>
17#include <vector>
18
19#include "uhf/uhf.hpp"
20
21namespace m5 {
22namespace unit {
30namespace m100 {
31
39namespace jrd {
41constexpr uint8_t FRAME_HEADER{0xBB};
43constexpr uint8_t FRAME_END{0x7E};
44} // namespace jrd
45
52inline uint8_t checksum(const uint8_t* body, const size_t len)
53{
54 uint32_t sum{};
55 for (size_t i = 0; i < len; ++i) {
56 sum += body[i];
57 }
58 return static_cast<uint8_t>(sum & 0xFF);
59}
60
62constexpr uint16_t MAX_PARAMETER_LENGTH{512};
63
75inline bool build_frame(std::vector<uint8_t>& out, const uint8_t type, const uint8_t command, const uint8_t* param,
76 const uint16_t param_len, const uint8_t header = jrd::FRAME_HEADER,
77 const uint8_t end = jrd::FRAME_END)
78{
79 if (param_len > MAX_PARAMETER_LENGTH || (param == nullptr && param_len != 0)) {
80 return false;
81 }
82
83 out.clear();
84 out.reserve(static_cast<size_t>(param_len) + 7);
85 out.push_back(header);
86 out.push_back(type);
87 out.push_back(command);
88 out.push_back(static_cast<uint8_t>(param_len >> 8));
89 out.push_back(static_cast<uint8_t>(param_len & 0xFF));
90 for (uint16_t i = 0; i < param_len; ++i) {
91 out.push_back(param[i]);
92 }
93 // The checksum covers Type through the last Parameter byte (index 1 onwards)
94 out.push_back(checksum(out.data() + 1, out.size() - 1));
95 out.push_back(end);
96 return true;
97}
98
100constexpr size_t FRAME_OVERHEAD{7};
102constexpr size_t FRAME_TYPE_OFFSET{1};
104constexpr size_t FRAME_PARAMETER_OFFSET{5};
105
107constexpr uint8_t TYPE_COMMAND{0x00};
109constexpr uint8_t TYPE_RESPONSE{0x01};
111constexpr uint8_t TYPE_NOTIFICATION{0x02};
112
117struct Frame {
118 uint8_t type{};
119 uint8_t command{};
120 std::vector<uint8_t> parameter{};
121};
122
132inline bool parse_frame(Frame& out, const uint8_t* raw, const size_t len, const uint8_t header = jrd::FRAME_HEADER,
133 const uint8_t end = jrd::FRAME_END)
134{
135 if (raw == nullptr || len < FRAME_OVERHEAD) {
136 return false;
137 }
138 if (raw[0] != header || raw[len - 1] != end) {
139 return false;
140 }
141
142 const uint16_t param_len = static_cast<uint16_t>((raw[3] << 8) | raw[4]);
143 if (param_len > MAX_PARAMETER_LENGTH || len != static_cast<size_t>(param_len) + FRAME_OVERHEAD) {
144 return false;
145 }
146 // The checksum covers Type through the last Parameter byte
147 if (raw[len - 2] != checksum(raw + FRAME_TYPE_OFFSET, len - 3)) {
148 return false;
149 }
150
151 out.type = raw[1];
152 out.command = raw[2];
153 out.parameter.assign(raw + FRAME_PARAMETER_OFFSET, raw + FRAME_PARAMETER_OFFSET + param_len);
154 return true;
155}
156
161enum class FrameExtract : uint8_t {
162 Ok,
163 NeedMore,
164};
165
181inline FrameExtract extract_frame(Frame& out, std::vector<uint8_t>& buffer, size_t& discarded, const uint8_t header,
182 const uint8_t end)
183{
184 discarded = 0;
185 for (;;) {
186 // Nothing before a header byte can be the start of a frame
187 size_t start = 0;
188 while (start < buffer.size() && buffer[start] != header) {
189 ++start;
190 }
191 if (start) {
192 discarded += start;
193 buffer.erase(buffer.begin(), buffer.begin() + static_cast<long>(start));
194 }
195 if (buffer.size() < FRAME_PARAMETER_OFFSET) {
196 return FrameExtract::NeedMore;
197 }
198 const uint16_t param_len = static_cast<uint16_t>((buffer[3] << 8) | buffer[4]);
199 const size_t total = static_cast<size_t>(param_len) + FRAME_OVERHEAD;
200 if (param_len > MAX_PARAMETER_LENGTH) {
201 // A length no frame can have says this header byte was data
202 discarded += 1;
203 buffer.erase(buffer.begin());
204 continue;
205 }
206 if (buffer.size() < total) {
207 return FrameExtract::NeedMore;
208 }
209 if (parse_frame(out, buffer.data(), total, header, end)) {
210 buffer.erase(buffer.begin(), buffer.begin() + static_cast<long>(total));
211 return FrameExtract::Ok;
212 }
213 // The checksum or the end byte disagreed, so this was not a frame either
214 discarded += 1;
215 buffer.erase(buffer.begin());
216 }
217}
218
220constexpr uint8_t COMMAND_SINGLE_POLLING{0x22};
222constexpr uint8_t COMMAND_MULTIPLE_POLLING{0x27};
223
228constexpr uint8_t COMMAND_READ_TAG_MEMORY{0x39};
229constexpr uint8_t COMMAND_WRITE_TAG_MEMORY{0x49};
230constexpr uint8_t COMMAND_KILL_TAG{0x65};
231constexpr uint8_t COMMAND_LOCK_TAG_MEMORY{0x82};
232constexpr uint8_t COMMAND_BLOCK_PERMALOCK{0xD3};
236constexpr uint8_t COMMAND_NXP_CHANGE_CONFIG{0xE0};
238constexpr uint8_t COMMAND_NXP_READ_PROTECT{0xE1};
240constexpr uint8_t COMMAND_NXP_CHANGE_EAS{0xE3};
242constexpr uint8_t COMMAND_NXP_EAS_ALARM{0xE4};
244constexpr uint8_t COMMAND_MONZA_QT{0xE5};
246constexpr uint8_t COMMAND_MONZA_QT_WRITE_ANSWER{0xE6};
249constexpr size_t TAG_NOTIFICATION_OVERHEAD{5};
250
259inline bool parse_tag_notification(m5::uhf::Tag& out, const uint8_t* param, const size_t len)
260{
261 if (param == nullptr || len <= TAG_NOTIFICATION_OVERHEAD) {
262 return false;
263 }
264
265 const size_t epc_len = len - TAG_NOTIFICATION_OVERHEAD;
266 // An EPC longer than the standard allows means the frame is not what it claims to be
267 if (!out.epc.assign(param + 3, epc_len)) {
268 return false;
269 }
270 out.rssi = static_cast<int8_t>(param[0]);
271 out.pc = static_cast<uint16_t>((param[1] << 8) | param[2]);
272 out.crc = static_cast<uint16_t>((param[3 + epc_len] << 8) | param[4 + epc_len]);
273 return true;
274}
275
277constexpr uint8_t MEMBANK_RESERVED{0x00};
278constexpr uint8_t MEMBANK_EPC{0x01};
279constexpr uint8_t MEMBANK_TID{0x02};
280constexpr uint8_t MEMBANK_USER{0x03};
281
283constexpr uint8_t SELECT_TRUNCATE_OFF{0x00};
284constexpr uint8_t SELECT_TRUNCATE_ON{0x80};
285
287constexpr uint8_t SELECT_MODE_ALWAYS{0x00};
288constexpr uint8_t SELECT_MODE_NEVER{0x01};
289constexpr uint8_t SELECT_MODE_NON_INVENTORY{0x02};
290
292constexpr size_t WRITE_MAX_WORDS{32};
294constexpr size_t SELECT_MASK_MAX_BITS{255};
295
303inline uint8_t select_parameter_byte(const uint8_t target, const uint8_t action, const uint8_t membank)
304{
305 return static_cast<uint8_t>(((target & 0x07) << 5) | ((action & 0x07) << 2) | (membank & 0x03));
306}
307
320inline bool build_select_parameter(std::vector<uint8_t>& out, const uint8_t sel_param, const uint32_t pointer_bits,
321 const uint8_t mask_length_bits, const uint8_t truncate, const uint8_t* mask,
322 const size_t mask_len)
323{
324 if ((sel_param & 0x03) == MEMBANK_RESERVED) {
325 return false;
326 }
327 if (mask_len == 0 || mask == nullptr || mask_length_bits == 0) {
328 return false;
329 }
330 // SELECT_MASK_MAX_BITS is the whole range of the length field, so the type already keeps
331 // the mask inside it; only the mask actually being that long still has to be checked
332 if ((mask_length_bits + 7U) / 8U > mask_len) {
333 return false;
334 }
335
336 out.clear();
337 out.reserve(7 + mask_len);
338 out.push_back(sel_param);
339 out.push_back(static_cast<uint8_t>(pointer_bits >> 24));
340 out.push_back(static_cast<uint8_t>(pointer_bits >> 16));
341 out.push_back(static_cast<uint8_t>(pointer_bits >> 8));
342 out.push_back(static_cast<uint8_t>(pointer_bits));
343 out.push_back(mask_length_bits);
344 out.push_back(truncate);
345 out.insert(out.end(), mask, mask + mask_len);
346 return true;
347}
348
356inline bool parse_select_parameter(m5::uhf::SelectParameter& out, const uint8_t* param, const size_t len)
357{
358 // SelParam, a four byte pointer, the mask length, the truncate flag, then the mask itself
359 constexpr size_t HEADER_LENGTH{7};
360 if (param == nullptr || len < HEADER_LENGTH) {
361 return false;
362 }
363 const size_t mask_len = len - HEADER_LENGTH;
364 if (mask_len > m5::uhf::SELECT_MASK_MAX_BYTES) {
365 return false;
366 }
367 // The mask travels as whole bytes, so the length in bits has to account for exactly the
368 // bytes that arrived (protocol 2.6.2: a mask of 96 bits comes with twelve of them). Taking
369 // a frame where the two disagree would hand the caller a length its own mask cannot back up
370 if ((static_cast<size_t>(param[5]) + 7U) / 8U != mask_len) {
371 return false;
372 }
373 out.target = static_cast<uint8_t>((param[0] >> 5) & 0x07);
374 out.action = static_cast<uint8_t>((param[0] >> 2) & 0x07);
375 out.bank = static_cast<m5::uhf::Bank>(param[0] & 0x03);
376 out.pointer_bits = (static_cast<uint32_t>(param[1]) << 24) | (static_cast<uint32_t>(param[2]) << 16) |
377 (static_cast<uint32_t>(param[3]) << 8) | param[4];
378 out.mask_length_bits = param[5];
379 out.truncate = param[6] != SELECT_TRUNCATE_OFF;
380 out.mask.fill(0);
381 for (size_t i = 0; i < mask_len; ++i) {
382 out.mask[i] = param[HEADER_LENGTH + i];
383 }
384 out.mask_size = static_cast<uint8_t>(mask_len);
385 return true;
386}
387
399inline bool build_read_tag_memory(std::vector<uint8_t>& out, const uint32_t access_password, const uint8_t membank,
400 const uint16_t word_address, const uint16_t word_count)
401{
402 if (word_count == 0) {
403 return false;
404 }
405 out.clear();
406 out.reserve(9);
407 out.push_back(static_cast<uint8_t>(access_password >> 24));
408 out.push_back(static_cast<uint8_t>(access_password >> 16));
409 out.push_back(static_cast<uint8_t>(access_password >> 8));
410 out.push_back(static_cast<uint8_t>(access_password));
411 out.push_back(membank);
412 out.push_back(static_cast<uint8_t>(word_address >> 8));
413 out.push_back(static_cast<uint8_t>(word_address));
414 out.push_back(static_cast<uint8_t>(word_count >> 8));
415 out.push_back(static_cast<uint8_t>(word_count));
416 return true;
417}
418
429inline bool build_write_tag_memory(std::vector<uint8_t>& out, const uint32_t access_password, const uint8_t membank,
430 const uint16_t word_address, const uint8_t* data, const size_t len)
431{
432 if (data == nullptr || len == 0 || (len % 2) != 0 || len / 2 > WRITE_MAX_WORDS) {
433 return false;
434 }
435 const uint16_t word_count = static_cast<uint16_t>(len / 2);
436 if (!build_read_tag_memory(out, access_password, membank, word_address, word_count)) {
437 return false;
438 }
439 out.insert(out.end(), data, data + len);
440 return true;
441}
442
450inline bool build_lock_tag(std::vector<uint8_t>& out, const uint32_t access_password, const uint32_t payload)
451{
452 if (payload > 0x000FFFFFU) {
453 return false;
454 }
455 out.clear();
456 out.reserve(7);
457 out.push_back(static_cast<uint8_t>(access_password >> 24));
458 out.push_back(static_cast<uint8_t>(access_password >> 16));
459 out.push_back(static_cast<uint8_t>(access_password >> 8));
460 out.push_back(static_cast<uint8_t>(access_password));
461 out.push_back(static_cast<uint8_t>(payload >> 16));
462 out.push_back(static_cast<uint8_t>(payload >> 8));
463 out.push_back(static_cast<uint8_t>(payload));
464 return true;
465}
466
474inline bool build_kill_tag(std::vector<uint8_t>& out, const uint32_t kill_password)
475{
476 if (kill_password == 0) {
477 return false;
478 }
479 out.clear();
480 out.reserve(4);
481 out.push_back(static_cast<uint8_t>(kill_password >> 24));
482 out.push_back(static_cast<uint8_t>(kill_password >> 16));
483 out.push_back(static_cast<uint8_t>(kill_password >> 8));
484 out.push_back(static_cast<uint8_t>(kill_password));
485 return true;
486}
487
496 uint16_t pc{};
498 const uint8_t* data{};
499 size_t data_len{};
500};
501
503constexpr uint8_t TAG_OPERATION_SUCCESS{0x00};
504
514inline bool parse_tag_operation(TagOperationResult& out, const uint8_t* param, const size_t len)
515{
516 if (param == nullptr || len < 4) {
517 return false;
518 }
519 const size_t ul = param[0];
520 // The count covers the two PC bytes as well, so anything smaller cannot be a valid answer
521 if (ul < 2 || ul + 1U > len) {
522 return false;
523 }
524 out.pc = static_cast<uint16_t>((param[1] << 8) | param[2]);
525 if (!out.epc.assign(param + 3, ul - 2)) {
526 return false;
527 }
528 out.data = param + 1 + ul;
529 out.data_len = len - 1 - ul;
530 return true;
531}
532
543inline void build_nxp_change_config(std::vector<uint8_t>& out, const uint32_t access_password, const uint16_t toggle)
544{
545 out.clear();
546 out.reserve(6);
547 out.push_back(static_cast<uint8_t>(access_password >> 24));
548 out.push_back(static_cast<uint8_t>(access_password >> 16));
549 out.push_back(static_cast<uint8_t>(access_password >> 8));
550 out.push_back(static_cast<uint8_t>(access_password));
551 out.push_back(static_cast<uint8_t>(toggle >> 8));
552 out.push_back(static_cast<uint8_t>(toggle));
553}
554
565inline void build_nxp_password_and_flag(std::vector<uint8_t>& out, const uint32_t access_password, const uint8_t flag)
566{
567 out.clear();
568 out.reserve(5);
569 out.push_back(static_cast<uint8_t>(access_password >> 24));
570 out.push_back(static_cast<uint8_t>(access_password >> 16));
571 out.push_back(static_cast<uint8_t>(access_password >> 8));
572 out.push_back(static_cast<uint8_t>(access_password));
573 out.push_back(flag);
574}
575
584inline bool parse_nxp_change_config(uint16_t& config, const uint8_t* param, const size_t len)
585{
586 config = 0;
588 if (!parse_tag_operation(r, param, len) || r.data_len < 2) {
589 return false;
590 }
591 config = static_cast<uint16_t>((r.data[0] << 8) | r.data[1]);
592 return true;
593}
594
606inline bool parse_nxp_eas_alarm(std::vector<uint8_t>& alarm, const uint8_t* param, const size_t len)
607{
608 alarm.clear();
609 if (param == nullptr || len == 0) {
610 return false;
611 }
612 alarm.assign(param, param + len);
613 return true;
614}
615
625inline bool parse_block_permalock_lock(const uint8_t* param, const size_t len)
626{
628 if (!parse_tag_operation(r, param, len)) {
629 return false;
630 }
631 return r.data_len >= 1 && r.data[0] == TAG_OPERATION_SUCCESS;
632}
633
644inline bool parse_block_permalock_read(std::vector<uint8_t>& mask, const uint8_t* param, const size_t len)
645{
646 mask.clear();
648 if (!parse_tag_operation(r, param, len)) {
649 return false;
650 }
651 if (r.data_len < 1) {
652 return false;
653 }
654 const size_t range = r.data[0];
655 if (range == 0 || r.data_len < 1 + range * 2) {
656 return false;
657 }
658 mask.assign(r.data + 1, r.data + 1 + range * 2);
659 return true;
660}
661
670constexpr uint8_t QUERY_Q_SHIFT{3};
671constexpr uint8_t QUERY_TARGET_SHIFT{7};
672constexpr uint8_t QUERY_SESSION_SHIFT{8};
673constexpr uint8_t QUERY_SEL_SHIFT{10};
675
681inline void parse_query_parameters(m5::uhf::QueryParameters& qp, const uint16_t raw)
682{
683 qp.q = static_cast<uint8_t>((raw >> QUERY_Q_SHIFT) & 0x0F);
684 qp.target = static_cast<m5::uhf::Target>((raw >> QUERY_TARGET_SHIFT) & 0x01);
685 qp.session = static_cast<m5::uhf::Session>((raw >> QUERY_SESSION_SHIFT) & 0x03);
686 qp.filter = static_cast<m5::uhf::SelectFilter>((raw >> QUERY_SEL_SHIFT) & 0x03);
687}
688
698inline uint16_t build_query_parameters(const m5::uhf::QueryParameters& qp, const uint16_t current)
699{
700 uint16_t raw = current;
701 raw &= static_cast<uint16_t>(~((0x03U << QUERY_SEL_SHIFT) | (0x03U << QUERY_SESSION_SHIFT) |
702 (0x01U << QUERY_TARGET_SHIFT) | (0x0FU << QUERY_Q_SHIFT)));
703 raw |= static_cast<uint16_t>((static_cast<uint8_t>(qp.filter) & 0x03) << QUERY_SEL_SHIFT);
704 raw |= static_cast<uint16_t>((static_cast<uint8_t>(qp.session) & 0x03) << QUERY_SESSION_SHIFT);
705 raw |= static_cast<uint16_t>((static_cast<uint8_t>(qp.target) & 0x01) << QUERY_TARGET_SHIFT);
706 raw |= static_cast<uint16_t>((qp.q & 0x0F) << QUERY_Q_SHIFT);
707 return raw;
708}
709
718enum class MixerGain : uint8_t {
719 dB0,
720 dB3,
721 dB6,
722 dB9,
723 dB12,
724 dB15,
725 dB16,
726};
727
733enum class IFGain : uint8_t {
734 dB12,
735 dB18,
736 dB21,
737 dB24,
738 dB27,
739 dB30,
740 dB36,
741 dB40,
742};
743
748constexpr uint16_t DEMODULATOR_THRESHOLD_DEFAULT{0x01B0};
749
758 MixerGain mixer_gain{MixerGain::dB9};
759 IFGain if_gain{IFGain::dB36};
766};
767
769inline uint8_t mixerGainDb(const MixerGain gain)
770{
771 static const uint8_t db[] = {0, 3, 6, 9, 12, 15, 16};
772 const uint8_t i = static_cast<uint8_t>(gain);
773 return i < sizeof(db) ? db[i] : 0;
774}
775
777inline uint8_t ifGainDb(const IFGain gain)
778{
779 static const uint8_t db[] = {12, 18, 21, 24, 27, 30, 36, 40};
780 const uint8_t i = static_cast<uint8_t>(gain);
781 return i < sizeof(db) ? db[i] : 0;
782}
784constexpr uint8_t MIXER_GAIN_MAX{0x06};
786constexpr uint8_t IF_GAIN_MAX{0x07};
789
797inline bool parse_demodulator_parameters(DemodulatorParameters& dp, const uint8_t* param, const size_t len)
798{
799 if (param == nullptr || len < DEMODULATOR_PARAMETER_LENGTH) {
800 return false;
801 }
802 if (param[0] > MIXER_GAIN_MAX || param[1] > IF_GAIN_MAX) {
803 return false;
804 }
805 dp.mixer_gain = static_cast<MixerGain>(param[0]);
806 dp.if_gain = static_cast<IFGain>(param[1]);
807 dp.threshold = static_cast<uint16_t>((param[2] << 8) | param[3]);
808 return true;
809}
810
817inline bool build_demodulator_parameters(std::vector<uint8_t>& out, const DemodulatorParameters& dp)
818{
819 const uint8_t mixer = static_cast<uint8_t>(dp.mixer_gain);
820 const uint8_t amp = static_cast<uint8_t>(dp.if_gain);
821 if (mixer > MIXER_GAIN_MAX || amp > IF_GAIN_MAX) {
822 return false;
823 }
824 out.clear();
825 out.reserve(DEMODULATOR_PARAMETER_LENGTH);
826 out.push_back(mixer);
827 out.push_back(amp);
828 out.push_back(static_cast<uint8_t>(dp.threshold >> 8));
829 out.push_back(static_cast<uint8_t>(dp.threshold));
830 return true;
831}
832
834constexpr uint8_t COMMAND_ERROR{0xFF};
835
840enum class Error : uint8_t {
841 WatchDogReset = 0x05,
842 ReadFail = 0x09,
843 InvalidParameter = 0x0E,
844 WriteFail = 0x10,
845 KillFail = 0x12,
846 LockFail = 0x13,
847 BlockPermalockFail = 0x14,
848 InventoryFail = 0x15,
849 AccessFail = 0x16,
850 CommandError = 0x17,
851 ChangeConfigFail = 0x1A,
852 ChangeEASFail = 0x1B,
853 EASAlarmFail = 0x1D,
854 FHSSFail = 0x20,
855 ReadProtectFail = 0x2A,
856 ResetReadProtectFail = 0x2B,
857 QTFail = 0x2E,
858};
859
865inline bool is_error_frame(const uint8_t command)
866{
867 return command == COMMAND_ERROR;
868}
869
878inline bool is_no_tag(const uint8_t error_code)
879{
880 return error_code == static_cast<uint8_t>(Error::InventoryFail);
881}
882
890constexpr uint8_t TAG_ERROR_READ{0xA0};
891constexpr uint8_t TAG_ERROR_WRITE{0xB0};
892constexpr uint8_t TAG_ERROR_LOCK{0xC0};
893constexpr uint8_t TAG_ERROR_KILL{0xD0};
894constexpr uint8_t TAG_ERROR_BLOCK_PERMALOCK{0xE0};
896
902inline bool is_tag_error(const uint8_t error_code)
903{
904 return error_code >= TAG_ERROR_READ && error_code <= (TAG_ERROR_BLOCK_PERMALOCK | 0x0F);
905}
906
917inline const char* error_description(const uint8_t error_code)
918{
919 if (is_tag_error(error_code)) {
920 switch (error_code & 0x0F) {
921 case 0x00:
922 return "Tag: other error";
923 case 0x03:
924 return "Tag: memory overrun";
925 case 0x04:
926 return "Tag: memory locked";
927 case 0x0B:
928 return "Tag: insufficient power";
929 case 0x0F:
930 return "Tag: non-specific error";
931 default:
932 return "Tag: unlisted error";
933 }
934 }
935 switch (static_cast<Error>(error_code)) {
936 case Error::WatchDogReset:
937 return "The module's watchdog timed out and reset it";
938 case Error::InvalidParameter:
939 return "A parameter in the command frame is wrong";
940 case Error::ReadFail:
941 return "Read failed: no answer, or a CRC error";
942 case Error::WriteFail:
943 return "Write failed: no answer, or a CRC error";
944 case Error::KillFail:
945 return "Kill failed: no answer, or a CRC error";
946 case Error::LockFail:
947 return "Lock failed: no answer, or a CRC error";
948 case Error::BlockPermalockFail:
949 return "BlockPermalock failed: no answer, or a CRC error";
950 case Error::InventoryFail:
951 return "No tag answered, or a CRC error";
952 case Error::AccessFail:
953 return "Access failed; the password may be wrong";
954 case Error::CommandError:
955 return "Command error in the command frame";
956 case Error::FHSSFail:
957 return "Frequency hopping channel search timed out";
958 case Error::ChangeConfigFail:
959 return "ChangeConfig failed: no answer, or a CRC error";
960 case Error::ChangeEASFail:
961 return "Change EAS failed: no answer, or a CRC error";
962 case Error::EASAlarmFail:
963 return "EAS_Alarm found no tag answering with an alarm code";
964 case Error::ReadProtectFail:
965 return "ReadProtect failed: no answer, or a CRC error";
966 case Error::ResetReadProtectFail:
967 return "Reset ReadProtect failed: no answer, or a CRC error";
968 case Error::QTFail:
969 return "Monza QT failed: no answer, or a CRC error";
970 default:
971 return "Unlisted error";
972 }
973}
974
986inline bool error_answers_command(const uint8_t error_code, const uint8_t command)
987{
988 if (is_tag_error(error_code)) {
989 switch (error_code & 0xF0) {
990 case TAG_ERROR_READ:
991 return command == COMMAND_READ_TAG_MEMORY;
992 case TAG_ERROR_WRITE:
993 return command == COMMAND_WRITE_TAG_MEMORY;
994 case TAG_ERROR_LOCK:
995 return command == COMMAND_LOCK_TAG_MEMORY;
996 case TAG_ERROR_KILL:
997 return command == COMMAND_KILL_TAG;
998 case TAG_ERROR_BLOCK_PERMALOCK:
999 return command == COMMAND_BLOCK_PERMALOCK;
1000 default:
1001 return true;
1002 }
1003 }
1004 switch (static_cast<Error>(error_code)) {
1005 case Error::ReadFail:
1006 return command == COMMAND_READ_TAG_MEMORY;
1007 case Error::WriteFail:
1008 return command == COMMAND_WRITE_TAG_MEMORY;
1009 case Error::KillFail:
1010 return command == COMMAND_KILL_TAG;
1011 case Error::LockFail:
1012 return command == COMMAND_LOCK_TAG_MEMORY;
1013 case Error::BlockPermalockFail:
1014 // A lock is answered under a code of its own, so both can be waited on
1015 return command == COMMAND_BLOCK_PERMALOCK || command == COMMAND_BLOCK_PERMALOCK_LOCK_ANSWER;
1016 case Error::InventoryFail:
1017 return command == COMMAND_SINGLE_POLLING || command == COMMAND_MULTIPLE_POLLING;
1018 case Error::AccessFail:
1019 // Every tag operation is preceded by an access, so any of them can end here
1020 return command == COMMAND_READ_TAG_MEMORY || command == COMMAND_WRITE_TAG_MEMORY ||
1021 command == COMMAND_LOCK_TAG_MEMORY || command == COMMAND_KILL_TAG ||
1022 command == COMMAND_BLOCK_PERMALOCK;
1023 case Error::ChangeConfigFail:
1024 return command == COMMAND_NXP_CHANGE_CONFIG;
1025 case Error::ChangeEASFail:
1026 return command == COMMAND_NXP_CHANGE_EAS;
1027 case Error::EASAlarmFail:
1028 return command == COMMAND_NXP_EAS_ALARM;
1029 case Error::ReadProtectFail:
1030 case Error::ResetReadProtectFail:
1031 return command == COMMAND_NXP_READ_PROTECT;
1032 case Error::QTFail:
1033 // A write is answered under a code of its own, so both can be waited on
1034 return command == COMMAND_MONZA_QT || command == COMMAND_MONZA_QT_WRITE_ANSWER;
1035 case Error::WatchDogReset:
1036 // The command in flight died with the reset and will never be answered
1037 return true;
1038 default:
1039 // A command error, a wrong parameter or a failed hop answers whatever was sent, and
1040 // so does a code this table does not name
1041 return true;
1042 }
1043}
1044
1048enum class FrameRoute : uint8_t {
1050 Response,
1051 Drop,
1052 Unexpected,
1053};
1054
1066inline FrameRoute route_for(const Frame& f, const bool response_pending, const uint8_t awaiting_command)
1067{
1068 // The protocol document is inconsistent about the Type byte of a tag notification, so the
1069 // command code is what routes it
1071 return FrameRoute::TagNotification;
1072 }
1073 if (is_error_frame(f.command)) {
1074 const uint8_t code = f.parameter.empty() ? 0x00 : f.parameter[0];
1075 // Inventory Fail answers a polling command and nothing else: every tag operation has a
1076 // failure code of its own. It arrives once per empty round, and the rounds already under
1077 // way keep arriving after a stop, so anywhere but in front of a polling command it is a
1078 // leftover rather than the answer to whatever was sent last
1079 const bool awaiting_inventory = response_pending && (awaiting_command == COMMAND_SINGLE_POLLING ||
1080 awaiting_command == COMMAND_MULTIPLE_POLLING);
1081 if (is_no_tag(code) && !awaiting_inventory) {
1082 return FrameRoute::Drop;
1083 }
1084 if (!response_pending) {
1085 return FrameRoute::Drop;
1086 }
1087 return error_answers_command(code, awaiting_command) ? FrameRoute::Response : FrameRoute::Drop;
1088 }
1089 if (response_pending && f.command == awaiting_command) {
1090 return FrameRoute::Response;
1091 }
1092 // A module answering under a command code nobody is waiting for is exactly how an exchange
1093 // slips by one frame, so this is worth saying out loud rather than dropping quietly
1094 return FrameRoute::Unexpected;
1095}
1096
1109inline bool is_worth_retrying(const uint8_t error_code)
1110{
1111 if (is_tag_error(error_code)) {
1112 return false;
1113 }
1114 switch (static_cast<Error>(error_code)) {
1115 case Error::ReadFail:
1116 case Error::WriteFail:
1117 case Error::KillFail:
1118 case Error::LockFail:
1119 case Error::BlockPermalockFail:
1120 case Error::ChangeConfigFail:
1121 case Error::ChangeEASFail:
1122 case Error::ReadProtectFail:
1123 case Error::ResetReadProtectFail:
1124 case Error::QTFail:
1125 return true;
1126 case Error::WatchDogReset:
1127 // A reset loses the mask, so the same command again would address whichever tag
1128 // answers rather than the one that was chosen
1129 return false;
1130 default:
1131 return false;
1132 }
1133}
1134
1135} // namespace m100
1136} // namespace unit
1137} // namespace m5
1138#endif
constexpr uint8_t COMMAND_NXP_EAS_ALARM
NXP EAS_Alarm.
Definition m100_frame.hpp:242
constexpr size_t DEMODULATOR_PARAMETER_LENGTH
Length of the demodulator parameter payload.
Definition m100_frame.hpp:788
Error
Error codes carried by a failure notification.
Definition m100_frame.hpp:840
@ CommandError
Command error in the command frame.
@ ChangeConfigFail
NXP ChangeConfig failed.
@ BlockPermalockFail
BlockPermalock execution failed.
@ ReadProtectFail
NXP ReadProtect failed.
@ KillFail
Failed to kill the tag.
@ LockFail
Failed to lock the tag's data memory area.
@ FHSSFail
Frequency hopping channel search timed out.
@ ResetReadProtectFail
NXP Reset ReadProtect failed.
@ ReadFail
Failed to read the tag's data memory area.
@ InvalidParameter
A parameter in the command frame is wrong.
@ EASAlarmFail
NXP EAS_Alarm found no tag answering with an alarm code.
@ WriteFail
Failed to write the tag's data memory area.
@ InventoryFail
No tag responded or a data CRC check error occurred.
@ AccessFail
Failed to access the tag.
@ WatchDogReset
The module's watchdog timed out and reset it.
@ QTFail
Impinj Monza QT failed.
@ ChangeEASFail
NXP Change EAS failed.
constexpr uint8_t COMMAND_SINGLE_POLLING
Command code of the single polling notification.
Definition m100_frame.hpp:220
void build_nxp_password_and_flag(std::vector< uint8_t > &out, const uint32_t access_password, const uint8_t flag)
Build the parameter of NXP Change EAS (0xE3) or ReadProtect (0xE1)
Definition m100_frame.hpp:565
bool build_frame(std::vector< uint8_t > &out, const uint8_t type, const uint8_t command, const uint8_t *param, const uint16_t param_len, const uint8_t header=jrd::FRAME_HEADER, const uint8_t end=jrd::FRAME_END)
Build a command frame.
Definition m100_frame.hpp:75
constexpr uint16_t MAX_PARAMETER_LENGTH
Maximum parameter length accepted for a frame.
Definition m100_frame.hpp:62
bool parse_nxp_change_config(uint16_t &config, const uint8_t *param, const size_t len)
Parse the answer to NXP ChangeConfig (0xE0)
Definition m100_frame.hpp:584
constexpr uint8_t COMMAND_NXP_CHANGE_EAS
NXP Change EAS.
Definition m100_frame.hpp:240
constexpr uint8_t TYPE_NOTIFICATION
Frame type: notification (module to host)
Definition m100_frame.hpp:111
constexpr uint8_t TAG_OPERATION_SUCCESS
Status byte a tag returns after a Write, Lock or Kill it carried out.
Definition m100_frame.hpp:503
void parse_query_parameters(m5::uhf::QueryParameters &qp, const uint16_t raw)
Split a Query parameter word into its fields.
Definition m100_frame.hpp:681
constexpr uint8_t SELECT_TRUNCATE_OFF
Truncate field of the Select parameter.
Definition m100_frame.hpp:283
constexpr uint8_t COMMAND_BLOCK_PERMALOCK
Command code a BlockPermalock lock is answered under, unlike the read.
Definition m100_frame.hpp:232
bool error_answers_command(const uint8_t error_code, const uint8_t command)
Could this error code be the answer to this command?
Definition m100_frame.hpp:986
constexpr uint8_t COMMAND_MULTIPLE_POLLING
Command code of the multiple polling notification.
Definition m100_frame.hpp:222
constexpr uint8_t COMMAND_MONZA_QT_WRITE_ANSWER
Command code a Monza QT write is answered under, which is not the one it was sent as.
Definition m100_frame.hpp:246
void build_nxp_change_config(std::vector< uint8_t > &out, const uint32_t access_password, const uint16_t toggle)
Build the parameter of NXP ChangeConfig (0xE0)
Definition m100_frame.hpp:543
bool parse_nxp_eas_alarm(std::vector< uint8_t > &alarm, const uint8_t *param, const size_t len)
Parse the answer to NXP EAS_Alarm (0xE4)
Definition m100_frame.hpp:606
constexpr uint8_t FRAME_END
Frame end.
Definition m100_frame.hpp:43
constexpr uint8_t MEMBANK_RESERVED
MemBank values of the Select parameter and of the read/write commands.
Definition m100_frame.hpp:277
constexpr size_t SELECT_MASK_MAX_BITS
Longest mask the Select parameter accepts, in bits.
Definition m100_frame.hpp:294
FrameExtract extract_frame(Frame &out, std::vector< uint8_t > &buffer, size_t &discarded, const uint8_t header, const uint8_t end)
Take the first whole frame out of received bytes.
Definition m100_frame.hpp:181
constexpr size_t FRAME_PARAMETER_OFFSET
Offset of the Parameter.
Definition m100_frame.hpp:104
constexpr uint8_t MIXER_GAIN_MAX
Highest mixer gain the module accepts.
Definition m100_frame.hpp:784
constexpr uint16_t DEMODULATOR_THRESHOLD_DEFAULT
Demodulation threshold the module leaves the factory with.
Definition m100_frame.hpp:748
constexpr size_t WRITE_MAX_WORDS
Longest write the module accepts, in 16-bit words.
Definition m100_frame.hpp:292
constexpr uint8_t COMMAND_NXP_READ_PROTECT
NXP ReadProtect and Reset ReadProtect, which share one command code.
Definition m100_frame.hpp:238
bool build_kill_tag(std::vector< uint8_t > &out, const uint32_t kill_password)
Build the parameter of Kill (0x65)
Definition m100_frame.hpp:474
bool is_worth_retrying(const uint8_t error_code)
Is this failure worth sending the same command again for?
Definition m100_frame.hpp:1109
bool parse_demodulator_parameters(DemodulatorParameters &dp, const uint8_t *param, const size_t len)
Split a demodulator parameter payload into its fields.
Definition m100_frame.hpp:797
const char * error_description(const uint8_t error_code)
Describe an error code in one word.
Definition m100_frame.hpp:917
bool is_no_tag(const uint8_t error_code)
Does the error code only mean "no tag was found this round"?
Definition m100_frame.hpp:878
constexpr uint8_t COMMAND_NXP_CHANGE_CONFIG
NXP ChangeConfig.
Definition m100_frame.hpp:236
bool build_read_tag_memory(std::vector< uint8_t > &out, const uint32_t access_password, const uint8_t membank, const uint16_t word_address, const uint16_t word_count)
Build the parameter of Read Tag Memory Area (0x39)
Definition m100_frame.hpp:399
constexpr uint8_t SELECT_MODE_NON_INVENTORY
Select before everything except inventory.
Definition m100_frame.hpp:289
constexpr uint8_t COMMAND_BLOCK_PERMALOCK_LOCK_ANSWER
Command code a BlockPermalock lock is answered under, unlike the read.
Definition m100_frame.hpp:234
constexpr uint8_t SELECT_MODE_ALWAYS
Select modes of Set Select Mode (0x12)
Definition m100_frame.hpp:287
FrameRoute route_for(const Frame &f, const bool response_pending, const uint8_t awaiting_command)
Decide what a received frame answers.
Definition m100_frame.hpp:1066
bool is_error_frame(const uint8_t command)
Is the command code of a failure notification?
Definition m100_frame.hpp:865
bool parse_tag_operation(TagOperationResult &out, const uint8_t *param, const size_t len)
Parse the answer to a tag operation.
Definition m100_frame.hpp:514
MixerGain
Gain of the receiver's mixer.
Definition m100_frame.hpp:718
@ dB9
9dB, the value the module leaves the factory with
constexpr uint8_t SELECT_MODE_NEVER
Never send Select.
Definition m100_frame.hpp:288
bool parse_block_permalock_lock(const uint8_t *param, const size_t len)
Parse the answer to a BlockPermalock that locked.
Definition m100_frame.hpp:625
constexpr uint8_t COMMAND_MONZA_QT
Impinj Monza QT.
Definition m100_frame.hpp:244
bool build_demodulator_parameters(std::vector< uint8_t > &out, const DemodulatorParameters &dp)
Build the parameter of Set Demodulator Parameter (0xF0)
Definition m100_frame.hpp:817
bool parse_block_permalock_read(std::vector< uint8_t > &mask, const uint8_t *param, const size_t len)
Parse the answer to a BlockPermalock that read.
Definition m100_frame.hpp:644
bool parse_tag_notification(m5::uhf::Tag &out, const uint8_t *param, const size_t len)
Parse the parameter of a tag notification.
Definition m100_frame.hpp:259
bool parse_frame(Frame &out, const uint8_t *raw, const size_t len, const uint8_t header=jrd::FRAME_HEADER, const uint8_t end=jrd::FRAME_END)
Parse a frame.
Definition m100_frame.hpp:132
constexpr uint8_t COMMAND_WRITE_TAG_MEMORY
Command code a BlockPermalock lock is answered under, unlike the read.
Definition m100_frame.hpp:229
bool build_write_tag_memory(std::vector< uint8_t > &out, const uint32_t access_password, const uint8_t membank, const uint16_t word_address, const uint8_t *data, const size_t len)
Build the parameter of Write Tag Memory Area (0x49)
Definition m100_frame.hpp:429
uint8_t checksum(const uint8_t *body, const size_t len)
Calculate the frame checksum.
Definition m100_frame.hpp:52
FrameExtract
What came of trying to take a frame out of the bytes received so far.
Definition m100_frame.hpp:161
@ NeedMore
What is in the buffer could still turn into a frame once more bytes arrive.
@ Ok
A frame was taken out, and the bytes it was made of are gone from the buffer.
constexpr uint8_t TYPE_COMMAND
Frame type: command (host to module)
Definition m100_frame.hpp:107
uint8_t select_parameter_byte(const uint8_t target, const uint8_t action, const uint8_t membank)
Build the SelParam byte of Set Select Parameter.
Definition m100_frame.hpp:303
bool build_lock_tag(std::vector< uint8_t > &out, const uint32_t access_password, const uint32_t payload)
Build the parameter of Lock (0x82)
Definition m100_frame.hpp:450
constexpr uint8_t FRAME_HEADER
Frame header.
Definition m100_frame.hpp:41
bool build_select_parameter(std::vector< uint8_t > &out, const uint8_t sel_param, const uint32_t pointer_bits, const uint8_t mask_length_bits, const uint8_t truncate, const uint8_t *mask, const size_t mask_len)
Build the parameter of Set Select Parameter (0x0C)
Definition m100_frame.hpp:320
bool parse_select_parameter(m5::uhf::SelectParameter &out, const uint8_t *param, const size_t len)
Parse the answer to Get Select Parameter (0x0B)
Definition m100_frame.hpp:356
constexpr uint8_t COMMAND_READ_TAG_MEMORY
Command code a BlockPermalock lock is answered under, unlike the read.
Definition m100_frame.hpp:228
constexpr uint8_t COMMAND_ERROR
Command code used by every failure notification.
Definition m100_frame.hpp:834
uint8_t mixerGainDb(const MixerGain gain)
Mixer gain in dB.
Definition m100_frame.hpp:769
constexpr size_t FRAME_OVERHEAD
Fixed part of a frame (Header, Type, Command, PL_MSB, PL_LSB, Checksum, End)
Definition m100_frame.hpp:100
constexpr uint8_t COMMAND_KILL_TAG
Command code a BlockPermalock lock is answered under, unlike the read.
Definition m100_frame.hpp:230
IFGain
Gain of the receiver's intermediate frequency amplifier.
Definition m100_frame.hpp:733
@ dB36
36dB, the value the module leaves the factory with
constexpr uint8_t IF_GAIN_MAX
Highest intermediate frequency gain the module accepts.
Definition m100_frame.hpp:786
uint16_t build_query_parameters(const m5::uhf::QueryParameters &qp, const uint16_t current)
Place the fields we expose into a Query parameter word.
Definition m100_frame.hpp:698
constexpr size_t FRAME_TYPE_OFFSET
Offset of the Type byte.
Definition m100_frame.hpp:102
constexpr uint8_t TYPE_RESPONSE
Frame type: response (module to host)
Definition m100_frame.hpp:109
FrameRoute
What is to be done with a frame that has just been read.
Definition m100_frame.hpp:1048
@ TagNotification
A tag the module found. Parse it and queue it.
@ Drop
Left over from an exchange that has already given up.
@ Unexpected
Nobody asked for this one.
@ Response
The answer to the command being waited on.
bool is_tag_error(const uint8_t error_code)
Did the tag itself report this error, rather than the module?
Definition m100_frame.hpp:902
constexpr uint8_t COMMAND_LOCK_TAG_MEMORY
Command code a BlockPermalock lock is answered under, unlike the read.
Definition m100_frame.hpp:231
uint8_t ifGainDb(const IFGain gain)
Intermediate frequency amplifier gain in dB.
Definition m100_frame.hpp:777
constexpr size_t TAG_NOTIFICATION_OVERHEAD
Fixed part of a tag notification (RSSI, PC and CRC)
Definition m100_frame.hpp:249
Framing the JRD-4035 and JRD-100 modules use.
magicRF M100 chip specific definitions
Top level namespace of M5Stack.
Unit-related namespace.
EPC of a tag.
Definition uhf.hpp:173
bool assign(const uint8_t *src, const size_t len)
Copy in from a raw buffer, refusing anything longer than EPC_MAX_BYTES.
Definition uhf.hpp:1474
EPC Gen2 query parameters.
Definition uhf.hpp:1302
Session session
Session whose inventoried flag qualifies the round.
Definition uhf.hpp:1304
Target target
Inventoried flag value that is invited to answer.
Definition uhf.hpp:1305
uint8_t q
Q value: a tag answers in one of 2^Q slots.
Definition uhf.hpp:1303
SelectFilter filter
Which tags the round invites, in terms of SL.
Definition uhf.hpp:1311
The mask the reader holds, and how it is matched.
Definition uhf.hpp:1424
Bank bank
Bank the mask is matched against.
Definition uhf.hpp:1427
bool truncate
Whether a matching tag replies with only the bits past the mask.
Definition uhf.hpp:1430
uint8_t mask_length_bits
Length of the mask in bits.
Definition uhf.hpp:1429
uint8_t target
Target: which flag the action acts on, 100 being SL.
Definition uhf.hpp:1425
uint8_t mask_size
Bytes of mask in use.
Definition uhf.hpp:1432
uint32_t pointer_bits
Where the mask starts, as a bit address inside the bank.
Definition uhf.hpp:1428
std::array< uint8_t, SELECT_MASK_MAX_BYTES > mask
Mask bytes.
Definition uhf.hpp:1431
uint8_t action
Action: what matching and non-matching tags are set to.
Definition uhf.hpp:1426
A tag, as the EPC Gen2 standard calls it.
Definition uhf.hpp:366
Epc epc
EPC.
Definition uhf.hpp:371
uint16_t crc
CRC-16 reported by the tag.
Definition uhf.hpp:369
int8_t rssi
RSSI in dBm (signed)
Definition uhf.hpp:370
uint16_t pc
Protocol Control.
Definition uhf.hpp:368
Receiver settings that decide how weak a reply the reader can still make sense of.
Definition m100_frame.hpp:757
uint16_t threshold
Definition m100_frame.hpp:765
Parsed frame.
Definition m100_frame.hpp:117
std::vector< uint8_t > parameter
Parameter.
Definition m100_frame.hpp:120
uint8_t type
Frame type.
Definition m100_frame.hpp:118
uint8_t command
Command code.
Definition m100_frame.hpp:119
What a tag answered to Read, Write, Lock or Kill.
Definition m100_frame.hpp:495
uint16_t pc
Protocol Control of the tag that answered.
Definition m100_frame.hpp:496
m5::uhf::Epc epc
EPC of the tag that answered.
Definition m100_frame.hpp:497
size_t data_len
Length of data in bytes.
Definition m100_frame.hpp:499
const uint8_t * data
Words read, or the one status byte.
Definition m100_frame.hpp:498
Common vocabulary for UHF-RFID (EPCglobal UHF Class 1 Gen 2 / ISO 18000-6C)
Session
EPC Gen2 session.
Definition uhf.hpp:57
SelectFilter
Which tags an inventory round invites, in terms of the SL flag.
Definition uhf.hpp:73
Target
EPC Gen2 inventoried flag target.
Definition uhf.hpp:63
Bank
EPC Gen2 memory bank.
Definition uhf.hpp:32