Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions cyberpunkunicorn-os/apps/frequency/FrequencyMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ bool openReader(bool &anyway) {
for (;;) {
CardStatus s = readerOpen();
if (s == CARD_OK) return true;
if (s == CARD_READER_STUCK) {
notice("FREQUENCY", "READER STUCK", cardStatusText(s), "Unplug the board for 10 s, DIP 3 only.");
return false;
}
if (s != CARD_NO_READER) {
notice("FREQUENCY", "READER BUSY", cardStatusText(s), "Close the other screen or serial command.");
return false;
Expand All @@ -120,6 +124,12 @@ bool openReader(bool &anyway) {
}
}

// A wait that ended because the reader stopped taking commands says so.
void stuckNotice(CardStatus s) {
if (s == CARD_READER_STUCK)
notice("FREQUENCY", "READER STUCK", cardStatusText(s), "Unplug the board for 10 s, DIP 3 only.");
}

bool openReader() {
bool anyway;
return openReader(anyway);
Expand Down Expand Up @@ -285,7 +295,10 @@ int writeLoop(const String &label, const UrlCheck &u) {
cyberAnimate();
}
);
if (s != CARD_OK) return written;
if (s != CARD_OK) {
stuckNotice(s);
return written;
}

CardInfo done;
s = writeCard(u.fixed, done);
Expand Down Expand Up @@ -520,7 +533,8 @@ void home() {
);
if (s != CARD_OK) {
readerClose();
if (k == K_ESC || returnToMenu) return;
stuckNotice(s);
if (k == K_ESC || returnToMenu || s == CARD_READER_STUCK) return;
if (!menu()) return;
continue;
}
Expand Down Expand Up @@ -597,7 +611,10 @@ void issueMemberCard() {
cyberAnimate();
}
);
if (s != CARD_OK) break;
if (s != CARD_OK) {
stuckNotice(s);
break;
}
s = writeCard(u.fixed.length() ? u.fixed : r.url, done);
if (s != CARD_OK) {
notice("ISSUE CARD", "TRY AGAIN", writeProblem(s), "", "any key: try again", 4000);
Expand Down
57 changes: 53 additions & 4 deletions cyberpunkunicorn-os/apps/frequency/card.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "modules/rfid/PN532.h"

#include "../hat/hat_probe.h"
#include "core/bus_HAL.h"
#include "card.h"
#include "flash.h"
#include <freertos/semphr.h>
Expand Down Expand Up @@ -185,6 +186,44 @@ void fill(CardInfo &out) {
out.uri = parseNdefUri(mem);
}

// A PN532 can ACK its I2C address and still never take a command: every
// transfer then runs into Wire's timeout (0.1.3 logs: 43 polls in 30 s, all
// "no ack"). Ask for the firmware version, the one command with a fixed answer;
// if it doesn't come, free the bus (9-clock recovery), start it again and ask
// once more.
bool commandsAnswered() {
uint32_t v = rfid->nfc.getFirmwareVersion();
// On serial, so the website's device log shows whether the chip took a command at all.
Serial.printf("[freq] PN532 firmware %s (0x%08lX)\n", v ? "answered" : "did not answer", (unsigned long)v);
return v != 0;
}

bool startReader() {
for (int attempt = 0; attempt < 2; attempt++) {
if (attempt) {
Serial.println("[freq] reader not answering commands; recovering the I2C bus");
releaseI2CBus(); // Wire must be off 22/27 while the lines are clocked by hand
hatI2CRecover();
delay(50);
if (!rfid->begin()) continue;
}
// The PN532 is rated to 400 kHz but clock-stretches; 100 kHz is what Bruce's own NFC tools use.
acquireI2CBus()->setClock(100000);
if (!commandsAnswered()) continue;
rfid->nfc.SAMConfig();
// Answer "no tag" after a few tries instead of waiting forever, so a poll
// never leaves a command pending in the chip.
rfid->nfc.setPassiveActivationRetries(0x05);
return true;
}
return false;
}

// Polls in a row that got no ACK. A few of them means the reader stopped taking
// commands mid-wait; one recovery, then give up instead of spinning to the timeout.
constexpr int STUCK_AFTER = 4;
int noAckRun = 0;

} // namespace

void resetPollStats() { stats = PollStats(); }
Expand All @@ -202,6 +241,7 @@ const char *cardStatusText(CardStatus s) {
case CARD_LOCKED: return "Tag is write-locked";
case CARD_WRITE_FAILED: return "Write failed - hold still";
case CARD_VERIFY_FAILED: return "Verify failed";
case CARD_READER_STUCK: return "Reader not answering - unplug 10 s";
}
return "?";
}
Expand All @@ -224,10 +264,10 @@ CardStatus readerOpen() {
readerClose();
return CARD_NO_READER;
}
rfid->nfc.SAMConfig();
// Answer "no tag" after a few tries instead of waiting forever, so a poll
// never leaves a command pending in the chip.
rfid->nfc.setPassiveActivationRetries(0x05);
if (!startReader()) {
readerClose();
return CARD_READER_STUCK;
}
return CARD_OK;
}

Expand All @@ -252,9 +292,18 @@ CardStatus waitCard(
CardInfo &out, uint32_t timeoutMs, const std::function<bool()> &cancel, const std::function<void()> &idle
) {
uint32_t start = millis();
noAckRun = 0;
bool recovered = false;
for (;;) {
uint32_t acksBefore = stats.noAck;
CardStatus s = pollCard(out);
if (s != CARD_NONE) return s;
noAckRun = stats.noAck != acksBefore ? noAckRun + 1 : 0;
if (noAckRun >= STUCK_AFTER) {
if (recovered || !startReader()) return CARD_READER_STUCK;
recovered = true;
noAckRun = 0;
}
if (cancel && cancel()) return CARD_NONE;
if (timeoutMs && millis() - start > timeoutMs) return CARD_NONE;
if (idle) idle();
Expand Down
1 change: 1 addition & 0 deletions cyberpunkunicorn-os/apps/frequency/card.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum CardStatus {
CARD_LOCKED, // already write-locked
CARD_WRITE_FAILED,
CARD_VERIFY_FAILED, // wrote, but read back something else
CARD_READER_STUCK, // PN532 answers its address but not commands, even after a bus recovery
};
const char *cardStatusText(CardStatus s);

Expand Down
5 changes: 3 additions & 2 deletions cyberpunkunicorn-os/apps/frequency/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ void printTag(const CardInfo &c) {

bool waitTag(CardInfo &c, uint32_t timeout) {
out("Tap a tag (" + String(timeout) + " ms)...");
if (waitCard(c, timeout, nullptr) == CARD_OK) return true;
out("No tag.");
CardStatus s = waitCard(c, timeout, nullptr);
if (s == CARD_OK) return true;
out(s == CARD_READER_STUCK ? String("ERROR: ") + cardStatusText(s) : String("No tag."));
return false;
}

Expand Down
5 changes: 5 additions & 0 deletions cyberpunkunicorn-os/apps/hat/hat_probe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ SemaphoreHandle_t hatBus() {
}
} // namespace

bool hatI2CRecover() {
recoverI2C();
return linesIdle();
}

bool hatBusTake(uint32_t waitMs) { return xSemaphoreTakeRecursive(hatBus(), pdMS_TO_TICKS(waitMs)) == pdTRUE; }

void hatBusGive() { xSemaphoreGiveRecursive(hatBus()); }
Expand Down
4 changes: 4 additions & 0 deletions cyberpunkunicorn-os/apps/hat/hat_probe.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ struct HatProbeResult {
bool hatBusTake(uint32_t waitMs);
void hatBusGive();

// Clocks a chip stuck mid-byte off SDA (the standard 9-clock recovery) and says
// whether both lines then idle high. Call with no Wire instance running on 22/27.
bool hatI2CRecover();

// While another task owns the bus, returns the last result without touching it.
HatProbeResult probeHat();
// allowSpi = false: I2C scan only, never drives 22/27 as SPI or reads the RX line
Expand Down
18 changes: 15 additions & 3 deletions cyberpunkunicorn-os/apps/remote/app_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,34 @@ void putCard(JsonObject o, const freq::CardInfo &c) {
bool tap(remote::Request &r, freq::CardInfo &card) {
freq::CardStatus s = freq::readerOpen();
if (s != freq::CARD_OK) {
r.fail(s == freq::CARD_NO_READER ? "no_reader" : "busy", freq::cardStatusText(s));
r.fail(
s == freq::CARD_NO_READER ? "no_reader"
: s == freq::CARD_READER_STUCK ? "reader_stuck"
: "busy",
freq::cardStatusText(s)
);
return false;
}
uint32_t timeout = r.args["timeout_ms"] | TAP_TIMEOUT_MS;
if (timeout > 30000) timeout = 30000;
remote::emit("frequency.waiting_for_tag", JsonObjectConst());
freq::resetPollStats();
uint32_t started = millis();
s = freq::waitCard(card, timeout, [] { return false; });
if (s != freq::CARD_OK) {
freq::readerClose();
// What the reader did meanwhile, for the website's device log.
freq::PollStats p = freq::pollStats();
String why = String(freq::cardStatusText(s)) + " (" + p.polls + " polls: " + p.noCard + " no card, " +
p.noAck + " no ack, " + p.noAnswer + " no answer, " + p.selected + " seen)";
p.noAck + " no ack, " + p.noAnswer + " no answer, " + p.selected + " seen, " +
(millis() - started) + " ms)";
Serial.println("[freq] " + why);
r.fail(s == freq::CARD_NONE ? "timeout" : "card_error", why);
r.fail(
s == freq::CARD_NONE ? "timeout"
: s == freq::CARD_READER_STUCK ? "reader_stuck"
: "card_error",
why
);
return false;
}
return true;
Expand Down
2 changes: 1 addition & 1 deletion cyberpunkunicorn-os/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ PIO_ENV=CYD-2432S028
PLATFORMIO_VERSION=6.2.0
ESPTOOL_VERSION=5.4.0
OS_NAME="CyberUnicorn 5000"
OS_VERSION=0.1.3
OS_VERSION=0.1.4

source ./boot.conf
for var in BOOT_TITLE BOOT_TAGLINE; do
Expand Down
Loading