149 lines
5.4 KiB
C++
149 lines
5.4 KiB
C++
#include "Dispatcher.h"
|
|
|
|
#if MESH_ENABLE_TX_BEACONS
|
|
#include <new>
|
|
|
|
namespace mesh {
|
|
|
|
struct Dispatcher::BeaconTransmit {
|
|
enum Phase : uint8_t { Pending, Sending, Restoring } phase = Pending;
|
|
RadioProfileParams radio;
|
|
uint8_t bytes[MAX_TRANS_UNIT];
|
|
uint16_t length = 0;
|
|
int8_t power = 0;
|
|
uint32_t retry_at = 0, prepare_expiry = 0, start = 0, expiry = 0;
|
|
};
|
|
|
|
Dispatcher::~Dispatcher() { delete beacon_tx; }
|
|
|
|
bool Dispatcher::queueBeaconRadioFrame(const uint8_t* bytes, size_t length,
|
|
const RadioProfileParams& params, int8_t power) {
|
|
if (!bytes || !length || length > MAX_TRANS_UNIT || beacon_tx || outbound
|
|
|| !radio_available || _radio->isCarrierWaveActive()
|
|
|| _radio->isBeaconRadioOwned()
|
|
|| _radio->isMorseTxActive() || _radio->isMorseRxActive()
|
|
|| !RadioProfiles::valid(params) || !_radio->validateProfile(params)) return false;
|
|
#if MESH_ENABLE_FOREIGN_RADIO
|
|
if (hasForeignRadioFrame()) return false;
|
|
#endif
|
|
const uint32_t now = _ms->getMillis();
|
|
// Admit a due slot before checking the actual RF-start guard below. CAD
|
|
// and tuning made the preceding start slightly later than its scheduled
|
|
// hour; rejecting here would silently drop the next hourly slot.
|
|
auto* frame = new (std::nothrow) BeaconTransmit;
|
|
if (!frame) return false;
|
|
frame->radio = params;
|
|
frame->power = power;
|
|
frame->length = uint16_t(length);
|
|
memcpy(frame->bytes, bytes, length);
|
|
frame->retry_at = now;
|
|
frame->prepare_expiry = now + 30000UL;
|
|
beacon_tx = frame;
|
|
return true;
|
|
}
|
|
|
|
void Dispatcher::accountBeaconAirtime() {
|
|
const uint32_t now = _ms->getMillis();
|
|
const uint32_t elapsed = now - beacon_tx->start;
|
|
total_air_time += elapsed;
|
|
// Match ordinary TX retirement without postponing adaptive maintenance.
|
|
agc_reset.noteTransmit(now);
|
|
updateTxBudget();
|
|
tx_budget_ms = elapsed >= tx_budget_ms ? 0 : tx_budget_ms - elapsed;
|
|
if (tx_budget_ms < 100) {
|
|
const float duty = 1.0f / (1.0f + getAirtimeBudgetFactor());
|
|
next_tx_time = futureMillis((unsigned long)((100 - tx_budget_ms) / duty));
|
|
} else next_tx_time = now;
|
|
}
|
|
|
|
void Dispatcher::cancelBeaconRadioFrame() {
|
|
if (!beacon_tx) return;
|
|
if (!radio_available) {
|
|
// Suspension never performs SPI against an unavailable peripheral.
|
|
if (!_radio->isBeaconRadioOwned()) { delete beacon_tx; beacon_tx = nullptr; }
|
|
return;
|
|
}
|
|
if (beacon_tx->phase == BeaconTransmit::Sending) {
|
|
_radio->onSendFinished();
|
|
accountBeaconAirtime();
|
|
}
|
|
beacon_tx->phase = BeaconTransmit::Restoring;
|
|
serviceBeaconRadioFrame();
|
|
}
|
|
|
|
bool Dispatcher::serviceBeaconRadioFrame() {
|
|
if (!beacon_tx || beacon_tx->phase == BeaconTransmit::Pending) return false;
|
|
if (beacon_tx->phase == BeaconTransmit::Sending) {
|
|
const bool complete = _radio->isSendComplete();
|
|
if (!complete && int32_t(uint32_t(_ms->getMillis()) - beacon_tx->expiry) < 0) return true;
|
|
_radio->onSendFinished();
|
|
accountBeaconAirtime();
|
|
beacon_tx->phase = BeaconTransmit::Restoring;
|
|
if (!complete) {
|
|
_err_flags |= ERR_EVENT_RADIO_WATCHDOG;
|
|
_radio->recoverRadio(true);
|
|
}
|
|
}
|
|
// Never let normal TX, AGC, or RX metadata processing use the beacon tuple.
|
|
if (_radio->restoreBeaconReceive() != RadioParamApplyResult::APPLIED) return true;
|
|
delete beacon_tx;
|
|
beacon_tx = nullptr;
|
|
radio_nonrx_start = _ms->getMillis();
|
|
return false;
|
|
}
|
|
|
|
bool Dispatcher::checkBeaconRadioSend() {
|
|
if (!beacon_tx || outbound || beacon_tx->phase != BeaconTransmit::Pending) return false;
|
|
const uint32_t now = _ms->getMillis();
|
|
if (int32_t(now - beacon_tx->prepare_expiry) >= 0) {
|
|
cancelBeaconRadioFrame();
|
|
return beacon_tx != nullptr;
|
|
}
|
|
if (int32_t(now - beacon_tx->retry_at) < 0) return false;
|
|
// Beacons are background work, never ahead of due MeshCore traffic.
|
|
if (_mgr->getOutboundCount(now) || !restoreOutboundTxOverrides()
|
|
|| _radio->isCarrierWaveActive() || _radio->isMorseTxActive()
|
|
|| _radio->isMorseRxActive() || _radio->isCalibratingNoiseFloor()) {
|
|
beacon_tx->retry_at = now + 101;
|
|
return false;
|
|
}
|
|
updateTxBudget();
|
|
const uint32_t airtime = _radio->getBeaconAirtime(beacon_tx->radio, beacon_tx->length);
|
|
if (!airtime || airtime > UINT32_MAX / 1000) {
|
|
cancelBeaconRadioFrame();
|
|
return beacon_tx != nullptr;
|
|
}
|
|
if (tx_budget_ms < airtime / 2 || !millisHasNowPassed(next_tx_time)
|
|
|| (beacon_has_started && uint32_t(now - beacon_last_start) < BEACON_START_GUARD_MS)) {
|
|
beacon_tx->retry_at = now + 101;
|
|
return false;
|
|
}
|
|
const auto prepared = _radio->prepareBeaconTransmit(beacon_tx->radio, beacon_tx->power);
|
|
if (prepared == RadioParamApplyResult::BUSY) {
|
|
if (_radio->isBeaconRadioOwned()) {
|
|
cancelBeaconRadioFrame();
|
|
return beacon_tx != nullptr;
|
|
}
|
|
// Coprime retry avoids repeatedly probing one RXPS sleep phase.
|
|
beacon_tx->retry_at = now + 11;
|
|
return false;
|
|
}
|
|
if (prepared != RadioParamApplyResult::APPLIED || _radio->isBeaconChannelBusy()) {
|
|
cancelBeaconRadioFrame();
|
|
return beacon_tx != nullptr;
|
|
}
|
|
// Count attempts, not delivery: no automatic retry after an RF start.
|
|
beacon_has_started = true;
|
|
beacon_last_start = beacon_tx->start = _ms->getMillis();
|
|
if (!_radio->startSendRaw(beacon_tx->bytes, beacon_tx->length)) {
|
|
cancelBeaconRadioFrame();
|
|
return beacon_tx != nullptr;
|
|
}
|
|
beacon_tx->phase = BeaconTransmit::Sending;
|
|
beacon_tx->expiry = beacon_tx->start + airtime * 3 / 2 + 1000;
|
|
return true;
|
|
}
|
|
|
|
} // namespace mesh
|
|
#endif
|