idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
airtime.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
19#include <idfxx/radio/types.hpp>
20
21#include <chrono>
22#include <cstddef>
23#include <cstdint>
24#include <utility>
25
26namespace idfxx::radio {
27
29namespace airtime_detail {
30
34[[nodiscard]] constexpr uint32_t bandwidth_hz(bandwidth bw) noexcept {
35 switch (bw) {
36 case bandwidth::bw_7_8:
37 return 7'800;
38 case bandwidth::bw_10_4:
39 return 10'400;
40 case bandwidth::bw_15_6:
41 return 15'625;
42 case bandwidth::bw_20_8:
43 return 20'800;
44 case bandwidth::bw_31_25:
45 return 31'250;
46 case bandwidth::bw_41_7:
47 return 41'700;
48 case bandwidth::bw_62_5:
49 return 62'500;
50 case bandwidth::bw_125:
51 return 125'000;
52 case bandwidth::bw_250:
53 return 250'000;
54 case bandwidth::bw_500:
55 return 500'000;
56 }
57 return 125'000; // safe default = 125 kHz
58}
59
67[[nodiscard]] constexpr uint32_t payload_symbols(
69 coding_rate cr,
70 header_type header,
71 bool crc_on,
72 bool low_data_rate_optimize,
73 size_t payload_length
74) noexcept {
75 const int32_t sf_n = static_cast<int32_t>(std::to_underlying(sf));
76 const int32_t cr_n = static_cast<int32_t>(std::to_underlying(cr)) - 4; // 1..4
77 const int32_t crc = crc_on ? 1 : 0;
78 const int32_t ih = (header == header_type::fixed) ? 1 : 0; // implicit header
79 const int32_t de = low_data_rate_optimize ? 1 : 0;
80 const int32_t pl = static_cast<int32_t>(payload_length);
81
82 // numerator = 8·PL − 4·SF + 28 + 16·CRC − 20·IH. Clamping it to zero here is
83 // equivalent to the classic `max(ceil(...)·(CR+4), 0)`: a non-positive
84 // numerator contributes no extra symbols beyond the fixed eight.
85 int32_t numerator = 8 * pl - 4 * sf_n + 28 + 16 * crc - 20 * ih;
86 if (numerator < 0) {
87 numerator = 0;
88 }
89 const int32_t denominator = 4 * (sf_n - 2 * de);
90 const int32_t ceil_term = (numerator + denominator - 1) / denominator; // ceil of a non-negative ratio
91 return 8u + static_cast<uint32_t>(ceil_term) * static_cast<uint32_t>(cr_n + 4);
92}
93
94} // namespace airtime_detail
96
134[[nodiscard]] constexpr std::chrono::microseconds
135time_on_air(const lora_modulation& mod, const lora_packet_params& pkt, size_t payload_length) noexcept {
136 const uint64_t bw = airtime_detail::bandwidth_hz(mod.bw);
137 const uint64_t sf = std::to_underlying(mod.sf);
138 const uint32_t n_payload = airtime_detail::payload_symbols(
139 mod.sf, mod.cr, pkt.header, pkt.crc_on, mod.low_data_rate_optimize, payload_length
140 );
141
142 // Total symbol count scaled ×4 so the 4.25-symbol preamble offset (×4 = 17)
143 // stays integral.
144 const uint64_t n_symbol_x4 = (static_cast<uint64_t>(pkt.preamble_length) + n_payload) * 4 + 17;
145
146 // T = n_symbol · (2^SF / BW). Fold the ×4 of n_symbol_x4 into the divisor and
147 // scale by 1e6 µs/s, then round to the nearest microsecond — all in integer
148 // arithmetic so the result is deterministic and constexpr-evaluable.
149 const uint64_t numerator = n_symbol_x4 * (uint64_t{1} << sf) * 1'000'000ull;
150 const uint64_t denominator = 4ull * bw;
151 const uint64_t us = (numerator + denominator / 2) / denominator;
152 return std::chrono::microseconds{static_cast<int64_t>(us)};
153}
154
155} // namespace idfxx::radio
Chip-agnostic LoRa modulation and packet types.
LoRa radio types and driver classes.
Definition airtime.hpp:26
spreading_factor
LoRa spreading factor.
Definition types.hpp:47
constexpr std::chrono::microseconds time_on_air(const lora_modulation &mod, const lora_packet_params &pkt, size_t payload_length) noexcept
Computes the time-on-air of a LoRa packet.
Definition airtime.hpp:135
coding_rate
LoRa forward-error-correction coding rate.
Definition types.hpp:86
bandwidth
LoRa channel bandwidth.
Definition types.hpp:66
header_type
LoRa packet header mode.
Definition types.hpp:109
LoRa modulation parameters.
Definition types.hpp:132
LoRa packet framing parameters.
Definition types.hpp:149