Merge remote-tracking branch 'OptoTest/master'
# Conflicts: # OpticalChannelTester/App.cpp
This commit is contained in:
@@ -47,7 +47,9 @@ void App::finishInitialization(bool factoryReset) {
|
||||
initialized_ = true;
|
||||
if (!receiver_.begin()) { Log::event("BOOT", "FATAL: capture peripheral init failed"); finish(false, FailReason::UNSUPPORTED); return; }
|
||||
Log::printf("BOOT", "capture initialized: %s", receiver_.highRateBackend() ? "RMT DMA" : "RMT ping-pong");
|
||||
printConfiguration(); showIdle();
|
||||
printConfiguration();
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
}
|
||||
|
||||
void App::update() {
|
||||
@@ -71,11 +73,26 @@ void App::update() {
|
||||
if (state_ == AppState::IDLE || state_ == AppState::FINISHED) {
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
settings_.role = (settings_.role + 1U) % 3U; const bool saved = store_.save(settings_);
|
||||
params_ = store_.params(settings_); showIdle();
|
||||
params_ = store_.params(settings_);
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
Log::printf("ACTION", "role changed to %s, NVS=%s", roleName(static_cast<Role>(settings_.role)), saved ? "OK" : "FAILED");
|
||||
} else if (modeEvent == ButtonEvent::LONG) {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
if (state_ == AppState::SLAVE_READY && modeEvent != ButtonEvent::NONE) {
|
||||
radio_.end(); havePeer_ = false;
|
||||
if (modeEvent == ButtonEvent::SHORT) {
|
||||
settings_.role = static_cast<uint8_t>(Role::SOLO); const bool saved = store_.save(settings_);
|
||||
params_ = store_.params(settings_); state_ = AppState::IDLE; showIdle();
|
||||
Log::printf("ACTION", "role changed to SOLO, NVS=%s", saved ? "OK" : "FAILED");
|
||||
} else if (modeEvent == ButtonEvent::LONG) {
|
||||
state_ = AppState::MENU; menuItem_ = 0; showMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state_ == AppState::MENU) {
|
||||
@@ -85,7 +102,9 @@ void App::update() {
|
||||
else if (modeEvent == ButtonEvent::LONG) {
|
||||
sanitizeRange(); const bool saved = store_.save(settings_); params_ = store_.params(settings_);
|
||||
Log::printf("ACTION", "settings menu saved and closed, NVS=%s", saved ? "OK" : "FAILED");
|
||||
state_ = AppState::IDLE; printConfiguration(); showIdle();
|
||||
state_ = AppState::IDLE; printConfiguration();
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) armSlave();
|
||||
else showIdle();
|
||||
} else if (startEvent == ButtonEvent::SHORT) changeMenu(+1);
|
||||
else if (startEvent == ButtonEvent::LONG || startEvent == ButtonEvent::REPEAT) changeMenu(-1);
|
||||
return;
|
||||
@@ -153,7 +172,8 @@ void App::showMenu() {
|
||||
|
||||
void App::startTest() {
|
||||
params_ = store_.params(settings_); stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz);
|
||||
stageIndex_ = 0; pendingReason_ = FailReason::NONE;
|
||||
stageIndex_ = 0; requestedHz_ = 0; pendingReason_ = FailReason::NONE;
|
||||
havePeer_ = false; lastHeartbeatMs_ = 0; lastPeerSeenMs_ = 0;
|
||||
if (!stageCount_) { finish(false, FailReason::UNSUPPORTED); return; }
|
||||
Log::printf("TEST", "starting role=%s stages=%lu", roleName(static_cast<Role>(settings_.role)), stageCount_);
|
||||
if (SERIAL_MINIMAL_LOG) {
|
||||
@@ -175,6 +195,23 @@ 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() {
|
||||
params_ = store_.params(settings_);
|
||||
stageIndex_ = 0; stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz);
|
||||
requestedHz_ = 0; session_ = 0; sequence_ = 0; havePeer_ = false;
|
||||
lastHeartbeatMs_ = 0; lastPeerSeenMs_ = 0; retries_ = 0; slaveRearmAtMs_ = 0;
|
||||
if (!radio_.begin()) {
|
||||
state_ = AppState::FINISHED; pendingReason_ = FailReason::LINK_LOST;
|
||||
slaveRearmAtMs_ = millis() + LINK_HEARTBEAT_TIMEOUT_MS;
|
||||
display_.show("LINK FAILED", "RADIO ERROR");
|
||||
return false;
|
||||
}
|
||||
radio_.flush(); state_ = AppState::SLAVE_READY;
|
||||
Log::event("TEST", "Slave automatically armed and waiting for Master");
|
||||
display_.show("SLAVE READY", "WAIT MASTER");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App::prepareStage() {
|
||||
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_);
|
||||
const uint32_t maxHz = TARGET_IS_C3 ? C3_STRICT_MAX_HZ :
|
||||
@@ -226,11 +263,12 @@ void App::stagePassed() {
|
||||
}
|
||||
|
||||
void App::startMasterDiscovery() {
|
||||
session_ = esp_random(); if (!session_) session_ = 1; sequence_ = 1; havePeer_ = false; radio_.flush();
|
||||
session_ = esp_random(); if (!session_) session_ = 1;
|
||||
sequence_ = 1; stageIndex_ = 0; requestedHz_ = 0; havePeer_ = false; radio_.flush();
|
||||
pendingPacket_ = makePacket(MessageType::DISCOVER); radio_.sendBroadcast(pendingPacket_);
|
||||
lastSendMs_ = millis(); deadlineMs_ = millis() + LINK_DISCOVERY_TIMEOUT_MS; retries_ = 0;
|
||||
lastSendMs_ = millis(); retries_ = 0;
|
||||
state_ = AppState::MASTER_DISCOVER; Log::printf("ESP-NOW", "discovery started session=%08lX", session_);
|
||||
display_.show("MASTER SEARCH", "WAIT SLAVE");
|
||||
display_.show("MASTER SEARCH", "HOLD START=STOP");
|
||||
}
|
||||
|
||||
ProtocolPacket App::makePacket(MessageType type) const {
|
||||
@@ -245,10 +283,27 @@ ProtocolPacket App::makePacket(MessageType type) const {
|
||||
|
||||
void App::sendCurrent(MessageType type) {
|
||||
++sequence_; pendingPacket_ = makePacket(type);
|
||||
const bool ok = radio_.sendTo(peer_, pendingPacket_); lastSendMs_ = millis();
|
||||
const bool ok = radio_.sendBroadcast(pendingPacket_); lastSendMs_ = millis();
|
||||
if (!ok) Log::printf("ESP-NOW", "sendCurrent %s FAILED", messageName(type));
|
||||
}
|
||||
|
||||
void App::updateHeartbeat() {
|
||||
if (!havePeer_ || state_ == AppState::FINISHED) return;
|
||||
const uint32_t now = millis();
|
||||
if (now - lastPeerSeenMs_ >= LINK_HEARTBEAT_TIMEOUT_MS) {
|
||||
Log::event("ESP-NOW", "peer heartbeat timeout");
|
||||
finish(false, FailReason::LINK_LOST);
|
||||
return;
|
||||
}
|
||||
if (static_cast<Role>(settings_.role) == Role::MASTER &&
|
||||
now - lastHeartbeatMs_ >= LINK_HEARTBEAT_INTERVAL_MS) {
|
||||
ProtocolPacket heartbeat = makePacket(MessageType::HEARTBEAT);
|
||||
heartbeat.sequence = sequence_;
|
||||
radio_.sendBroadcast(heartbeat);
|
||||
lastHeartbeatMs_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
bool App::packetForCurrent(const ProtocolPacket &p) const {
|
||||
return p.session == session_ && p.stage == stageIndex_;
|
||||
}
|
||||
@@ -257,29 +312,35 @@ void App::handleRadio() {
|
||||
ReceivedPacket r;
|
||||
while (radio_.receive(r)) {
|
||||
const MessageType type = static_cast<MessageType>(r.packet.type);
|
||||
if (state_ != AppState::SLAVE_MEASURE)
|
||||
if (type != MessageType::HEARTBEAT && type != MessageType::HEARTBEAT_ACK &&
|
||||
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);
|
||||
if (state_ == AppState::SLAVE_READY && type == MessageType::DISCOVER) {
|
||||
if ((state_ == AppState::SLAVE_READY || state_ == AppState::SLAVE_WAIT_START) &&
|
||||
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;
|
||||
ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK); ack.sequence = r.packet.sequence; radio_.sendTo(peer_, ack);
|
||||
state_ = AppState::SLAVE_WAIT_START; display_.show("SLAVE LINKED", "WAIT PREPARE"); continue;
|
||||
lastPeerSeenMs_ = millis();
|
||||
ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK); ack.sequence = r.packet.sequence; radio_.sendBroadcast(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_) {
|
||||
memcpy(peer_, r.mac, 6); havePeer_ = true; requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_);
|
||||
memcpy(peer_, r.mac, 6); havePeer_ = true; lastPeerSeenMs_ = lastHeartbeatMs_ = millis();
|
||||
requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_);
|
||||
sendCurrent(MessageType::PREPARE); state_ = AppState::MASTER_WAIT_READY; retries_ = 0; deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS;
|
||||
char mac[20]; Radio::macText(peer_, mac, sizeof(mac)); Log::printf("ESP-NOW", "Slave selected %s", mac); continue;
|
||||
}
|
||||
if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::DISCOVER &&
|
||||
r.packet.session == session_ && !memcmp(peer_, r.mac, 6)) {
|
||||
ProtocolPacket ack = makePacket(MessageType::DISCOVER_ACK);
|
||||
ack.sequence = r.packet.sequence; radio_.sendTo(peer_, ack); continue;
|
||||
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;
|
||||
}
|
||||
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_.sendTo(peer_, ack); continue; // idempotent ACK for a retried old result
|
||||
radio_.sendBroadcast(ack); continue; // idempotent ACK for a retried old result
|
||||
}
|
||||
if (!havePeer_ || memcmp(peer_, r.mac, 6) || !packetForCurrent(r.packet)) continue;
|
||||
if (type == MessageType::ABORT) { finish(false, FailReason::ABORTED); continue; }
|
||||
@@ -288,12 +349,13 @@ void App::handleRadio() {
|
||||
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;
|
||||
} else if (state_ == AppState::MASTER_WAIT_RESULT && type == MessageType::RESULT) {
|
||||
ProtocolPacket ack = makePacket(MessageType::ACK); ack.sequence = r.packet.sequence; radio_.sendTo(peer_, ack); pwm_.stop();
|
||||
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_.sendTo(peer_, ready);
|
||||
ProtocolPacket ready = makePacket(MessageType::READY); ready.sequence = r.packet.sequence; radio_.sendBroadcast(ready);
|
||||
display_.show("SLAVE LINKED", "MASTER ONLINE");
|
||||
} else if (state_ == AppState::SLAVE_WAIT_START && type == MessageType::START_STAGE) {
|
||||
actual_.actualHz = r.packet.actualHz; actual_.actualDutyPct = r.packet.actualDutyX100 / 100.0f;
|
||||
if (!startLocalMeasurement(actual_.actualHz, actual_.actualDutyPct)) { finish(false, FailReason::UNSUPPORTED); continue; }
|
||||
@@ -308,22 +370,25 @@ void App::handleRadio() {
|
||||
void App::updateMaster() {
|
||||
const uint32_t now = millis();
|
||||
if (state_ == AppState::MASTER_DISCOVER) {
|
||||
if (now >= deadlineMs_) { finish(false, FailReason::LINK_LOST); return; }
|
||||
if (now - lastSendMs_ >= LINK_RETRY_INTERVAL_MS) {
|
||||
Log::printf("ESP-NOW", "DISCOVER retry=%u", retries_ + 1); radio_.sendBroadcast(pendingPacket_);
|
||||
lastSendMs_ = now; ++retries_;
|
||||
Log::event("ESP-NOW", "DISCOVER retry"); radio_.sendBroadcast(pendingPacket_);
|
||||
lastSendMs_ = now;
|
||||
}
|
||||
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_.sendTo(peer_, pendingPacket_); ++retries_;
|
||||
radio_.sendBroadcast(pendingPacket_); ++retries_;
|
||||
deadlineMs_ = now + (state_ == AppState::MASTER_WAIT_RESULT ?
|
||||
params_.testTimeMs * params_.repeats + LINK_REPLY_TIMEOUT_MS : LINK_REPLY_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
void App::updateSlave() {
|
||||
updateHeartbeat();
|
||||
if (state_ == AppState::FINISHED) return;
|
||||
if (state_ == AppState::SLAVE_MEASURE) {
|
||||
const MeasureState ms = measurement_.update();
|
||||
if (ms != MeasureState::PASS && ms != MeasureState::FAIL) return;
|
||||
@@ -332,13 +397,13 @@ void App::updateSlave() {
|
||||
pendingPacket_.passed = ms == MeasureState::PASS && measurement_.reason() == FailReason::NONE;
|
||||
pendingPacket_.reason = static_cast<uint8_t>(measurement_.reason()); pendingPacket_.periods = measurement_.stats().periods;
|
||||
pendingPacket_.minPeriodTicks = measurement_.stats().minPeriod; pendingPacket_.maxPeriodTicks = measurement_.stats().maxPeriod;
|
||||
pendingPacket_.sequence = ++sequence_; radio_.sendTo(peer_, pendingPacket_);
|
||||
pendingPacket_.sequence = ++sequence_; radio_.sendBroadcast(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_.sendTo(peer_, pendingPacket_); deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; }
|
||||
else { Log::printf("ESP-NOW", "RESULT retry=%u", retries_); radio_.sendBroadcast(pendingPacket_); deadlineMs_ = millis() + LINK_REPLY_TIMEOUT_MS; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,14 +416,33 @@ void App::abortTest() {
|
||||
|
||||
void App::finish(bool pass, FailReason reason) {
|
||||
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);
|
||||
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);
|
||||
pwm_.stop(); receiver_.stop();
|
||||
if (masterLinkLost) {
|
||||
Log::event("ESP-NOW", "link lost; returning to continuous discovery");
|
||||
startMasterDiscovery();
|
||||
return;
|
||||
}
|
||||
if (state_ != AppState::IDLE && state_ != AppState::MENU) radio_.end();
|
||||
state_ = AppState::FINISHED; pendingReason_ = reason;
|
||||
if (slaveLinkLost) {
|
||||
if (armSlave()) display_.show("MASTER LOST", "WAIT MASTER");
|
||||
return;
|
||||
}
|
||||
if (static_cast<Role>(settings_.role) == Role::SLAVE) slaveRearmAtMs_ = millis() + 2000;
|
||||
char one[24];
|
||||
if (pass) display_.show("PASS", "REPEAT");
|
||||
else {
|
||||
else if (requestedHz_) {
|
||||
char frequency[12]; Display::formatFrequency(requestedHz_, frequency, sizeof(frequency));
|
||||
snprintf(one, sizeof(one), "FAIL %s", frequency); display_.show(one, failName(reason));
|
||||
} else {
|
||||
display_.show("TEST FAILED", failName(reason));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user