* Introduce non-linear TX_GAIN_LORA Previously, our TX_GAIN_LORA setting was a single number, intended to represent the signal gain going through a power amp (plus or minus antenna, attenuator, and other parts of the RF chain). It turns out the relationship between the input power (i.e. from an SX1262) and total output power is often non-linear. While we fudged a 1dBm difference here and there with existing chips, the Heltec v4 has a 5dBm difference in gain depending on which end of the input power (and frequency) you are at. To allow people to run their Heltec v4 at max power when legal, and future proof our code, this patch introduced an optional array-based TX_GAIN_LORA. Define NUM_PA_POINTS and set TX_GAIN_LORA to gain values for a given input power in 1dBm increments, and all will work. For linear systems, just continue to define TX_GAIN_LORA as a number. Fixes https://github.com/meshtastic/firmware/issues/8070 * Remove temporary power limit on heltec v4 * Add function RadioLibInterface::checkOutputPower * Ensure SX126x reaches minimum supported power. * Keep it simple, instead.
742 lines
28 KiB
C++
742 lines
28 KiB
C++
#include "RadioInterface.h"
|
||
#include "Channels.h"
|
||
#include "DisplayFormatters.h"
|
||
#include "MeshRadio.h"
|
||
#include "MeshService.h"
|
||
#include "NodeDB.h"
|
||
#include "Router.h"
|
||
#include "configuration.h"
|
||
#include "main.h"
|
||
#include "sleep.h"
|
||
#include <assert.h>
|
||
#include <pb_decode.h>
|
||
#include <pb_encode.h>
|
||
|
||
// Calculate 2^n without calling pow()
|
||
uint32_t pow_of_2(uint32_t n)
|
||
{
|
||
return 1 << n;
|
||
}
|
||
|
||
#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \
|
||
{ \
|
||
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \
|
||
frequency_switching, wide_lora, #name \
|
||
}
|
||
|
||
const RegionInfo regions[] = {
|
||
/*
|
||
https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf
|
||
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
|
||
*/
|
||
RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false),
|
||
|
||
/*
|
||
EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
|
||
|
||
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
|
||
FIXME: https://github.com/meshtastic/firmware/issues/3371
|
||
*/
|
||
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 10, true, false, false),
|
||
|
||
/*
|
||
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
|
||
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
|
||
https://www.legislation.gov.uk/uksi/1999/930/schedule/6/part/III/made/data.xht?view=snippet&wrap=true
|
||
|
||
audio_permitted = false per regulation
|
||
|
||
Special Note:
|
||
The link above describes LoRaWAN's band plan, stating a power limit of 16 dBm. This is their own suggested specification,
|
||
we do not need to follow it. The European Union regulations clearly state that the power limit for this frequency range is
|
||
500 mW, or 27 dBm. It also states that we can use interference avoidance and spectrum access techniques (such as LBT +
|
||
AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.)
|
||
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf
|
||
*/
|
||
RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false),
|
||
|
||
/*
|
||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||
*/
|
||
RDEF(CN, 470.0f, 510.0f, 100, 0, 19, true, false, false),
|
||
|
||
/*
|
||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||
https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf
|
||
https://qiita.com/ammo0613/items/d952154f1195b64dc29f
|
||
*/
|
||
RDEF(JP, 920.5f, 923.5f, 100, 0, 13, true, false, false),
|
||
|
||
/*
|
||
https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf
|
||
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
|
||
Also used in Brazil.
|
||
*/
|
||
RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false, false),
|
||
|
||
/*
|
||
433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
|
||
AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence
|
||
NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100
|
||
*/
|
||
RDEF(ANZ_433, 433.05f, 434.79f, 100, 0, 14, true, false, false),
|
||
|
||
/*
|
||
https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf
|
||
|
||
Note:
|
||
- We do LBT, so 100% is allowed.
|
||
*/
|
||
RDEF(RU, 868.7f, 869.2f, 100, 0, 20, true, false, false),
|
||
|
||
/*
|
||
https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0
|
||
https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters
|
||
*/
|
||
RDEF(KR, 920.0f, 923.0f, 100, 0, 23, true, false, false),
|
||
|
||
/*
|
||
Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
|
||
5.8.1 in the Low-power Radio-frequency Devices Technical Regulations
|
||
https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
|
||
https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
|
||
*/
|
||
RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false),
|
||
|
||
/*
|
||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||
*/
|
||
RDEF(IN, 865.0f, 867.0f, 100, 0, 30, true, false, false),
|
||
|
||
/*
|
||
https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752
|
||
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
|
||
*/
|
||
RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 36, true, false, false),
|
||
|
||
/*
|
||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||
*/
|
||
RDEF(TH, 920.0f, 925.0f, 100, 0, 16, true, false, false),
|
||
|
||
/*
|
||
433,05-434,7 Mhz 10 mW
|
||
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
|
||
*/
|
||
RDEF(UA_433, 433.0f, 434.7f, 10, 0, 10, true, false, false),
|
||
|
||
/*
|
||
868,0-868,6 Mhz 25 mW
|
||
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
|
||
*/
|
||
RDEF(UA_868, 868.0f, 868.6f, 1, 0, 14, true, false, false),
|
||
|
||
/*
|
||
Malaysia
|
||
433 - 435 MHz at 100mW, no restrictions.
|
||
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
|
||
*/
|
||
RDEF(MY_433, 433.0f, 435.0f, 100, 0, 20, true, false, false),
|
||
|
||
/*
|
||
Malaysia
|
||
919 - 923 Mhz at 500mW, no restrictions.
|
||
923 - 924 MHz at 500mW with 1% duty cycle OR frequency hopping.
|
||
Frequency hopping is used for 919 - 923 MHz.
|
||
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
|
||
*/
|
||
RDEF(MY_919, 919.0f, 924.0f, 100, 0, 27, true, true, false),
|
||
|
||
/*
|
||
Singapore
|
||
SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions.
|
||
https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf
|
||
*/
|
||
RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false),
|
||
|
||
/*
|
||
Philippines
|
||
433 - 434.7 MHz <10 mW erp, NTC approved device required
|
||
868 - 869.4 MHz <25 mW erp, NTC approved device required
|
||
915 - 918 MHz <250 mW EIRP, no external antenna allowed
|
||
https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135
|
||
*/
|
||
|
||
RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false),
|
||
RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false),
|
||
|
||
/*
|
||
Kazakhstan
|
||
433.075 - 434.775 MHz <10 mW EIRP, Low Powered Devices (LPD)
|
||
863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
|
||
https://github.com/meshtastic/firmware/issues/7204
|
||
*/
|
||
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false), RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, true),
|
||
|
||
/*
|
||
Nepal
|
||
865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
|
||
specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
|
||
*/
|
||
RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false),
|
||
|
||
/*
|
||
Brazil
|
||
902 - 907.5 MHz , 1W power limit, no duty cycle restrictions
|
||
https://github.com/meshtastic/firmware/issues/3741
|
||
*/
|
||
RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false),
|
||
|
||
/*
|
||
2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
|
||
*/
|
||
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true),
|
||
|
||
/*
|
||
This needs to be last. Same as US.
|
||
*/
|
||
RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false)
|
||
|
||
};
|
||
|
||
const RegionInfo *myRegion;
|
||
bool RadioInterface::uses_default_frequency_slot = true;
|
||
|
||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1];
|
||
|
||
void initRegion()
|
||
{
|
||
const RegionInfo *r = regions;
|
||
#ifdef REGULATORY_LORA_REGIONCODE
|
||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != REGULATORY_LORA_REGIONCODE; r++)
|
||
;
|
||
LOG_INFO("Wanted region %d, regulatory override to %s", config.lora.region, r->name);
|
||
#else
|
||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != config.lora.region; r++)
|
||
;
|
||
LOG_INFO("Wanted region %d, using %s", config.lora.region, r->name);
|
||
#endif
|
||
myRegion = r;
|
||
}
|
||
|
||
/**
|
||
* ## LoRaWAN for North America
|
||
|
||
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
|
||
|
||
The maximum output power for North America is +30 dBM.
|
||
|
||
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
|
||
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
|
||
*/
|
||
|
||
/**
|
||
* Calculate airtime per
|
||
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
|
||
* section 4
|
||
*
|
||
* @return num msecs for the packet
|
||
*/
|
||
uint32_t RadioInterface::getPacketTime(uint32_t pl)
|
||
{
|
||
float bandwidthHz = bw * 1000.0f;
|
||
bool headDisable = false; // we currently always use the header
|
||
float tSym = (1 << sf) / bandwidthHz;
|
||
|
||
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
|
||
|
||
float tPreamble = (preambleLength + 4.25f) * tSym;
|
||
float numPayloadSym =
|
||
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
|
||
float tPayload = numPayloadSym * tSym;
|
||
float tPacket = tPreamble + tPayload;
|
||
|
||
uint32_t msecs = tPacket * 1000;
|
||
|
||
return msecs;
|
||
}
|
||
|
||
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
|
||
{
|
||
uint32_t pl = 0;
|
||
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
|
||
pl = p->encrypted.size + sizeof(PacketHeader);
|
||
} else {
|
||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
|
||
pl = numbytes + sizeof(PacketHeader);
|
||
}
|
||
return getPacketTime(pl);
|
||
}
|
||
|
||
/** The delay to use for retransmitting dropped packets */
|
||
uint32_t RadioInterface::getRetransmissionMsec(const meshtastic_MeshPacket *p)
|
||
{
|
||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
|
||
uint32_t packetAirtime = getPacketTime(numbytes + sizeof(PacketHeader));
|
||
// Make sure enough time has elapsed for this packet to be sent and an ACK is received.
|
||
// LOG_DEBUG("Waiting for flooding message with airtime %d and slotTime is %d", packetAirtime, slotTimeMsec);
|
||
float channelUtil = airTime->channelUtilizationPercent();
|
||
uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax);
|
||
// Assuming we pick max. of CWsize and there will be a client with SNR at half the range
|
||
return 2 * packetAirtime + (pow_of_2(CWsize) + 2 * CWmax + pow_of_2(int((CWmax + CWmin) / 2))) * slotTimeMsec +
|
||
PROCESSING_TIME_MSEC;
|
||
}
|
||
|
||
/** The delay to use when we want to send something */
|
||
uint32_t RadioInterface::getTxDelayMsec()
|
||
{
|
||
/** We wait a random multiple of 'slotTimes' (see definition in header file) in order to avoid collisions.
|
||
The pool to take a random multiple from is the contention window (CW), which size depends on the
|
||
current channel utilization. */
|
||
float channelUtil = airTime->channelUtilizationPercent();
|
||
uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax);
|
||
// LOG_DEBUG("Current channel utilization is %f so setting CWsize to %d", channelUtil, CWsize);
|
||
return random(0, pow_of_2(CWsize)) * slotTimeMsec;
|
||
}
|
||
|
||
/** The CW size to use when calculating SNR_based delays */
|
||
uint8_t RadioInterface::getCWsize(float snr)
|
||
{
|
||
// The minimum value for a LoRa SNR
|
||
const uint32_t SNR_MIN = -20;
|
||
|
||
// The maximum value for a LoRa SNR
|
||
const uint32_t SNR_MAX = 10;
|
||
|
||
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
|
||
}
|
||
|
||
/** The worst-case SNR_based packet delay */
|
||
uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
|
||
{
|
||
uint8_t CWsize = getCWsize(snr);
|
||
// offset the maximum delay for routers: (2 * CWmax * slotTimeMsec)
|
||
return (2 * CWmax * slotTimeMsec) + pow_of_2(CWsize) * slotTimeMsec;
|
||
}
|
||
|
||
/** Returns true if we should rebroadcast early like a ROUTER */
|
||
bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
|
||
{
|
||
// If we are a ROUTER or REPEATER, we always rebroadcast early
|
||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
|
||
return true;
|
||
}
|
||
|
||
// If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
|
||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
||
return nodeDB->isFromOrToFavoritedNode(*p);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/** The delay to use when we want to flood a message */
|
||
uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
|
||
{
|
||
// high SNR = large CW size (Long Delay)
|
||
// low SNR = small CW size (Short Delay)
|
||
float snr = p->rx_snr;
|
||
uint32_t delay = 0;
|
||
uint8_t CWsize = getCWsize(snr);
|
||
// LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
|
||
if (shouldRebroadcastEarlyLikeRouter(p)) {
|
||
delay = random(0, 2 * CWsize) * slotTimeMsec;
|
||
LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
|
||
} else {
|
||
// offset the maximum delay for routers: (2 * CWmax * slotTimeMsec)
|
||
delay = (2 * CWmax * slotTimeMsec) + random(0, pow_of_2(CWsize)) * slotTimeMsec;
|
||
LOG_DEBUG("rx_snr found in packet. Setting tx delay:%d", delay);
|
||
}
|
||
|
||
return delay;
|
||
}
|
||
|
||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||
{
|
||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||
std::string out =
|
||
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
|
||
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
|
||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||
auto &s = p->decoded;
|
||
|
||
out += DEBUG_PORT.mt_sprintf(" Portnum=%d", s.portnum);
|
||
|
||
if (s.want_response)
|
||
out += DEBUG_PORT.mt_sprintf(" WANTRESP");
|
||
|
||
if (p->pki_encrypted)
|
||
out += DEBUG_PORT.mt_sprintf(" PKI");
|
||
|
||
if (s.source != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" source=%08x", s.source);
|
||
|
||
if (s.dest != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" dest=%08x", s.dest);
|
||
|
||
if (s.request_id)
|
||
out += DEBUG_PORT.mt_sprintf(" requestId=%0x", s.request_id);
|
||
|
||
/* now inside Data and therefore kinda opaque
|
||
if (s.which_ackVariant == SubPacket_success_id_tag)
|
||
out += DEBUG_PORT.mt_sprintf(" successId=%08x", s.ackVariant.success_id);
|
||
else if (s.which_ackVariant == SubPacket_fail_id_tag)
|
||
out += DEBUG_PORT.mt_sprintf(" failId=%08x", s.ackVariant.fail_id); */
|
||
} else {
|
||
out += " encrypted";
|
||
out += DEBUG_PORT.mt_sprintf(" len=%d", p->encrypted.size + sizeof(PacketHeader));
|
||
}
|
||
|
||
if (p->rx_time != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" rxtime=%u", p->rx_time);
|
||
if (p->rx_snr != 0.0)
|
||
out += DEBUG_PORT.mt_sprintf(" rxSNR=%g", p->rx_snr);
|
||
if (p->rx_rssi != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" rxRSSI=%i", p->rx_rssi);
|
||
if (p->via_mqtt != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" via MQTT");
|
||
if (p->hop_start != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" hopStart=%d", p->hop_start);
|
||
if (p->next_hop != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" nextHop=0x%x", p->next_hop);
|
||
if (p->relay_node != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" relay=0x%x", p->relay_node);
|
||
if (p->priority != 0)
|
||
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
|
||
|
||
out += ")";
|
||
LOG_DEBUG("%s", out.c_str());
|
||
#endif
|
||
}
|
||
|
||
RadioInterface::RadioInterface()
|
||
{
|
||
assert(sizeof(PacketHeader) == MESHTASTIC_HEADER_LENGTH); // make sure the compiler did what we expected
|
||
}
|
||
|
||
bool RadioInterface::reconfigure()
|
||
{
|
||
applyModemConfig();
|
||
return true;
|
||
}
|
||
|
||
bool RadioInterface::init()
|
||
{
|
||
LOG_INFO("Start meshradio init");
|
||
|
||
configChangedObserver.observe(&service->configChanged);
|
||
preflightSleepObserver.observe(&preflightSleep);
|
||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||
|
||
// we now expect interfaces to operate in promiscuous mode
|
||
// radioIf.setThisAddress(nodeDB->getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at
|
||
// constructor time.
|
||
|
||
applyModemConfig();
|
||
|
||
return true;
|
||
}
|
||
|
||
int RadioInterface::notifyDeepSleepCb(void *unused)
|
||
{
|
||
sleep();
|
||
return 0;
|
||
}
|
||
|
||
/** hash a string into an integer
|
||
*
|
||
* djb2 by Dan Bernstein.
|
||
* http://www.cse.yorku.ca/~oz/hash.html
|
||
*/
|
||
uint32_t hash(const char *str)
|
||
{
|
||
uint32_t hash = 5381;
|
||
int c;
|
||
|
||
while ((c = *str++) != 0)
|
||
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
|
||
|
||
return hash;
|
||
}
|
||
|
||
/**
|
||
* Save our frequency for later reuse.
|
||
*/
|
||
void RadioInterface::saveFreq(float freq)
|
||
{
|
||
savedFreq = freq;
|
||
}
|
||
|
||
/**
|
||
* Save our channel for later reuse.
|
||
*/
|
||
void RadioInterface::saveChannelNum(uint32_t channel_num)
|
||
{
|
||
savedChannelNum = channel_num;
|
||
}
|
||
|
||
/**
|
||
* Save our frequency for later reuse.
|
||
*/
|
||
float RadioInterface::getFreq()
|
||
{
|
||
return savedFreq;
|
||
}
|
||
|
||
/**
|
||
* Save our channel for later reuse.
|
||
*/
|
||
uint32_t RadioInterface::getChannelNum()
|
||
{
|
||
return savedChannelNum;
|
||
}
|
||
|
||
/**
|
||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||
*/
|
||
void RadioInterface::applyModemConfig()
|
||
{
|
||
// Set up default configuration
|
||
// No Sync Words in LORA mode
|
||
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
|
||
bool validConfig = false; // We need to check for a valid configuration
|
||
while (!validConfig) {
|
||
if (loraConfig.use_preset) {
|
||
|
||
switch (loraConfig.modem_preset) {
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||
bw = (myRegion->wideLora) ? 1625.0 : 500;
|
||
cr = 5;
|
||
sf = 7;
|
||
break;
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
|
||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||
cr = 5;
|
||
sf = 7;
|
||
break;
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
|
||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||
cr = 5;
|
||
sf = 8;
|
||
break;
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
|
||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||
cr = 5;
|
||
sf = 9;
|
||
break;
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
|
||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||
cr = 5;
|
||
sf = 10;
|
||
break;
|
||
default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal.
|
||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||
cr = 5;
|
||
sf = 11;
|
||
break;
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||
bw = (myRegion->wideLora) ? 406.25 : 125;
|
||
cr = 8;
|
||
sf = 11;
|
||
break;
|
||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
|
||
bw = (myRegion->wideLora) ? 406.25 : 125;
|
||
cr = 8;
|
||
sf = 12;
|
||
break;
|
||
}
|
||
} else {
|
||
sf = loraConfig.spread_factor;
|
||
cr = loraConfig.coding_rate;
|
||
bw = loraConfig.bandwidth;
|
||
|
||
if (bw == 31) // This parameter is not an integer
|
||
bw = 31.25;
|
||
if (bw == 62) // Fix for 62.5Khz bandwidth
|
||
bw = 62.5;
|
||
if (bw == 200)
|
||
bw = 203.125;
|
||
if (bw == 400)
|
||
bw = 406.25;
|
||
if (bw == 800)
|
||
bw = 812.5;
|
||
if (bw == 1600)
|
||
bw = 1625.0;
|
||
}
|
||
|
||
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
|
||
static const char *err_string = "Regional frequency range is smaller than bandwidth. Fall back to default preset";
|
||
LOG_ERROR(err_string);
|
||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||
|
||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||
cn->level = meshtastic_LogRecord_Level_ERROR;
|
||
sprintf(cn->message, err_string);
|
||
service->sendClientNotification(cn);
|
||
|
||
// Set to default modem preset
|
||
loraConfig.use_preset = true;
|
||
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
|
||
} else {
|
||
validConfig = true;
|
||
}
|
||
}
|
||
|
||
power = loraConfig.tx_power;
|
||
|
||
if ((power == 0) || ((power > myRegion->powerLimit) && !devicestate.owner.is_licensed))
|
||
power = myRegion->powerLimit;
|
||
|
||
if (power == 0)
|
||
power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of myRegion defaults
|
||
// to 0, currently no region has an actual power limit of 0 [dBm] so we can assume regions which have this
|
||
// variable set to 0 don't have a valid power limit)
|
||
|
||
// Set final tx_power back onto config
|
||
loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger
|
||
|
||
// Calculate the number of channels
|
||
uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000)));
|
||
|
||
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
|
||
uint32_t channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
|
||
|
||
// Check if we use the default frequency slot
|
||
RadioInterface::uses_default_frequency_slot =
|
||
channel_num ==
|
||
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
|
||
|
||
// Old frequency selection formula
|
||
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
|
||
|
||
// New frequency selection formula
|
||
float freq = myRegion->freqStart + (bw / 2000) + (channel_num * (bw / 1000));
|
||
|
||
// override if we have a verbatim frequency
|
||
if (loraConfig.override_frequency) {
|
||
freq = loraConfig.override_frequency;
|
||
channel_num = -1;
|
||
}
|
||
|
||
saveChannelNum(channel_num);
|
||
saveFreq(freq + loraConfig.frequency_offset);
|
||
|
||
slotTimeMsec = computeSlotTimeMsec();
|
||
preambleTimeMsec = getPacketTime((uint32_t)0);
|
||
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
|
||
|
||
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
|
||
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
|
||
channel_num, power);
|
||
LOG_INFO("myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)", myRegion->freqStart, myRegion->freqEnd,
|
||
myRegion->freqEnd - myRegion->freqStart);
|
||
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
|
||
LOG_INFO("channel_num: %d", channel_num + 1);
|
||
LOG_INFO("frequency: %f", getFreq());
|
||
LOG_INFO("Slot time: %u msec", slotTimeMsec);
|
||
}
|
||
|
||
/** Slottime is the time to detect a transmission has started, consisting of:
|
||
- CAD duration;
|
||
- roundtrip air propagation time (assuming max. 30km between nodes);
|
||
- Tx/Rx turnaround time (maximum of SX126x and SX127x);
|
||
- MAC processing time (measured on T-beam) */
|
||
uint32_t RadioInterface::computeSlotTimeMsec()
|
||
{
|
||
float sumPropagationTurnaroundMACTime = 0.2 + 0.4 + 7; // in milliseconds
|
||
float symbolTime = pow_of_2(sf) / bw; // in milliseconds
|
||
|
||
if (myRegion->wideLora) {
|
||
// CAD duration derived from AN1200.22 of SX1280
|
||
return (NUM_SYM_CAD_24GHZ + (2 * sf + 3) / 32) * symbolTime + sumPropagationTurnaroundMACTime;
|
||
} else {
|
||
// CAD duration for SX127x is max. 2.25 symbols, for SX126x it is number of symbols + 0.5 symbol
|
||
return max(2.25, NUM_SYM_CAD + 0.5) * symbolTime + sumPropagationTurnaroundMACTime;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Some regulatory regions limit xmit power.
|
||
* This function should be called by subclasses after setting their desired power. It might lower it
|
||
*/
|
||
void RadioInterface::limitPower(int8_t loraMaxPower)
|
||
{
|
||
uint8_t maxPower = 255; // No limit
|
||
|
||
if (myRegion->powerLimit)
|
||
maxPower = myRegion->powerLimit;
|
||
|
||
if ((power > maxPower) && !devicestate.owner.is_licensed) {
|
||
LOG_INFO("Lower transmit power because of regulatory limits");
|
||
power = maxPower;
|
||
}
|
||
|
||
#ifndef NUM_PA_POINTS
|
||
if (TX_GAIN_LORA > 0) {
|
||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA);
|
||
power -= TX_GAIN_LORA;
|
||
}
|
||
#else
|
||
// we have an array of PA gain values. Find the highest power setting that works.
|
||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||
for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) {
|
||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||
((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||
// we've exceeded the power limit, or hit the max we can do
|
||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
|
||
power -= tx_gain[radio_dbm];
|
||
break;
|
||
}
|
||
}
|
||
|
||
#endif
|
||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||
power = loraMaxPower;
|
||
|
||
LOG_INFO("Final Tx power: %d dBm", power);
|
||
}
|
||
|
||
void RadioInterface::deliverToReceiver(meshtastic_MeshPacket *p)
|
||
{
|
||
if (router) {
|
||
p->transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA;
|
||
router->enqueueReceivedMessage(p);
|
||
}
|
||
}
|
||
|
||
/***
|
||
* given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of payload bytes to send
|
||
*/
|
||
size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||
{
|
||
assert(!sendingPacket);
|
||
|
||
// LOG_DEBUG("Send queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
|
||
assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag); // It should have already been encoded by now
|
||
|
||
radioBuffer.header.from = p->from;
|
||
radioBuffer.header.to = p->to;
|
||
radioBuffer.header.id = p->id;
|
||
radioBuffer.header.channel = p->channel;
|
||
radioBuffer.header.next_hop = p->next_hop;
|
||
radioBuffer.header.relay_node = p->relay_node;
|
||
if (p->hop_limit > HOP_MAX) {
|
||
LOG_WARN("hop limit %d is too high, setting to %d", p->hop_limit, HOP_RELIABLE);
|
||
p->hop_limit = HOP_RELIABLE;
|
||
}
|
||
radioBuffer.header.flags =
|
||
p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0);
|
||
radioBuffer.header.flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK;
|
||
|
||
// if the sender nodenum is zero, that means uninitialized
|
||
assert(radioBuffer.header.from);
|
||
assert(p->encrypted.size <= sizeof(radioBuffer.payload));
|
||
memcpy(radioBuffer.payload, p->encrypted.bytes, p->encrypted.size);
|
||
|
||
sendingPacket = p;
|
||
return p->encrypted.size + sizeof(PacketHeader);
|
||
}
|