Files
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

332 lines
13 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest
#include "PN532Serial.h"
#include <errno.h>
#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <string.h>
#include <string>
#include <thread>
#include <unistd.h>
#include <everest/logging.hpp>
constexpr size_t FIRMWARE_VERSION_IC_POS = 0;
constexpr size_t FIRMWARE_VERSION_VER_POS = 1;
constexpr size_t FIRMWARE_VERSION_REV_POS = 2;
constexpr size_t FIRMWARE_VERSION_SUPPORT_POS = 3;
constexpr size_t IN_LIST_PASSIVE_TARGET_NBTG = 0;
constexpr size_t IN_LIST_PASSIVE_TARGET_TG = 1;
constexpr size_t IN_LIST_PASSIVE_TARGET_SENS_RES_MSB = 2;
constexpr size_t IN_LIST_PASSIVE_TARGET_SENS_RES_LSB = 3;
constexpr size_t IN_LIST_PASSIVE_TARGET_SEL_RES = 4;
constexpr size_t IN_LIST_PASSIVE_TARGET_NFCID_LENGTH = 5;
// command codes
constexpr uint8_t COMMAND_GET_FIRMWARE_VERSION = 0x02;
constexpr uint8_t COMMAND_GET_FIRMWARE_VERSION_RESPONSE = 0x03;
constexpr uint8_t COMMAND_SAM_CONFIGURATION = 0x14;
constexpr uint8_t COMMAND_SAM_CONFIGURATION_RESPONSE = 0x15;
constexpr uint8_t COMMAND_IN_LIST_PASSIVE_TARGET = 0x4a;
constexpr uint8_t COMMAND_IN_LIST_PASSIVE_TARGET_RESPONSE = 0x4b;
void PN532Serial::run() {
readThreadHandle = std::thread(&PN532Serial::readThread, this);
}
void PN532Serial::readThread() {
uint8_t buf[2048];
size_t n = 0;
ssize_t first_start_code_offset;
while (true) {
if (readThreadHandle.shouldExit()) {
break;
}
auto c = read(fd, &buf[n], sizeof(buf) - n);
if (c == -1) {
EVLOG_error << "Read failed: " << strerror(errno) << " (" << errno << ")";
break;
}
if (c == 0) {
continue;
}
if (this->debug) {
EVLOG_info << "Received: " << hexdump(buf + n, c);
}
n += c;
restart_buffer_analysis:
EVLOG_verbose << "buffer content (size: " << n << "): " << hexdump(buf, n);
// packet minimum size is 6 byte for ACK or NACK, other frames are larger
if (n < 6) {
continue;
}
first_start_code_offset = -1;
// we cannot assume that we receive the whole response in one read call
// because of different driver/interfaces, so we have to scan for well-known
// patterns or check whether it looks like a valid frame after a start code
// pattern; if we find one, we can delete it from the buffer and move the
// left-over buffer content to the front, then we must restart analyzing
// to be sure to handle any following frame immediately and don't wait
// until the next read call runs/returns
for (ssize_t i = 0; i <= n - 6; ++i) {
// check for ACK or NACKs
if (memcmp(&buf[i], ACK_FRAME.data(), ACK_FRAME.size()) == 0) {
EVLOG_debug << "Received ACK";
n = n - i - ACK_FRAME.size();
memmove(buf, buf + i + ACK_FRAME.size(), n);
goto restart_buffer_analysis;
}
if (memcmp(&buf[i], NACK_FRAME.data(), NACK_FRAME.size()) == 0) {
EVLOG_debug << "Received NACK";
n = n - i - NACK_FRAME.size();
memmove(buf, buf + i + NACK_FRAME.size(), n);
goto restart_buffer_analysis;
}
// check for start_code
if (memcmp(&buf[i], START_CODE.data(), START_CODE.size()) == 0) {
if (first_start_code_offset == -1) {
first_start_code_offset = i;
}
auto len = buf[i + START_CODE.size()];
auto lcs = buf[i + START_CODE.size() + 1];
if (((len + lcs) & 0xff) != 0) {
EVLOG_verbose << "package length checksum mismatch";
continue;
}
// offset + start code size + len and lcs byte + data length + dcs and postamble
if (i + START_CODE.size() + 2 + len + 2 > n) {
EVLOG_verbose << "frame still too short or length bogus";
continue;
}
// accesses beyond i are safe below since we ensure that above
auto tfi = buf[i + START_CODE.size() + 2];
if (tfi != PN532_TO_HOST) {
EVLOG_verbose << "frame is not a response";
continue;
}
if (buf[i + START_CODE.size() + 2 + len + 1] != 0x00) {
EVLOG_verbose << "postamble check failed";
continue;
}
auto dcs_compl = 0;
for (size_t j = 0; j < len; ++j) {
dcs_compl += buf[i + START_CODE.size() + 2 + j];
}
auto dcs = buf[i + START_CODE.size() + 2 + len];
if (((dcs_compl + dcs) & 0xff) != 0) {
EVLOG_verbose << "package data checksum does not match";
continue;
}
// all checks so far indicate that frame/packet looks good
command_code = buf[i + START_CODE.size() + 2 + 1];
data = std::vector<uint8_t>(&buf[i + START_CODE.size() + 2 + 1 + 1],
&buf[i + START_CODE.size() + 2 + len]);
parseData();
// discard processed data, the processed size is:
// offset (incl. preamble) + start code size + len and lcs byte + data length + dcs and postamble
auto processed_size = i + START_CODE.size() + 1 + 1 + data.size() + 1 + 1;
n = n - processed_size;
memmove(buf, buf + processed_size, n);
goto restart_buffer_analysis;
}
}
if (first_start_code_offset == -1) {
// no start code found so far: continue with outer loop and receive more data
} else {
// use first start code offset found and assume garbage before -> discard this
n -= first_start_code_offset;
memmove(buf, buf + first_start_code_offset, n);
}
}
}
void PN532Serial::parseData() {
if (command_code == COMMAND_GET_FIRMWARE_VERSION_RESPONSE && this->get_firmware_version_promise) {
PN532Response response;
if (data.size() == 4) {
EVLOG_debug << "Sending ACK";
serialWrite(ACK_FRAME);
response.valid = true;
response.message = std::make_shared<FirmwareVersion>(
data.at(FIRMWARE_VERSION_IC_POS), data.at(FIRMWARE_VERSION_VER_POS), data.at(FIRMWARE_VERSION_REV_POS),
data.at(FIRMWARE_VERSION_SUPPORT_POS));
this->get_firmware_version_promise->set_value(response);
} else {
EVLOG_debug << "Sending NACK";
serialWrite(NACK_FRAME);
}
} else if (command_code == COMMAND_SAM_CONFIGURATION_RESPONSE && this->configure_sam_promise) {
EVLOG_debug << "Sending ACK";
serialWrite(ACK_FRAME);
this->configure_sam_promise->set_value(true);
} else if (command_code == COMMAND_IN_LIST_PASSIVE_TARGET_RESPONSE && this->in_list_passive_target_promise) {
// always send ACK for now
EVLOG_debug << "Sending ACK";
serialWrite(ACK_FRAME);
parseInListPassiveTargetResponse();
}
}
void PN532Serial::parseInListPassiveTargetResponse() {
PN532Response response;
auto in_list_passive_target_response = std::make_shared<InListPassiveTargetResponse>();
TargetData target_data;
if (data.size() >= 6) {
in_list_passive_target_response->nbtg = data.at(IN_LIST_PASSIVE_TARGET_NBTG);
if (in_list_passive_target_response->nbtg == 1) {
target_data.tg = data.at(IN_LIST_PASSIVE_TARGET_TG);
target_data.sens_res_msb = data.at(IN_LIST_PASSIVE_TARGET_SENS_RES_MSB);
target_data.sens_res_lsb = data.at(IN_LIST_PASSIVE_TARGET_SENS_RES_LSB);
target_data.sens_res = (target_data.sens_res_msb << 8) + target_data.sens_res_lsb;
target_data.sel_res = data.at(IN_LIST_PASSIVE_TARGET_SEL_RES);
target_data.nfcid_length = data.at(IN_LIST_PASSIVE_TARGET_NFCID_LENGTH);
if (this->debug) {
EVLOG_info << "Target data: " << std::right << std::hex << "\ntg: 0x" << std::setfill('0')
<< std::setw(2) << (int)target_data.tg << "\nsens_res_msb: 0x" << std::setfill('0')
<< std::setw(2) << (int)target_data.sens_res_msb << "\nsens_res_lsb: 0x" << std::setfill('0')
<< std::setw(2) << (int)target_data.sens_res_lsb << "\nsens_res: 0x" << std::setfill('0')
<< std::setw(4) << (int)target_data.sens_res << "\nsel_res: 0x" << std::setfill('0')
<< std::setw(2) << (int)target_data.sel_res << "\nnfcid_length: 0x" << std::setfill('0')
<< std::setw(2) << (int)target_data.nfcid_length;
}
if (data.size() >= 6 + target_data.nfcid_length) {
response.valid = true;
for (ssize_t i = 6; i < 6 + target_data.nfcid_length; i++) {
target_data.nfcid.push_back(data.at(i));
}
if (this->debug) {
std::stringstream nfcid_stream;
nfcid_stream << hexdump(target_data.nfcid);
nfcid_stream << " (length: " << std::dec << target_data.nfcid.size() << ")";
EVLOG_info << "NFCID: " << nfcid_stream.str();
}
if (data.size() >= 6 + target_data.nfcid_length + 1) {
auto ats_length = data.at(6 + target_data.nfcid_length);
if (data.size() >= 6 + target_data.nfcid_length + ats_length) {
for (size_t i = 6 + target_data.nfcid_length; i < 6 + target_data.nfcid_length + ats_length;
i++) {
target_data.ats.push_back(data.at(i));
}
}
if (this->debug) {
std::stringstream ats_stream;
ats_stream << hexdump(target_data.ats);
ats_stream << " (length: " << std::dec << target_data.ats.size() << ")";
EVLOG_info << "ATS: " << ats_stream.str();
}
}
}
in_list_passive_target_response->target_data.push_back(target_data);
}
}
response.message = in_list_passive_target_response;
this->in_list_passive_target_promise->set_value(response);
}
std::future<bool> PN532Serial::configureSAM() {
this->configure_sam_promise = std::make_unique<std::promise<bool>>();
this->serialWriteCommand({
COMMAND_SAM_CONFIGURATION, // command code
0x01, // normal mode
0x00, // no timeout
0x01 // P70_IRQ pin driven by PN532
});
return this->configure_sam_promise->get_future();
}
std::future<PN532Response> PN532Serial::getFirmwareVersion() {
this->get_firmware_version_promise = std::make_unique<std::promise<PN532Response>>();
this->serialWriteCommand({COMMAND_GET_FIRMWARE_VERSION});
return this->get_firmware_version_promise->get_future();
}
std::future<PN532Response> PN532Serial::inListPassiveTarget() {
this->in_list_passive_target_promise = std::make_unique<std::promise<PN532Response>>();
this->serialWriteCommand({
COMMAND_IN_LIST_PASSIVE_TARGET, // command code
0x01, // one target
0x00 // 105 kbps type A (ISO/IEC1443 Type A)
});
return this->in_list_passive_target_promise->get_future();
}
bool PN532Serial::serialWrite(const std::vector<uint8_t>& data) {
auto rv = write(fd, data.data(), data.size());
if (rv != data.size()) {
EVLOG_error << "Write failed: " << strerror(errno) << " (" << errno << ")";
return false;
}
return true;
}
bool PN532Serial::serialWriteCommand(const std::vector<uint8_t>& data) {
// calculate packet length checksum
uint8_t lcs = data.size() + 1;
lcs = (uint8_t)-lcs;
// calculate data check sum
uint8_t dcs = HOST_TO_PN532;
for (auto element : data) {
dcs += element;
}
dcs = (uint8_t)-dcs;
std::vector<uint8_t> serial_data;
// size: preamble + start code + LEN + LCS + TFI + data + DCS + postamble
serial_data.reserve(1 + START_CODE.size() + 1 + 1 + 1 + data.size() + 1 + 1);
serial_data.push_back(PREAMBLE);
serial_data.insert(serial_data.end(), START_CODE.begin(), START_CODE.end());
serial_data.push_back(data.size() + 1);
serial_data.push_back(lcs);
serial_data.push_back(HOST_TO_PN532);
serial_data.insert(serial_data.end(), data.begin(), data.end());
serial_data.push_back(dcs);
serial_data.push_back(POSTAMBLE);
if (this->debug) {
EVLOG_info << "Sending bytes: " << hexdump(serial_data);
}
return this->serialWrite(serial_data);
}
bool PN532Serial::reset() {
return this->serialWrite(
{0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
}
void PN532Serial::enableDebug() {
this->debug = true;
}