запущен тест между есп с перемычкой
This commit is contained in:
@@ -4,12 +4,13 @@
|
||||
#include <WiFi.h>
|
||||
#include <esp_mac.h>
|
||||
#include <esp_system.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace {
|
||||
const char *appStateName(AppState state) {
|
||||
static const char *names[] = {"IDLE", "MENU", "SOLO_MEASURE", "MASTER_DISCOVER",
|
||||
"MASTER_WAIT_READY", "MASTER_WAIT_RESULT", "SLAVE_READY", "SLAVE_WAIT_START",
|
||||
"MASTER_WAIT_READY", "MASTER_WAIT_RESULT", "MASTER_FINALIZE", "SLAVE_READY", "SLAVE_WAIT_START",
|
||||
"SLAVE_MEASURE", "SLAVE_WAIT_ACK", "FINISHED"};
|
||||
const uint8_t index = static_cast<uint8_t>(state);
|
||||
return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN";
|
||||
@@ -20,6 +21,11 @@ const char *buttonEventName(ButtonEvent event) {
|
||||
const uint8_t index = static_cast<uint8_t>(event);
|
||||
return index < sizeof(names) / sizeof(names[0]) ? names[index] : "UNKNOWN";
|
||||
}
|
||||
|
||||
void formatErrorDuty(float duty, char *out, size_t size) {
|
||||
if (fabsf(duty - roundf(duty)) < 0.05f) snprintf(out, size, "%.0f%%", duty);
|
||||
else snprintf(out, size, "%.1f%%", duty);
|
||||
}
|
||||
}
|
||||
|
||||
App::App() : startButton_(GPIO_BUTTON_START), modeButton_(GPIO_BUTTON_MODE), measurement_(receiver_) {}
|
||||
@@ -81,7 +87,7 @@ void App::update() {
|
||||
state_ = AppState::MENU; menuItem_ = 0; Log::event("ACTION", "settings menu entered"); showMenu();
|
||||
} else if (startEvent == ButtonEvent::SHORT) { Log::event("ACTION", "test start requested"); startTest(); }
|
||||
else if (state_ == AppState::FINISHED && static_cast<Role>(settings_.role) == Role::SLAVE &&
|
||||
now >= slaveRearmAtMs_) armSlave();
|
||||
now >= slaveRearmAtMs_) armSlave(pendingReason_ != FailReason::NONE);
|
||||
return;
|
||||
}
|
||||
if (state_ == AppState::SLAVE_READY && modeEvent != ButtonEvent::NONE) {
|
||||
@@ -111,13 +117,22 @@ void App::update() {
|
||||
}
|
||||
if (state_ == AppState::SOLO_MEASURE) {
|
||||
const MeasureState ms = measurement_.update();
|
||||
if (ms == MeasureState::FAIL) { printStageStats(measurement_.stats(), actual_.actualHz); finish(false, measurement_.reason()); }
|
||||
if (ms == MeasureState::FAIL) {
|
||||
printStageStats(measurement_.stats(), actual_.actualHz);
|
||||
showStageResult(measurement_.stats());
|
||||
finish(false, measurement_.reason(), true);
|
||||
}
|
||||
else if (ms == MeasureState::PASS) {
|
||||
printStageStats(measurement_.stats(), actual_.actualHz);
|
||||
showStageResult(measurement_.stats());
|
||||
stagePassed();
|
||||
} else if (ms == MeasureState::STEP_READY) {
|
||||
StageStats live = {};
|
||||
if (measurement_.statsSnapshot(live)) showStageResult(live);
|
||||
measurement_.continueAfterDisplay();
|
||||
}
|
||||
} else if (state_ == AppState::MASTER_DISCOVER || state_ == AppState::MASTER_WAIT_READY ||
|
||||
state_ == AppState::MASTER_WAIT_RESULT) {
|
||||
state_ == AppState::MASTER_WAIT_RESULT || state_ == AppState::MASTER_FINALIZE) {
|
||||
handleRadio(); updateMaster();
|
||||
} else {
|
||||
handleRadio(); updateSlave();
|
||||
@@ -195,7 +210,7 @@ void App::startTest() {
|
||||
else { state_ = AppState::SLAVE_READY; Log::event("TEST", "Slave armed and waiting for Master"); display_.show("SLAVE READY", "WAIT MASTER"); }
|
||||
}
|
||||
|
||||
bool App::armSlave() {
|
||||
bool App::armSlave(bool preserveDisplay) {
|
||||
params_ = store_.params(settings_);
|
||||
stageIndex_ = 0; stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz);
|
||||
requestedHz_ = 0; session_ = 0; sequence_ = 0; havePeer_ = false;
|
||||
@@ -208,12 +223,13 @@ bool App::armSlave() {
|
||||
}
|
||||
radio_.flush(); state_ = AppState::SLAVE_READY;
|
||||
Log::event("TEST", "Slave automatically armed and waiting for Master");
|
||||
display_.show("SLAVE READY", "WAIT MASTER");
|
||||
if (!preserveDisplay) display_.show("SLAVE READY", "WAIT MASTER");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::prepareStage() {
|
||||
bool App::prepareStage(bool showProgress) {
|
||||
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_);
|
||||
actual_ = {};
|
||||
const uint32_t maxHz = TARGET_IS_C3 ? C3_STRICT_MAX_HZ :
|
||||
(receiver_.highRateBackend() ? S3_STRICT_MAX_HZ : C3_STRICT_MAX_HZ);
|
||||
if (requestedHz_ > maxHz) { finish(false, FailReason::UNSUPPORTED); return false; }
|
||||
@@ -232,9 +248,7 @@ bool App::prepareStage() {
|
||||
}
|
||||
Log::printf("PWM", "stage=%lu/%lu requested=%luHz actual=%luHz duty=%.2f%% bits=%u STARTED",
|
||||
stageIndex_ + 1, stageCount_, requestedHz_, actual_.actualHz, actual_.actualDutyPct, actual_.bits);
|
||||
char f[12], one[24], two[24]; Display::formatFrequency(actual_.actualHz, f, sizeof(f));
|
||||
snprintf(one, sizeof(one), "F %s D %.1f%%", f, actual_.actualDutyPct);
|
||||
snprintf(two, sizeof(two), "%lu/%lu RUN", stageIndex_ + 1, stageCount_); display_.show(one, two);
|
||||
if (showProgress) showStageProgress();
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO && !startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) {
|
||||
finish(false, FailReason::UNSUPPORTED); return false;
|
||||
}
|
||||
@@ -257,6 +271,8 @@ void App::stagePassed() {
|
||||
if (static_cast<Role>(settings_.role) == Role::SOLO) { if (prepareStage()) state_ = AppState::SOLO_MEASURE; }
|
||||
else if (static_cast<Role>(settings_.role) == Role::MASTER) {
|
||||
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_);
|
||||
actual_ = {};
|
||||
stageStartConfirmed_ = false;
|
||||
pendingPacket_ = makePacket(MessageType::PREPARE); sendCurrent(MessageType::PREPARE);
|
||||
state_ = AppState::MASTER_WAIT_READY; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS;
|
||||
}
|
||||
@@ -273,9 +289,11 @@ void App::startMasterDiscovery() {
|
||||
|
||||
ProtocolPacket App::makePacket(MessageType type) const {
|
||||
ProtocolPacket p = {};
|
||||
p.type = static_cast<uint8_t>(type); p.session = session_; p.stage = stageIndex_; p.sequence = sequence_;
|
||||
p.type = static_cast<uint8_t>(type); p.session = session_; p.stage = stageIndex_;
|
||||
p.stageCount = static_cast<uint16_t>(stageCount_); p.sequence = sequence_;
|
||||
p.requestedHz = requestedHz_; p.actualHz = actual_.actualHz;
|
||||
p.actualDutyX100 = static_cast<uint16_t>(actual_.actualDutyPct * 100.0f + 0.5f);
|
||||
const float packetDuty = actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct;
|
||||
p.actualDutyX100 = static_cast<uint16_t>(packetDuty * 100.0f + 0.5f);
|
||||
p.testTimeMs = params_.testTimeMs; p.repeats = params_.repeats;
|
||||
p.accuracyX100 = static_cast<uint16_t>(params_.accuracyPct * 100.0f + 0.5f); p.settleCycles = PWM_SETTLE_CYCLES;
|
||||
return p;
|
||||
@@ -283,13 +301,19 @@ ProtocolPacket App::makePacket(MessageType type) const {
|
||||
|
||||
void App::sendCurrent(MessageType type) {
|
||||
++sequence_; pendingPacket_ = makePacket(type);
|
||||
const bool ok = radio_.sendBroadcast(pendingPacket_); lastSendMs_ = millis();
|
||||
const bool ok = sendLinked(pendingPacket_); lastSendMs_ = millis();
|
||||
if (!ok) Log::printf("ESP-NOW", "sendCurrent %s FAILED", messageName(type));
|
||||
}
|
||||
|
||||
bool App::sendLinked(ProtocolPacket packet) {
|
||||
return havePeer_ ? radio_.sendTo(peer_, packet) : radio_.sendBroadcast(packet);
|
||||
}
|
||||
|
||||
void App::updateHeartbeat() {
|
||||
if (!havePeer_ || state_ == AppState::FINISHED) return;
|
||||
const uint32_t now = millis();
|
||||
const uint32_t radioRxMs = radio_.lastReceiveMs();
|
||||
if (radioRxMs && now - radioRxMs < now - lastPeerSeenMs_) lastPeerSeenMs_ = radioRxMs;
|
||||
if (now - lastPeerSeenMs_ >= LINK_HEARTBEAT_TIMEOUT_MS) {
|
||||
Log::event("ESP-NOW", "peer heartbeat timeout");
|
||||
finish(false, FailReason::LINK_LOST);
|
||||
@@ -299,7 +323,7 @@ void App::updateHeartbeat() {
|
||||
now - lastHeartbeatMs_ >= LINK_HEARTBEAT_INTERVAL_MS) {
|
||||
ProtocolPacket heartbeat = makePacket(MessageType::HEARTBEAT);
|
||||
heartbeat.sequence = sequence_;
|
||||
radio_.sendBroadcast(heartbeat);
|
||||
sendLinked(heartbeat);
|
||||
lastHeartbeatMs_ = now;
|
||||
}
|
||||
}
|
||||
@@ -312,7 +336,7 @@ void App::handleRadio() {
|
||||
ReceivedPacket r;
|
||||
while (radio_.receive(r)) {
|
||||
const MessageType type = static_cast<MessageType>(r.packet.type);
|
||||
if (type != MessageType::HEARTBEAT && type != MessageType::HEARTBEAT_ACK &&
|
||||
if (type != MessageType::HEARTBEAT && type != MessageType::HEARTBEAT_ACK && type != MessageType::PROGRESS &&
|
||||
state_ != AppState::SLAVE_MEASURE)
|
||||
Log::printf("ESP-NOW", "RX %s session=%08lX stage=%u seq=%u",
|
||||
messageName(type), r.packet.session, r.packet.stage, r.packet.sequence);
|
||||
@@ -320,7 +344,7 @@ void App::handleRadio() {
|
||||
type == MessageType::DISCOVER && (!havePeer_ || !memcmp(peer_, r.mac, 6))) {
|
||||
memcpy(peer_, r.mac, 6); havePeer_ = true; session_ = r.packet.session; stageIndex_ = 0; sequence_ = r.packet.sequence;
|
||||
lastPeerSeenMs_ = millis();
|
||||
ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK); ack.sequence = r.packet.sequence; radio_.sendBroadcast(ack);
|
||||
ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK); ack.sequence = r.packet.sequence; sendLinked(ack);
|
||||
state_ = AppState::SLAVE_WAIT_START; display_.show("MASTER SEEN", "ACK SENT"); continue;
|
||||
}
|
||||
if (state_ == AppState::MASTER_DISCOVER && type == MessageType::DISCOVER_ACK && r.packet.session == session_) {
|
||||
@@ -332,37 +356,97 @@ void App::handleRadio() {
|
||||
if (havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_) lastPeerSeenMs_ = millis();
|
||||
if (havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_ &&
|
||||
type == MessageType::HEARTBEAT) {
|
||||
ProtocolPacket ack = makePacket(MessageType::HEARTBEAT_ACK);
|
||||
ack.sequence = r.packet.sequence; radio_.sendBroadcast(ack); continue;
|
||||
continue; // Radio's priority heartbeat task has already sent the ACK.
|
||||
}
|
||||
if (type == MessageType::HEARTBEAT_ACK) continue;
|
||||
if (havePeer_ && !memcmp(peer_, r.mac, 6) && type == MessageType::RESULT &&
|
||||
r.packet.session == session_ && r.packet.stage < stageIndex_) {
|
||||
ProtocolPacket ack = {}; ack.type = static_cast<uint8_t>(MessageType::ACK);
|
||||
ack.session = session_; ack.stage = r.packet.stage; ack.sequence = r.packet.sequence;
|
||||
radio_.sendBroadcast(ack); continue; // idempotent ACK for a retried old result
|
||||
sendLinked(ack); continue; // idempotent ACK for a retried old result
|
||||
}
|
||||
const bool matchingPeer = havePeer_ && !memcmp(peer_, r.mac, 6) && r.packet.session == session_;
|
||||
if (matchingPeer && type == MessageType::PREPARE) {
|
||||
const bool expected = state_ == AppState::SLAVE_WAIT_START && r.packet.stage == stageIndex_;
|
||||
const bool implicitAck = state_ == AppState::SLAVE_WAIT_ACK && pendingPacket_.passed &&
|
||||
r.packet.stage == static_cast<uint16_t>(pendingPacket_.stage + 1U);
|
||||
if (expected || implicitAck) {
|
||||
stageIndex_ = r.packet.stage;
|
||||
sequence_ = r.packet.sequence;
|
||||
state_ = AppState::SLAVE_WAIT_START;
|
||||
params_.testTimeMs = r.packet.testTimeMs; params_.repeats = r.packet.repeats;
|
||||
params_.accuracyPct = r.packet.accuracyX100 / 100.0f; requestedHz_ = r.packet.requestedHz;
|
||||
stageCount_ = r.packet.stageCount;
|
||||
actual_ = {};
|
||||
ProtocolPacket ready = makePacket(MessageType::READY);
|
||||
ready.sequence = r.packet.sequence; sendLinked(ready);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!havePeer_ || memcmp(peer_, r.mac, 6) || !packetForCurrent(r.packet)) continue;
|
||||
if (type == MessageType::ABORT) { finish(false, FailReason::ABORTED); continue; }
|
||||
if (type == MessageType::ABORT) {
|
||||
const FailReason reason = r.packet.reason > static_cast<uint8_t>(FailReason::NONE) &&
|
||||
r.packet.reason <= static_cast<uint8_t>(FailReason::ABORTED)
|
||||
? static_cast<FailReason>(r.packet.reason) : FailReason::ABORTED;
|
||||
if (r.packet.requestedHz) requestedHz_ = r.packet.requestedHz;
|
||||
actual_.actualHz = r.packet.actualHz ? r.packet.actualHz : requestedHz_;
|
||||
actual_.actualDutyPct = r.packet.actualDutyX100 ? r.packet.actualDutyX100 / 100.0f : params_.dutyPct;
|
||||
measurement_.abort(); finish(false, reason); continue;
|
||||
}
|
||||
if (state_ == AppState::MASTER_WAIT_READY && type == MessageType::READY) {
|
||||
if (!prepareStage()) continue;
|
||||
sendCurrent(MessageType::START_STAGE); state_ = AppState::MASTER_WAIT_RESULT; retries_ = 0; deadlineMs_ = millis() +
|
||||
params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS + (1000UL * PWM_SETTLE_CYCLES / actual_.actualHz) + 20;
|
||||
if (!prepareStage(false)) continue;
|
||||
sendCurrent(MessageType::START_STAGE); state_ = AppState::MASTER_WAIT_RESULT;
|
||||
stageStartConfirmed_ = false; retries_ = 0;
|
||||
deadlineMs_ = millis() + LINK_RETRY_INTERVAL_MS;
|
||||
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::READY &&
|
||||
r.packet.sequence == pendingPacket_.sequence) {
|
||||
if (!stageStartConfirmed_) showStageProgress();
|
||||
stageStartConfirmed_ = true;
|
||||
deadlineMs_ = millis() + params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS +
|
||||
(1000UL * PWM_SETTLE_CYCLES / actual_.actualHz) + 20;
|
||||
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::PROGRESS) {
|
||||
stageStartConfirmed_ = true;
|
||||
deadlineMs_ = millis() + params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS;
|
||||
showRemoteResult(r.packet);
|
||||
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::RESULT) {
|
||||
ProtocolPacket ack = makePacket(MessageType::ACK); ack.sequence = r.packet.sequence; radio_.sendBroadcast(ack); pwm_.stop();
|
||||
if (!r.packet.passed) finish(false, static_cast<FailReason>(r.packet.reason)); else stagePassed();
|
||||
} else if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::PREPARE) {
|
||||
params_.testTimeMs = r.packet.testTimeMs; params_.repeats = r.packet.repeats;
|
||||
params_.accuracyPct = r.packet.accuracyX100 / 100.0f; requestedHz_ = r.packet.requestedHz;
|
||||
ProtocolPacket ready = makePacket(MessageType::READY); ready.sequence = r.packet.sequence; radio_.sendBroadcast(ready);
|
||||
display_.show("SLAVE LINKED", "MASTER ONLINE");
|
||||
ProtocolPacket ack = makePacket(MessageType::ACK); ack.sequence = r.packet.sequence;
|
||||
ack.passed = r.packet.passed && stageIndex_ + 1U >= stageCount_;
|
||||
sendLinked(ack); pwm_.stop();
|
||||
showRemoteResult(r.packet);
|
||||
if (!r.packet.passed) {
|
||||
finish(false, static_cast<FailReason>(r.packet.reason), true);
|
||||
} else if (ack.passed) {
|
||||
pendingPacket_ = ack;
|
||||
state_ = AppState::MASTER_FINALIZE; retries_ = 0;
|
||||
deadlineMs_ = millis() + FINAL_ACK_RETRY_INTERVAL_MS;
|
||||
} else stagePassed();
|
||||
} else if (state_ == AppState::MASTER_FINALIZE && type == MessageType::RESULT) {
|
||||
// The Slave did not receive the final ACK and repeated RESULT.
|
||||
sendLinked(pendingPacket_);
|
||||
} else if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::START_STAGE) {
|
||||
sequence_ = r.packet.sequence;
|
||||
actual_.actualHz = r.packet.actualHz; actual_.actualDutyPct = r.packet.actualDutyX100 / 100.0f;
|
||||
if (!startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) { finish(false, FailReason::UNSUPPORTED); continue; }
|
||||
showStageProgress();
|
||||
state_ = AppState::SLAVE_MEASURE; deadlineMs_ = millis() + params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS;
|
||||
ProtocolPacket started = makePacket(MessageType::READY);
|
||||
started.sequence = r.packet.sequence; sendLinked(started);
|
||||
} else if (state_ == AppState::SLAVE_MEASURE && type == MessageType::START_STAGE) {
|
||||
// START_STAGE or its acknowledgement was lost. Do not restart the
|
||||
// measurement; only confirm the already running stage again.
|
||||
ProtocolPacket started = makePacket(MessageType::READY);
|
||||
started.sequence = r.packet.sequence; sendLinked(started);
|
||||
} else if (state_ == AppState::SLAVE_WAIT_ACK && type == MessageType::ACK && r.packet.sequence == pendingPacket_.sequence) {
|
||||
if (pendingPacket_.passed) { ++stageIndex_; state_ = AppState::SLAVE_WAIT_START; display_.show("SLAVE READY", "WAIT PREPARE"); }
|
||||
else finish(false, static_cast<FailReason>(pendingPacket_.reason));
|
||||
if (pendingPacket_.passed) {
|
||||
if (r.packet.passed) {
|
||||
radio_.end(); pendingReason_ = FailReason::NONE;
|
||||
if (armSlave(true)) display_.show("SLAVE PASS", "WAIT MASTER");
|
||||
} else {
|
||||
stageIndex_ = static_cast<uint32_t>(r.packet.stage) + 1U;
|
||||
state_ = AppState::SLAVE_WAIT_START;
|
||||
}
|
||||
}
|
||||
else finish(false, static_cast<FailReason>(pendingPacket_.reason), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,14 +460,23 @@ void App::updateMaster() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state_ == AppState::MASTER_FINALIZE) {
|
||||
if (now < deadlineMs_) return;
|
||||
if (retries_++ < FINAL_ACK_RETRIES) {
|
||||
sendLinked(pendingPacket_);
|
||||
deadlineMs_ = now + FINAL_ACK_RETRY_INTERVAL_MS;
|
||||
} else finish(true, FailReason::NONE);
|
||||
return;
|
||||
}
|
||||
updateHeartbeat();
|
||||
if (state_ == AppState::FINISHED) return;
|
||||
if (now < deadlineMs_) return;
|
||||
if (retries_ >= LINK_PACKET_RETRIES) { finish(false, FailReason::LINK_LOST); return; }
|
||||
Log::printf("ESP-NOW", "%s retry=%u", messageName(static_cast<MessageType>(pendingPacket_.type)), retries_ + 1);
|
||||
radio_.sendBroadcast(pendingPacket_); ++retries_;
|
||||
sendLinked(pendingPacket_); ++retries_;
|
||||
deadlineMs_ = now + (state_ == AppState::MASTER_WAIT_RESULT ?
|
||||
params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS : LINK_REPLY_TIMEOUT_MS);
|
||||
(stageStartConfirmed_ ? params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS : LINK_RETRY_INTERVAL_MS) :
|
||||
LINK_REPLY_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
void App::updateSlave() {
|
||||
@@ -391,35 +484,62 @@ void App::updateSlave() {
|
||||
if (state_ == AppState::FINISHED) return;
|
||||
if (state_ == AppState::SLAVE_MEASURE) {
|
||||
const MeasureState ms = measurement_.update();
|
||||
if (ms != MeasureState::PASS && ms != MeasureState::FAIL) return;
|
||||
if (ms == MeasureState::STEP_READY) {
|
||||
StageStats live = {};
|
||||
if (measurement_.statsSnapshot(live)) {
|
||||
ProtocolPacket progress = makePacket(MessageType::PROGRESS);
|
||||
fillMeasuredResult(progress, live);
|
||||
progress.sequence = sequence_; sendLinked(progress);
|
||||
// oled.display() is synchronous. Resume capture only after the full
|
||||
// framebuffer has reached the display.
|
||||
showStageResult(live);
|
||||
}
|
||||
measurement_.continueAfterDisplay();
|
||||
return;
|
||||
}
|
||||
if (ms != MeasureState::PASS && ms != MeasureState::FAIL) {
|
||||
return;
|
||||
}
|
||||
printStageStats(measurement_.stats(), actual_.actualHz);
|
||||
showStageResult(measurement_.stats());
|
||||
pendingPacket_ = makePacket(MessageType::RESULT);
|
||||
pendingPacket_.passed = ms == MeasureState::PASS && measurement_.reason() == FailReason::NONE;
|
||||
pendingPacket_.reason = static_cast<uint8_t>(measurement_.reason()); pendingPacket_.periods = measurement_.stats().periods;
|
||||
fillMeasuredResult(pendingPacket_, measurement_.stats());
|
||||
pendingPacket_.minPeriodTicks = measurement_.stats().minPeriod; pendingPacket_.maxPeriodTicks = measurement_.stats().maxPeriod;
|
||||
pendingPacket_.sequence = ++sequence_; radio_.sendBroadcast(pendingPacket_);
|
||||
pendingPacket_.sequence = ++sequence_; sendLinked(pendingPacket_);
|
||||
Log::printf("TEST", "Slave result prepared: %s reason=%s periods=%lu",
|
||||
pendingPacket_.passed ? "PASS" : "FAIL", failName(static_cast<FailReason>(pendingPacket_.reason)), pendingPacket_.periods);
|
||||
state_ = AppState::SLAVE_WAIT_ACK; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS;
|
||||
} else if (state_ == AppState::SLAVE_WAIT_ACK && millis() >= deadlineMs_) {
|
||||
if (retries_++ >= LINK_PACKET_RETRIES) finish(false, FailReason::LINK_LOST);
|
||||
else { Log::printf("ESP-NOW", "RESULT retry=%u", retries_); radio_.sendBroadcast(pendingPacket_); deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; }
|
||||
else { Log::printf("ESP-NOW", "RESULT retry=%u", retries_); sendLinked(pendingPacket_); deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; }
|
||||
}
|
||||
}
|
||||
|
||||
void App::sendAbort() { if (havePeer_) sendCurrent(MessageType::ABORT); }
|
||||
void App::sendAbort(FailReason reason) {
|
||||
if (!havePeer_) return;
|
||||
++sequence_;
|
||||
ProtocolPacket packet = makePacket(MessageType::ABORT);
|
||||
packet.reason = static_cast<uint8_t>(reason);
|
||||
if (!packet.actualDutyX100) packet.actualDutyX100 = params_.dutyPct * 100U;
|
||||
sendLinked(packet);
|
||||
}
|
||||
|
||||
void App::abortTest() {
|
||||
Log::event("ACTION", "abort requested: sending ABORT, stopping receiver and PWM");
|
||||
sendAbort(); measurement_.abort(); finish(false, FailReason::ABORTED);
|
||||
sendAbort(FailReason::ABORTED); measurement_.abort(); finish(false, FailReason::ABORTED);
|
||||
}
|
||||
|
||||
void App::finish(bool pass, FailReason reason) {
|
||||
void App::finish(bool pass, FailReason reason, bool preserveDisplay) {
|
||||
Log::printf("TEST", "finishing result=%s reason=%s", pass ? "PASS" : "FAIL", failName(reason));
|
||||
const AppState failedState = state_;
|
||||
const bool masterLinkLost = reason == FailReason::LINK_LOST &&
|
||||
(failedState == AppState::MASTER_DISCOVER || failedState == AppState::MASTER_WAIT_READY ||
|
||||
failedState == AppState::MASTER_WAIT_RESULT);
|
||||
failedState == AppState::MASTER_WAIT_RESULT || failedState == AppState::MASTER_FINALIZE);
|
||||
const bool masterActive = failedState == AppState::MASTER_DISCOVER ||
|
||||
failedState == AppState::MASTER_WAIT_READY || failedState == AppState::MASTER_WAIT_RESULT ||
|
||||
failedState == AppState::MASTER_FINALIZE;
|
||||
const bool slaveLinkLost = reason == FailReason::LINK_LOST &&
|
||||
(failedState == AppState::SLAVE_READY || failedState == AppState::SLAVE_WAIT_START ||
|
||||
failedState == AppState::SLAVE_MEASURE || failedState == AppState::SLAVE_WAIT_ACK);
|
||||
@@ -429,18 +549,32 @@ void App::finish(bool pass, FailReason reason) {
|
||||
startMasterDiscovery();
|
||||
return;
|
||||
}
|
||||
if (!pass && masterActive && havePeer_ && reason != FailReason::ABORTED) sendAbort(reason);
|
||||
if (state_ != AppState::IDLE && state_ != AppState::MENU) radio_.end();
|
||||
state_ = AppState::FINISHED; pendingReason_ = reason;
|
||||
if (slaveLinkLost) {
|
||||
if (armSlave()) display_.show("MASTER LOST", "WAIT MASTER");
|
||||
char target[12], one[24];
|
||||
Display::formatTestFrequency(actual_.actualHz ? actual_.actualHz : requestedHz_, target, sizeof(target));
|
||||
snprintf(one, sizeof(one), "FAIL %s %.0f%%", target,
|
||||
actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct);
|
||||
display_.show(one, failName(reason), stageIndex_ + 1, stageCount_);
|
||||
armSlave(true);
|
||||
return;
|
||||
}
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) slaveRearmAtMs_ = millis() + 2000;
|
||||
if (preserveDisplay) return;
|
||||
char one[24];
|
||||
if (pass) display_.show("PASS", "REPEAT");
|
||||
if (pass) {
|
||||
const Role role = static_cast<Role>(settings_.role);
|
||||
snprintf(one, sizeof(one), "%s PASS", roleName(role));
|
||||
display_.show(one, role == Role::SLAVE ? "WAIT MASTER" : "START=REPEAT");
|
||||
}
|
||||
else if (requestedHz_) {
|
||||
char frequency[12]; Display::formatFrequency(requestedHz_, frequency, sizeof(frequency));
|
||||
snprintf(one, sizeof(one), "FAIL %s", frequency); display_.show(one, failName(reason));
|
||||
char frequency[12];
|
||||
Display::formatTestFrequency(actual_.actualHz ? actual_.actualHz : requestedHz_, frequency, sizeof(frequency));
|
||||
snprintf(one, sizeof(one), "FAIL %s %.0f%%", frequency,
|
||||
actual_.actualDutyPct > 0.0f ? actual_.actualDutyPct : params_.dutyPct);
|
||||
display_.show(one, failName(reason), stageIndex_ + 1, stageCount_);
|
||||
} else {
|
||||
display_.show("TEST FAILED", failName(reason));
|
||||
}
|
||||
@@ -481,3 +615,85 @@ void App::printStageStats(const StageStats &s, uint32_t hz) {
|
||||
requestedText, status, s.periods, measuredText, measuredDuty, s.droppedItems,
|
||||
s.reason == FailReason::NONE ? "" : " reason=", s.reason == FailReason::NONE ? "" : failName(s.reason));
|
||||
}
|
||||
|
||||
void App::showStageResult(const StageStats &s) {
|
||||
char one[24], two[24];
|
||||
char target[12]; Display::formatTestFrequency(actual_.actualHz, target, sizeof(target));
|
||||
if (s.reason != FailReason::NONE) {
|
||||
snprintf(one, sizeof(one), "FAIL %s %.0f%%", target, actual_.actualDutyPct);
|
||||
if (s.reason == FailReason::PERIOD_OUT && s.badFrequency > 0.0f) {
|
||||
char frequency[12];
|
||||
Display::formatTestFrequency(static_cast<uint32_t>(lroundf(s.badFrequency)), frequency, sizeof(frequency));
|
||||
snprintf(two, sizeof(two), "PERIOD OUT %s", frequency);
|
||||
} else if (s.reason == FailReason::DUTY_OUT && s.badFrequency > 0.0f) {
|
||||
char duty[10]; formatErrorDuty(s.badDuty, duty, sizeof(duty));
|
||||
snprintf(two, sizeof(two), "DUTY OUT %s", duty);
|
||||
} else {
|
||||
snprintf(two, sizeof(two), "%s", failName(s.reason));
|
||||
}
|
||||
display_.show(one, two, stageIndex_ + 1, stageCount_);
|
||||
return;
|
||||
}
|
||||
snprintf(one, sizeof(one), "Test:%-6s %2.0f%% %2lu/%2lu",
|
||||
target, actual_.actualDutyPct, stageIndex_ + 1, stageCount_);
|
||||
if (!s.periods || !s.periodSum) {
|
||||
display_.show(one, "F:--- D:---%", stageIndex_ + 1, stageCount_);
|
||||
return;
|
||||
}
|
||||
const float measuredHz = static_cast<float>(receiver_.tickHz()) * s.periods / s.periodSum;
|
||||
const float measuredDuty = 100.0f * s.activeSum / s.periodSum;
|
||||
char frequency[12]; Display::formatFrequency(measuredHz, frequency, sizeof(frequency));
|
||||
snprintf(two, sizeof(two), "F:%-8s D:%4.1f%%", frequency, measuredDuty);
|
||||
display_.show(one, two, stageIndex_ + 1, stageCount_);
|
||||
}
|
||||
|
||||
void App::showRemoteResult(const ProtocolPacket &packet) {
|
||||
const FailReason reason = packet.reason <= static_cast<uint8_t>(FailReason::ABORTED)
|
||||
? static_cast<FailReason>(packet.reason) : FailReason::UNSUPPORTED;
|
||||
char target[12], one[24], two[24];
|
||||
Display::formatTestFrequency(packet.actualHz ? packet.actualHz : packet.requestedHz,
|
||||
target, sizeof(target));
|
||||
if (reason == FailReason::NONE) {
|
||||
snprintf(one, sizeof(one), "Test:%-6s %2.0f%% %2lu/%2lu",
|
||||
target, packet.actualDutyX100 / 100.0f, stageIndex_ + 1, stageCount_);
|
||||
if (packet.measuredHzX10) {
|
||||
char measured[12];
|
||||
Display::formatFrequency(packet.measuredHzX10 / 10.0f, measured, sizeof(measured));
|
||||
snprintf(two, sizeof(two), "F:%-8s D:%4.1f%%", measured, packet.measuredDutyX10 / 10.0f);
|
||||
} else snprintf(two, sizeof(two), "F:--- D:---%%");
|
||||
} else if (reason == FailReason::PERIOD_OUT && packet.measuredHzX10) {
|
||||
snprintf(one, sizeof(one), "FAIL %s %.0f%%", target, packet.actualDutyX100 / 100.0f);
|
||||
char measured[12];
|
||||
Display::formatTestFrequency((packet.measuredHzX10 + 5U) / 10U, measured, sizeof(measured));
|
||||
snprintf(two, sizeof(two), "PERIOD OUT %s", measured);
|
||||
} else if (reason == FailReason::DUTY_OUT && packet.measuredDutyX10) {
|
||||
snprintf(one, sizeof(one), "FAIL %s %.0f%%", target, packet.actualDutyX100 / 100.0f);
|
||||
char duty[10]; formatErrorDuty(packet.measuredDutyX10 / 10.0f, duty, sizeof(duty));
|
||||
snprintf(two, sizeof(two), "DUTY OUT %s", duty);
|
||||
} else {
|
||||
snprintf(one, sizeof(one), "FAIL %s %.0f%%", target, packet.actualDutyX100 / 100.0f);
|
||||
snprintf(two, sizeof(two), "%s", failName(reason));
|
||||
}
|
||||
display_.show(one, two, stageIndex_ + 1, stageCount_);
|
||||
}
|
||||
|
||||
void App::fillMeasuredResult(ProtocolPacket &packet, const StageStats &stats) const {
|
||||
packet.reason = static_cast<uint8_t>(stats.reason);
|
||||
packet.periods = stats.periods;
|
||||
if (!stats.periods || !stats.periodSum) return;
|
||||
const bool badPeriod = (stats.reason == FailReason::PERIOD_OUT || stats.reason == FailReason::DUTY_OUT) &&
|
||||
stats.badFrequency > 0.0f;
|
||||
const float measuredHz = badPeriod ? stats.badFrequency :
|
||||
static_cast<float>(receiver_.tickHz()) * stats.periods / stats.periodSum;
|
||||
const float measuredDuty = badPeriod ? stats.badDuty : 100.0f * stats.activeSum / stats.periodSum;
|
||||
packet.measuredHzX10 = static_cast<uint32_t>(lroundf(measuredHz * 10.0f));
|
||||
packet.measuredDutyX10 = static_cast<uint16_t>(lroundf(measuredDuty * 10.0f));
|
||||
}
|
||||
|
||||
void App::showStageProgress() {
|
||||
char target[12], one[24];
|
||||
Display::formatTestFrequency(actual_.actualHz, target, sizeof(target));
|
||||
snprintf(one, sizeof(one), "Test:%-6s %2.0f%% %2lu/%2lu",
|
||||
target, actual_.actualDutyPct, stageIndex_ + 1, stageCount_);
|
||||
display_.show(one, "F:--- D:---%", stageIndex_ + 1, stageCount_);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user