lorawan_toa/lorawan_toa.py

184 lines
7.3 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 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
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:
n_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_payload"] = n_payload
ret["t_payload"] = t_payload
ret["t_packet"] = round(t_packet, 3)
return ret
if __name__ == "__main__" :
import sys
import argparse
def parse_args():
p = argparse.ArgumentParser(
description="LoRa Time on Air calculator.",
epilog="")
p.add_argument("n_sf", metavar="SF", type=int,
help="Spreading Factor. It should be from 7 to 12.")
p.add_argument("n_size", metavar="SIZE", type=int,
help="PHY payload size in byte. It's equal to the MAC payload + 5.")
p.add_argument("--band-width", action="store", dest="n_bw", type=int,
default=125,
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,
help="specify the CR value. default is 1 as LoRaWAN does.")
p.add_argument("--preamble", action="store", dest="n_preamble",
type=int, default=8,
help="specify the preamble. default is 8 for AS923.")
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()
args.v_de = False
args.debug_level = len(args._f_debug)
return args
#
# main
#
opt = parse_args()
ret = get_toa(opt.n_size, opt.n_sf, n_bw=opt.n_bw,
enable_auto_ldro=opt.enable_auto_ldro,
enable_ldro=opt.enable_ldro,
enable_eh=opt.enable_eh, enable_crc=opt.enable_crc,
n_cr=opt.n_cr, n_preamble=opt.n_preamble)
if opt.f_verbose:
print "PHY payload size : %d Bytes" % opt.n_size
print "MAC payload size : %d Bytes" % (opt.n_size-5)
print "Spreading Factor : %d" % opt.n_sf
print "Band width : %d kHz" % opt.n_bw
print "Low data rate opt. : %s" % ("enable" if ret["v_DE"]
else "disable")
print "Explicit header : %s" % ("enable" if opt.enable_eh
else "disable")
print "CR (coding rate) : %d (4/%d)" % (opt.n_cr, 4+opt.n_cr)
print "Symbol Rate : %.3f symbol/s" % ret["r_sym"]
print "Symbol Time : %.3f msec/symbol" % ret["t_sym"]
print "Preamble size : %d symbols" % opt.n_preamble
print "Packet symbol size : %d symbols" % ret["n_payload"]
print "Preamble ToA : %.3f msec" % ret["t_preamble"]
print "Payload ToA : %.3f msec" % ret["t_payload"]
print "Time on Air : %.3f msec" % ret["t_packet"]
if opt.debug_level:
ret0 = get_toa(0, opt.n_sf, n_bw=opt.n_bw,
enable_auto_ldro=opt.enable_auto_ldro,
enable_ldro=opt.enable_ldro,
enable_eh=opt.enable_eh, enable_crc=opt.enable_crc,
n_cr=opt.n_cr, n_preamble=opt.n_preamble)
print "PHY PL=0 ToA : %.3f msec" % (ret0["t_packet"])
# preamble=8 cr=1? payload-len=7? crc=16 (payload) payload-crc=16
# =? 48 ==> 6 bytes ?
t0 = (ret["t_packet"]-ret0["t_packet"])/1000.
print "PHY fr.dr.(48b) 7:15: %.3f bps" % ((8.*opt.n_size+48)/t0)
print "MAC frame DR : %.3f bps" % ((8.*(opt.n_size)) /t0)
print "Ceil(x) : %.3f" % ret["v_ceil"]
else:
print "%.3f" % ret["t_packet"]