blob: dc850ee0bcc76cf61a733eeba205944bb14a90fb [file] [log] [blame]
#!/usr/bin/env python3
import os
import sys
import math
import json
from optparse import OptionParser
# This defaulr value is sync with:
# dw3000_calib.c:DW3000_CALIBRATION_PDOA_LUT_MAX
DW3000_CALIBRATION_PDOA_LUT_MAX = 31
usage="""%s [options]
This script aims to dump a binary blob suitable for uwb-stack calibration file.
It works in two modes :
1. Input JSON LUT
2. Antenna spacing and formula
== Input JSON LUT ==
It takes a JSON input on stdin and dumps the corresponding blob on stdout.
The JSON input must be a list of either:
* lists containing 2 items of type float, 1st is assumed to be PDoA, 2nd AoA,
* maps containing 'aoa' and 'pdoa' keys, and corresponding values of type float.
float values are assumed to be in radian unit.
== Antenna spacing and formula ==
This mode uses the theorical math formula to compute AoA value from PDoA measurement.
It also uses the antenna spacing parameter, please ensure to provide the right value.
2 sub modes are possible:
* PDoA value are provided in list of float that matches the LUT size
* --no-json option is specified or empty input is provided, then the tool will
split the [-Pi, Pi] range in %d equal parts and use it as PDoA value list
"""%(
os.path.basename(sys.argv[0]),
DW3000_CALIBRATION_PDOA_LUT_MAX,
)
# ==== Fixed point / math utils ====
Q = 11
K = 2 ** Q
S16_MAX = ((2 ** 15) - 1)
S16_MIN = -(2 ** 15)
speed_of_light_m_per_s = 299702547
def freq_hz(chan):
if chan == 5:
return 6.5e9
elif chan == 9:
return 7.9872e9
else:
raise ValueError("Channel must be either 5 or 9")
def sat_uf(x):
return min(S16_MAX, max(S16_MIN, x))
def float_to_q11(x):
return sat_uf(round(x * K))
pi_q = float_to_q11(math.pi)
def pdoa_to_aoa_rad(x, chan, antenna_dist_m):
L_M = speed_of_light_m_per_s / freq_hz(chan)
phase_m = x * L_M / (2.0 * math.pi)
rad = phase_m / antenna_dist_m
rad = min(1.0, max(-1.0, rad))
return math.asin(rad)
# ==================================
def blob_item(pdoa, aoa, fmt, byteorder):
pdoa_q11 = float_to_q11(pdoa)
aoa_q11 = float_to_q11(aoa)
pdoa_bytes = pdoa_q11.to_bytes(2, byteorder=byteorder, signed=True)
aoa_bytes = aoa_q11.to_bytes(2, byteorder=byteorder, signed=True)
return fmt%(pdoa_bytes[0], pdoa_bytes[1], aoa_bytes[0], aoa_bytes[1])
def input_json_lut(pdoa_lut, options):
print("Generating LUT using 'input JSON LUT' mode...")
print("LUT = %s"%str(pdoa_lut))
if not isinstance(pdoa_lut, list) \
or (options.size_check and (len(pdoa_lut) != options.lut_size)):
raise ValueError("input JSON must be list of size %d"
%(str(options.lut_size)))
blob = ""
first_val = True
internal_pdoa_lut = []
for item in pdoa_lut:
if isinstance(item, list) and len(item) == 2:
pdoa = item[0]
aoa = item[1]
elif isinstance(item, dict):
# NB: exception raised if key not provided in input data
pdoa = item['pdoa']
aoa = item['aoa']
else:
raise ValueError("input JSON list item must of type list \
[float, float] (PDoA then AoA) or map {'aoa':float, 'pdoa':float}")
internal_pdoa_lut.append((pdoa, aoa))
internal_pdoa_lut.sort(key=lambda x: x[0])
for item in internal_pdoa_lut:
pdoa = item[0]
aoa = item[1]
if first_val:
first_val = False
else:
blob += options.sep
blob += blob_item(pdoa, aoa, options.fmt, options.byteorder)
return blob
def antenna_spacing_and_formula(pdoa_values, options):
print("Generating LUT using 'antenna spacing and formula' mode...")
print("PDoA values = %s"%(str(pdoa_values)))
print("Antenna spacing (mm) = %f"%(options.antenna_spacing / 1000.0))
if not isinstance(pdoa_values, list) \
or (options.size_check and (len(pdoa_values) != options.lut_size)):
raise ValueError("input JSON must be list of size %d"
%(options.lut_size))
blob = ""
first_val = True
for value in pdoa_values:
pdoa = float(value) # would raise TypeError
aoa = pdoa_to_aoa_rad(pdoa, options.channel,
options.antenna_spacing / 1000000.0)
print("\t%f\t=>\t%f"%(pdoa, aoa))
if first_val:
first_val = False
else:
blob += options.sep
blob += blob_item(pdoa, aoa, options.fmt, options.byteorder)
return blob
def float_range(start, stop, step):
while start < stop:
yield float(start)
start += step
if __name__ == "__main__":
parser = OptionParser(usage=usage)
parser.add_option("--channel", dest="channel", type="int", default=5,
help="UWB Channel (5 or 9, default is 5)")
parser.add_option("--formula-mode", dest="formula_mode",
action="store_true", default=False,
help="selects formula mode")
parser.add_option("--antenna-spacing", dest="antenna_spacing",
type="int", default=20800,
help="sets the antenna pair spacing used in formula \
mode, unit is micrometers (default value 20800 \
matches Mona Lisa design).")
parser.add_option("--byteorder", dest="byteorder",
type="string", default=sys.byteorder,
help="Override byte order. Default is the system's \
byteorder (%s)"%(sys.byteorder))
parser.add_option("--lut-size", dest="lut_size",
type="int", default=DW3000_CALIBRATION_PDOA_LUT_MAX,
help="Override default lut size (%d)"%(DW3000_CALIBRATION_PDOA_LUT_MAX))
parser.add_option("--no-size-check", dest="size_check",
action="store_false", default=True,
help="skips length of the input map.")
parser.add_option("--dump-c-array", dest="dump_c_array",
action="store_true", default=False,
help="Dumps a C array instead of calib blob")
parser.add_option("--no-input", dest="read_stdin",
action="store_false", default=True,
help="Skips reading input on stdin")
(options, args) = parser.parse_args()
if options.dump_c_array:
options.sep = ",\n"
options.fmt = "\t{ 0x%02x%02x, 0x%02x%02x }"
options.byteorder = 'big'
else:
options.sep = ":"
options.fmt = "%02x:%02x:%02x:%02x"
txt_input = ""
if options.read_stdin:
for line in sys.stdin:
txt_input += line
if options.formula_mode:
if txt_input:
pdoa_values = json.loads(txt_input)
else:
step = 2 * math.pi / (options.lut_size-1)
pdoa_values = list(float_range(-math.pi, math.pi, step))
blob = antenna_spacing_and_formula(pdoa_values, options)
else:
pdoa_lut = json.loads(txt_input)
blob = input_json_lut(pdoa_lut, options)
if options.dump_c_array:
print("const dw3000_pdoa_lut_t pdoa_lut = {\n" + blob + "\n};")
else:
print("antX.antY.chN.pdoa_lut " + blob)