lorawan_toa/lorawan_toa_cal.py

162 lines
6.0 KiB
Python

# 4.1.1.6. LoRaTM Packet Structure, SX1276/77/78/79 Datasheet Rev.5 Aug 2016
# http://www.semtech.com/images/datasheet/sx1276.pdf
import math
from argparse import ArgumentParser
def mpsrange(a, b):
'''
Mac Payload Size range.
return a list of [a, b], a <= arange(a,b) <= b
'''
a += 5 # MHDR + MIC
b += 6 # MHDR + MIC + 1
return range(a, b)
def get_toa(n_size, n_sf, n_bw=125, enable_auto_ldro=True, enable_ldro=False,
enable_eh=True, enable_crc=True, n_cr=1, n_preamble=8):
'''
Parameters:
n_size:
PL in the fomula. PHY Payload size in byte (= MAC Payload + 5)
n_sf: SF (12 to 7)
n_bw: Bandwidth in kHz. default is 125 kHz for AS923.
enable_auto_ldro
flag whether the auto Low Data Rate Optimization is enabled or not.
default is True.
enable_ldro:
if enable_auto_ldro is disabled, LDRO is disable by default,
which means that DE in the fomula is going to be 0.
When enable_ldro is set to True, DE is going to be 1.
LoRaWAN specification does not specify the usage.
SX1276 datasheet reuiqres to enable LDRO
when the symbol duration exceeds 16ms.
enable_eh:
when enable_eh is set to False, IH in the fomula is going to be 1.
default is True, which means IH is 0.
LoRaWAN always enables the explicit header.
enable_crc:
when enable_crc is set to False, CRC in the fomula is going to be 0.
The downlink stream doesn't use the CRC in the LoRaWAN spec.
default is True to calculate ToA for the uplink stream.
n_cr:
CR in the fomula, should be from 1 to 4.
Coding Rate = (n_cr/(n_cr+1)).
LoRaWAN takes alway 1.
n_preamble:
The preamble length in bit.
default is 8 in AS923.
Return:
dict type contains below:
r_sym: symbol rate in *second*
t_sym: the time on air in millisecond*.
t_preamble:
v_ceil:
symbol_size_payload:
t_payload:
t_packet: the time on air in *milisecond*.
'''
r_sym = (n_bw*1000.) / math.pow(2,n_sf)
t_sym = 1000. / r_sym
t_preamble = (n_preamble + 4.25) * t_sym
# LDRO
v_DE = 0
if enable_auto_ldro:
if t_sym > 16:
v_DE = 1
elif enable_ldro:
v_DE = 1
# IH
v_IH = 0
if not enable_eh:
v_IH = 1
# CRC
v_CRC = 1
if enable_crc == False:
v_CRC = 0
#
a = 8.*n_size - 4.*n_sf + 28 + 16*v_CRC - 20.*v_IH
b = 4.*(n_sf-2.*v_DE)
v_ceil = a/b
n_payload = 8 + max(math.ceil(a/b)*(n_cr+4), 0)
t_payload = n_payload * t_sym
t_packet = t_preamble+ t_payload
ret = {}
ret["r_sym"] = r_sym
ret["t_sym"] = t_sym
ret["n_preamble"] = n_preamble
ret["t_preamble"] = t_preamble
ret["v_DE"] = v_DE
ret["v_ceil"] = v_ceil
ret["n_sym_payload"] = n_payload
ret["t_payload"] = t_payload
ret["t_packet"] = round(t_packet, 3)
ret["phy_pl_size"] = n_size
ret["mac_pl_size"] = n_size - 5
ret["sf"] = n_sf
ret["bw"] = n_bw
ret["ldro"] = "enable" if v_DE else "disable"
ret["eh"] = "enable" if enable_eh else "disable"
ret["cr"] = n_cr
ret["preamble"] = n_preamble
ret["raw_datarate"] = n_sf * 4/(4+n_cr) * r_sym
return ret
def parse_args():
p = ArgumentParser(description="LoRa Time on Air calculator.")
p.add_argument("sf", metavar="SF",
help="Spreading Factor. It should be from 7 to 12, or FSK")
p.add_argument("n_size", metavar="SIZE", type=int,
help="""PHY payload size in byte.
Remember that PHY payload (i.e. MAC frame) consists of
MHDR(1) + MAC payload + MIC(4), or
MHDR(1) + FHDR(7) + FPort(1) + APP + MIC(4).
For example, SIZE for Join Request is going to be 23.
If the size of an application message (APP) is
12, SIZE is going to be 25. """)
p.add_argument("--band-width", action="store", dest="n_bw", type=int,
default=125, metavar="NUMBER",
help="bandwidth in kHz. default is 125 kHz.")
p.add_argument("--disable-auto-ldro", action="store_false",
dest="enable_auto_ldro",
help="disable the auto LDRO and disable LDRO.")
p.add_argument("--enable-ldro", action="store_true", dest="enable_ldro",
help="This option is available when the auto LDRO is disabled.")
p.add_argument("--disable-eh", action="store_false", dest="enable_eh",
help="disable the explicit header.")
p.add_argument("--downlink", action="store_false", dest="enable_crc",
help="disable the CRC field, which is for the LoRaWAN downlink stream.")
p.add_argument("--disable-crc", action="store_false", dest="enable_crc",
help="same effect as the --downlink option.")
p.add_argument("--cr", action="store", dest="n_cr",
type=int, default=1, metavar="NUMBER",
help="specify the CR value. default is 1 as LoRaWAN does.")
p.add_argument("--preamble", action="store", dest="n_preamble",
type=int, default=8, metavar="NUMBER",
help="specify the preamble. default is 8 for AS923.")
p.add_argument("--duty-cycle", action="store", dest="n_duty_cycle",
type=int, default=1, metavar="NUMBER",
help="specify the duty cycle in percentage. default is 1 %%.")
p.add_argument("-v", action="store_true", dest="f_verbose",
default=False,
help="enable verbose mode.")
p.add_argument("-d", action="append_const", dest="_f_debug", default=[],
const=1, help="increase debug mode.")
args = p.parse_args()
if args.sf == "FSK":
args.n_sf = 0
else:
try:
args.n_sf = int(args.sf)
except ValueError:
raise ValueError("ERROR: SF must be a number of 'FSK', "
f"but {opt.sf}")
args.v_de = False
args.debug_level = len(args._f_debug)
return args