1096 lines
32 KiB
JavaScript
1096 lines
32 KiB
JavaScript
/*
|
|
Copyright (c) 2023-forever Douglas Malnati. All rights reserved.
|
|
|
|
See the /faq/tos page for details.
|
|
|
|
(If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.)
|
|
*/
|
|
|
|
import { WSPREncoded } from '/js/WSPREncoded.js';
|
|
import { StrAccumulator } from '/js/Utl.js';
|
|
|
|
|
|
export class WsprCodecMaker
|
|
{
|
|
constructor()
|
|
{
|
|
this.debug = false;
|
|
|
|
this.codec = "";
|
|
this.json = {};
|
|
this.errList = [];
|
|
this.CodecClass = null;
|
|
|
|
this.SetCodecDefFragment("MyMessageType", "");
|
|
}
|
|
|
|
SetDebug(debug)
|
|
{
|
|
this.debug = debug;
|
|
}
|
|
|
|
GetFieldBitsAvailable()
|
|
{
|
|
const BITS_AVAILABLE = 29.180;
|
|
|
|
return BITS_AVAILABLE;
|
|
}
|
|
|
|
// allow setting just name and fields, don't worry about object structure
|
|
SetCodecDefFragment(msgName, codecFragment)
|
|
{
|
|
// support comments in input by eliminating lines with comment char before them
|
|
let codecDefLineList = [];
|
|
for (let line of codecFragment.split("\n"))
|
|
{
|
|
line = line.trim();
|
|
|
|
if (line.substring(0, 2) != "//")
|
|
{
|
|
codecDefLineList.push(line);
|
|
}
|
|
}
|
|
let codecFragmentUse = codecDefLineList.join("\n");
|
|
|
|
// attempt to parse the fragment as valid json so that decent error messages
|
|
// can be returned to users if it's busted
|
|
|
|
this.ResetErrList();
|
|
|
|
let ok = true;
|
|
try
|
|
{
|
|
// expected to have a trailing comma
|
|
let fakeCodec = `{ "fieldList": [ ${codecFragmentUse}\n\n {}] }`;
|
|
|
|
let json = JSON.parse(fakeCodec);
|
|
}
|
|
catch (e)
|
|
{
|
|
ok = false;
|
|
|
|
this.AddErr(e);
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
let finalFieldFragment = `
|
|
{ "name": "HdrSlot", "unit": "Enum", "lowValue": 0, "highValue": 4, "stepSize": 1 },
|
|
{ "name": "HdrType", "unit": "Enum", "lowValue": 0, "highValue": 15, "stepSize": 1 },
|
|
{ "name": "HdrRESERVED", "unit": "Enum", "lowValue": 0, "highValue": 3, "stepSize": 1 },
|
|
{ "name": "HdrTelemetryType", "unit": "Enum", "lowValue": 0, "highValue": 1, "stepSize": 1 }
|
|
`;
|
|
|
|
// assumes the input's codeFragment ends with a comma if there are fields
|
|
let codec = `
|
|
{
|
|
"name": "${msgName}",
|
|
"fieldList": [ ${codecFragmentUse} ${finalFieldFragment}]
|
|
}`;
|
|
ok = this.SetCodecDef(codec);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
SetCodecDef(codec)
|
|
{
|
|
this.codec = codec;
|
|
|
|
let ok = this.ParseCodecDef(this.codec);
|
|
|
|
if (ok)
|
|
{
|
|
this.Calculate();
|
|
this.#MakeCodecClass();
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
GetErrList()
|
|
{
|
|
return this.errList;
|
|
}
|
|
|
|
ResetErrList()
|
|
{
|
|
this.errList = [];
|
|
}
|
|
|
|
AddErr(err)
|
|
{
|
|
this.errList.push(err);
|
|
|
|
if (this.debug)
|
|
{
|
|
console.log(err);
|
|
}
|
|
}
|
|
|
|
#IsValueSegmentList(field)
|
|
{
|
|
return Array.isArray(field.valueSegmentList);
|
|
}
|
|
|
|
#IsSegmentedField(field)
|
|
{
|
|
return this.#IsValueSegmentList(field);
|
|
}
|
|
|
|
#IsPositiveNumber(value)
|
|
{
|
|
return typeof value == "number" && Number.isFinite(value) && value > 0;
|
|
}
|
|
|
|
#IsWholeNumberClose(value, epsilon = 1e-9)
|
|
{
|
|
return Math.abs(value - Math.round(value)) < epsilon;
|
|
}
|
|
|
|
#ValidateScalarField(field)
|
|
{
|
|
let ok = true;
|
|
let stepCount = (field.highValue - field.lowValue) / field.stepSize;
|
|
|
|
if (this.#IsWholeNumberClose(stepCount) == false)
|
|
{
|
|
ok = false;
|
|
|
|
let err = `Field(${field.name}) stepSize(${field.stepSize}) does not evenly divide the low(${field.lowValue})-to-high(${field.highValue}) range.`;
|
|
|
|
let factorList = [];
|
|
if (Number.isInteger(field.lowValue) && Number.isInteger(field.highValue))
|
|
{
|
|
for (let stepSize = 1; stepSize < ((field.highValue - field.lowValue) / 2); ++stepSize)
|
|
{
|
|
let stepCountNew = (field.highValue - field.lowValue) / stepSize;
|
|
|
|
if (Number.isInteger(stepCountNew))
|
|
{
|
|
factorList.push(stepSize);
|
|
}
|
|
}
|
|
|
|
if (factorList.length)
|
|
{
|
|
err += `\n`;
|
|
err += ` Whole integer steps are: ${factorList.join(", ")}.`;
|
|
}
|
|
}
|
|
|
|
this.AddErr(err);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
#ValidateValueSegmentListField(field)
|
|
{
|
|
let ok = true;
|
|
|
|
if (this.#IsValueSegmentList(field) == false || field.valueSegmentList.length == 0)
|
|
{
|
|
this.AddErr(`Field(${field.name}) valueSegmentList must be a non-empty array.`);
|
|
return false;
|
|
}
|
|
|
|
let prevHigh = null;
|
|
for (let i = 0; i < field.valueSegmentList.length; ++i)
|
|
{
|
|
const segment = field.valueSegmentList[i];
|
|
|
|
if (Array.isArray(segment) == false || segment.length != 3)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) valueSegmentList[${i}] must be [low, step, high].`);
|
|
continue;
|
|
}
|
|
|
|
const [low, step, high] = segment;
|
|
if ([low, step, high].every((value) => typeof value == "number" && Number.isFinite(value)))
|
|
{
|
|
if (low >= high)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) segment ${i + 1} low(${low}) must be less than high(${high}).`);
|
|
}
|
|
|
|
if (step <= 0)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) segment ${i + 1} step(${step}) must be greater than zero.`);
|
|
}
|
|
|
|
const stepCount = (high - low) / step;
|
|
if (this.#IsWholeNumberClose(stepCount) == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) segment ${i + 1} step(${step}) does not evenly divide the low(${low})-to-high(${high}) range.`);
|
|
}
|
|
|
|
if (prevHigh != null && Math.abs(prevHigh - low) > 1e-9)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) segment ${i + 1} low(${low}) must equal prior segment high(${prevHigh}).`);
|
|
}
|
|
|
|
prevHigh = high;
|
|
}
|
|
else
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) valueSegmentList[${i}] values must all be finite numbers.`);
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
#MakeSegmentListFromValueSegmentList(field)
|
|
{
|
|
let segmentList = [];
|
|
|
|
for (let i = 0; i < field.valueSegmentList.length; ++i)
|
|
{
|
|
const [low, step, high] = field.valueSegmentList[i];
|
|
const isLast = i == field.valueSegmentList.length - 1;
|
|
const rawCount = Math.round((high - low) / step);
|
|
const count = isLast ? rawCount + 1 : rawCount;
|
|
|
|
segmentList.push({
|
|
low,
|
|
high,
|
|
step,
|
|
isLast,
|
|
count,
|
|
});
|
|
}
|
|
|
|
return segmentList;
|
|
}
|
|
|
|
#ValidateField(field)
|
|
{
|
|
let ok = true;
|
|
|
|
const isValueSegmented = this.#IsValueSegmentList(field);
|
|
const propList = [
|
|
"unit",
|
|
...(isValueSegmented ? ["valueSegmentList"] : ["lowValue", "highValue", "stepSize"]),
|
|
];
|
|
|
|
let IsInvalidString = (str) => {
|
|
return str.trim() == "" || str.trim() != str || str.indexOf(' ') !== -1;
|
|
};
|
|
|
|
let HasInvalidChar = (str) => {
|
|
return /[^a-zA-Z0-9_]/.test(str);
|
|
};
|
|
|
|
for (const prop of propList)
|
|
{
|
|
if (prop in field == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`No "${prop}" property in field(${field.name})`);
|
|
}
|
|
else if (prop == "unit")
|
|
{
|
|
if (IsInvalidString(field[prop].toString()))
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field name "${prop}" cannot be blank or have whitespace`);
|
|
}
|
|
else if (HasInvalidChar(field[prop].substring(1)))
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field name "${field.name}" must only have alphanumeric characters or underscore (_) after first character`);
|
|
}
|
|
}
|
|
else if (prop == "valueSegmentList")
|
|
{
|
|
if (this.#ValidateValueSegmentListField(field) == false)
|
|
{
|
|
ok = false;
|
|
}
|
|
}
|
|
else if (prop == "stepSize")
|
|
{
|
|
let validStepSize = false;
|
|
|
|
if (typeof field.stepSize == "number")
|
|
{
|
|
validStepSize = this.#IsPositiveNumber(field.stepSize);
|
|
}
|
|
else if (Array.isArray(field.stepSize))
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) stepSize must be a positive number.`);
|
|
continue;
|
|
}
|
|
|
|
if (validStepSize == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) stepSize(${JSON.stringify(field.stepSize)}) must be a positive number`);
|
|
}
|
|
}
|
|
else if (typeof field[prop] != "number" || Number.isFinite(field[prop]) == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field value "${prop}" = ${field[prop]} must be a number`);
|
|
}
|
|
}
|
|
|
|
if (ok && this.#IsValueSegmentList(field) == false && field.lowValue >= field.highValue)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field(${field.name}) lowValue(${field.lowValue}) must be less than highValue(${field.highValue})`);
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
if (this.#IsValueSegmentList(field))
|
|
{
|
|
ok = this.#ValidateValueSegmentListField(field);
|
|
}
|
|
else
|
|
{
|
|
ok = this.#ValidateScalarField(field);
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
#PrepareField(field)
|
|
{
|
|
let bits = 0;
|
|
let numValues = 0;
|
|
let indexCount = 0;
|
|
let enumCount = this.#IsSegmentedField(field)
|
|
? field.valueSegmentList.length
|
|
: 1;
|
|
let segmentList = [];
|
|
|
|
if (this.#IsValueSegmentList(field))
|
|
{
|
|
segmentList = this.#MakeSegmentListFromValueSegmentList(field);
|
|
numValues = segmentList.reduce((acc, segment) => acc + segment.count, 0);
|
|
indexCount = 0;
|
|
field.lowValue = segmentList[0].low;
|
|
field.highValue = segmentList[segmentList.length - 1].high;
|
|
field.stepSize = segmentList.map((segment) => segment.step);
|
|
}
|
|
else
|
|
{
|
|
let stepCount = Math.round((field.highValue - field.lowValue) / field.stepSize);
|
|
indexCount = stepCount + 1;
|
|
numValues = indexCount;
|
|
}
|
|
|
|
if (segmentList.length)
|
|
{
|
|
let base = 0;
|
|
for (const segment of segmentList)
|
|
{
|
|
segment.base = base;
|
|
base += segment.count;
|
|
}
|
|
}
|
|
|
|
bits = Math.log2(numValues);
|
|
|
|
field.EnumCount = enumCount;
|
|
field.IndexCount = indexCount;
|
|
field.NumValues = numValues;
|
|
field.Bits = bits;
|
|
field.IsSegmented = this.#IsSegmentedField(field);
|
|
|
|
if (field.IsSegmented)
|
|
{
|
|
field.SegmentList = segmentList;
|
|
}
|
|
else
|
|
{
|
|
delete field.SegmentList;
|
|
}
|
|
}
|
|
|
|
ParseCodecDef(codec)
|
|
{
|
|
let ok = true;
|
|
|
|
if (this.debug)
|
|
{
|
|
console.log(codec);
|
|
}
|
|
|
|
this.ResetErrList();
|
|
|
|
try
|
|
{
|
|
let json = JSON.parse(codec);
|
|
|
|
// validate basic structure
|
|
if ("name" in json == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`No "name" property for codec`);
|
|
}
|
|
else if ("fieldList" in json == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`No "fieldList" property for codec`);
|
|
}
|
|
else
|
|
{
|
|
let fieldNameSet = new Set();
|
|
|
|
let IsInvalidString = (str) => {
|
|
return str.trim() == "" || str.trim() != str || str.indexOf(' ') !== -1;
|
|
};
|
|
|
|
let HasInvalidChar = (str) => {
|
|
return /[^a-zA-Z0-9_]/.test(str);
|
|
};
|
|
|
|
for (const field of json.fieldList)
|
|
{
|
|
if ("name" in field == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`No "name" property in field`);
|
|
}
|
|
else if (fieldNameSet.has(field.name))
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field name "${field.name}" already defined`);
|
|
}
|
|
else if (IsInvalidString(field.name))
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field name "${field.name}" cannot be blank or have whitespace`);
|
|
}
|
|
else if (/^[a-zA-Z]/.test(field.name) == false)
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field name "${field.name}" must start with a letter`);
|
|
}
|
|
else if (HasInvalidChar(field.name.substring(1)))
|
|
{
|
|
ok = false;
|
|
this.AddErr(`Field name "${field.name}" must only have alphanumeric characters or underscore (_) after first character`);
|
|
}
|
|
else
|
|
{
|
|
fieldNameSet.add(field.name);
|
|
if (this.#ValidateField(field) == false)
|
|
{
|
|
ok = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
this.json = json;
|
|
}
|
|
}
|
|
}
|
|
catch (e)
|
|
{
|
|
ok = false;
|
|
this.AddErr(e);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
GetLastErr()
|
|
{
|
|
return this.lastErr;
|
|
}
|
|
|
|
Calculate()
|
|
{
|
|
let bitsSum = 0;
|
|
for (let field of this.json.fieldList)
|
|
{
|
|
this.#PrepareField(field);
|
|
field.BitsSum = field.Bits + bitsSum;
|
|
bitsSum += field.Bits;
|
|
}
|
|
|
|
if (this.debug)
|
|
{
|
|
console.table(this.json.fieldList);
|
|
}
|
|
}
|
|
|
|
GetField(fieldName)
|
|
{
|
|
let retVal = null;
|
|
|
|
for (let field of this.json.fieldList)
|
|
{
|
|
if (field.name == fieldName)
|
|
{
|
|
retVal = field;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
#MakeCodecClass()
|
|
{
|
|
let c = this.GenerateCodecClassDef();
|
|
if (this.debug)
|
|
{
|
|
console.log(c);
|
|
}
|
|
|
|
const MyClassDef = new Function('', `return ${c};`);
|
|
const MyClass = MyClassDef();
|
|
|
|
this.CodecClass = MyClass;
|
|
}
|
|
|
|
GetCodecInstance()
|
|
{
|
|
let codec = new this.CodecClass();
|
|
|
|
codec.SetWsprEncoded(WSPREncoded);
|
|
|
|
return codec;
|
|
}
|
|
|
|
GenerateCodecClassDef()
|
|
{
|
|
let a = new StrAccumulator();
|
|
|
|
a.A(`class ${this.json.name}Codec`);
|
|
a.A(`{`);
|
|
|
|
// Constructor
|
|
a.IncrIndent();
|
|
a.A(`constructor()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.Reset();`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// Application field list
|
|
a.IncrIndent();
|
|
a.A(`GetFieldList()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`return [`);
|
|
a.IncrIndent();
|
|
|
|
let sep = "";
|
|
|
|
for (let field of this.json.fieldList)
|
|
{
|
|
if (field.name.substr(0, 3) != "Hdr")
|
|
{
|
|
a.A(`${sep}${JSON.stringify(field)}`);
|
|
|
|
sep = ",";
|
|
}
|
|
}
|
|
|
|
a.DecrIndent();
|
|
a.A(`];`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// Reset
|
|
a.IncrIndent();
|
|
a.A(`Reset()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.call = "0A0AAA";`);
|
|
a.A(`this.grid = "AA00";`);
|
|
a.A(`this.powerDbm = 0;`);
|
|
a.A(``);
|
|
a.A(`this.id13 = "00";`);
|
|
a.A(``);
|
|
a.A(`if (this.wsprEncoded == undefined) { this.wsprEncoded = null; }`);
|
|
a.A(``);
|
|
for (let field of this.json.fieldList)
|
|
{
|
|
a.A(`this.${field.name} = ${field.lowValue};`);
|
|
}
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// Hack to get WSPREncoded into this object
|
|
a.IncrIndent();
|
|
a.A(`SetWsprEncoded(wsprEncoded)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.wsprEncoded = wsprEncoded;`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// Set id13
|
|
a.IncrIndent();
|
|
a.A(`SetId13(id13)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.id13 = id13;`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// Get id13
|
|
a.IncrIndent();
|
|
a.A(`GetId13(id13)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`return this.id13;`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
// Setters / Getters
|
|
for (let field of this.json.fieldList)
|
|
{
|
|
a.A(` `);
|
|
|
|
// Setter
|
|
a.IncrIndent();
|
|
a.A(`Set${field.name}${field.unit}(inputVal)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`let val = inputVal ?? ${field.lowValue};`);
|
|
a.A(``);
|
|
a.A(`if (val < ${field.lowValue}) { val = ${field.lowValue}; }`);
|
|
a.A(`else if (val > ${field.highValue}) { val = ${field.highValue}; }`);
|
|
a.A(``);
|
|
a.A(`this.${field.name} = this.Get${field.name}${field.unit}ValueFromNumber(this.Get${field.name}${field.unit}NumberFromValue(val));`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// Getter
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`return this.${field.name};`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// field metadata - low value
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}LowValue()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`return ${field.lowValue};`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// field metadata - high value
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}HighValue()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`return ${field.highValue};`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// field metadata - step size
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}StepSize()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`return ${JSON.stringify(field.stepSize)};`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// Encoded Number Getter
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}NumberFromValue(inputVal)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`let val = inputVal ?? ${field.lowValue};`);
|
|
a.A(`if (val < ${field.lowValue}) { val = ${field.lowValue}; }`);
|
|
a.A(`else if (val > ${field.highValue}) { val = ${field.highValue}; }`);
|
|
a.A(``);
|
|
|
|
if (field.IsSegmented)
|
|
{
|
|
a.A(`let segmentList = ${JSON.stringify(field.SegmentList)};`);
|
|
a.A(`let bestNumber = 0;`);
|
|
a.A(`let bestValue = segmentList[0].low;`);
|
|
a.A(`let bestDiff = Math.abs(val - bestValue);`);
|
|
a.A(``);
|
|
a.A(`for (let i = 0; i < segmentList.length; ++i)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`let segment = segmentList[i];`);
|
|
a.A(`let rawIndex = (val - segment.low) / segment.step;`);
|
|
a.A(`let candidateIdxList = [Math.floor(rawIndex), Math.ceil(rawIndex)];`);
|
|
a.A(`for (let candidateIdx of candidateIdxList)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`if (candidateIdx < 0) { candidateIdx = 0; }`);
|
|
a.A(`else if (candidateIdx >= segment.count) { candidateIdx = segment.count - 1; }`);
|
|
a.A(`let candidateValue = segment.low + (candidateIdx * segment.step);`);
|
|
a.A(`let candidateNumber = segment.base + candidateIdx;`);
|
|
a.A(`let candidateDiff = Math.abs(val - candidateValue);`);
|
|
a.A(`if (candidateDiff < bestDiff || (Math.abs(candidateDiff - bestDiff) < 1e-9 && candidateValue > bestValue))`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`bestDiff = candidateDiff;`);
|
|
a.A(`bestValue = candidateValue;`);
|
|
a.A(`bestNumber = candidateNumber;`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.A(``);
|
|
a.A(`return bestNumber;`);
|
|
}
|
|
else
|
|
{
|
|
a.A(`let retVal = ((val - ${field.lowValue}) / ${field.stepSize});`);
|
|
a.A(`retVal = Math.round(retVal);`);
|
|
a.A(`return retVal;`);
|
|
}
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
// Encoded Number Getter
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}Number()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`return this.Get${field.name}${field.unit}NumberFromValue(this.Get${field.name}${field.unit}());`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(` `);
|
|
|
|
a.IncrIndent();
|
|
a.A(`Get${field.name}${field.unit}ValueFromNumber(inputVal)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`let val = inputVal ?? 0;`);
|
|
a.A(`if (val < 0) { val = 0; }`);
|
|
a.A(`else if (val >= ${field.NumValues}) { val = ${field.NumValues - 1}; }`);
|
|
a.A(``);
|
|
|
|
if (field.IsSegmented)
|
|
{
|
|
a.A(`let segmentList = ${JSON.stringify(field.SegmentList)};`);
|
|
a.A(`for (let i = 0; i < segmentList.length; ++i)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`let segment = segmentList[i];`);
|
|
a.A(`if (val >= segment.base && val < segment.base + segment.count)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`let localIndex = val - segment.base;`);
|
|
a.A(`return segment.low + (localIndex * segment.step);`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.A(`let segment = segmentList[segmentList.length - 1];`);
|
|
a.A(`return segment.low + ((segment.count - 1) * segment.step);`);
|
|
}
|
|
else
|
|
{
|
|
a.A(`return ${field.lowValue} + (val * ${field.stepSize});`);
|
|
}
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
}
|
|
|
|
a.A(` `);
|
|
|
|
// Encode
|
|
|
|
// arrange application fields in reverse order
|
|
// but ensure the original order of header fields.
|
|
// this allows decode to pull out the "first" application field
|
|
// consistently, even if the fields after it
|
|
// change, are added, or revmoed.
|
|
// this isn't an expected feature, but a good feature as it protects
|
|
// legacy data in the event of future change as much as possible.
|
|
let fieldEncodeList = this.json.fieldList.slice();
|
|
let fieldListApp = [];
|
|
let fieldListHdr = [];
|
|
for (const field of fieldEncodeList)
|
|
{
|
|
if (field.name.substr(0, 3) == "Hdr")
|
|
{
|
|
fieldListHdr.push(field);
|
|
}
|
|
else
|
|
{
|
|
fieldListApp.push(field);
|
|
}
|
|
}
|
|
|
|
// reverse the application fields in-place
|
|
fieldListApp.reverse();
|
|
|
|
// re-make the field list
|
|
fieldEncodeList = [];
|
|
for (const field of fieldListApp)
|
|
{
|
|
fieldEncodeList.push(field);
|
|
}
|
|
for (const field of fieldListHdr)
|
|
{
|
|
fieldEncodeList.push(field);
|
|
}
|
|
|
|
a.IncrIndent();
|
|
a.A(`Encode()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
|
|
a.A(`let val = 0;`);
|
|
a.A(``);
|
|
|
|
a.A(`// combine field values`);
|
|
for (let field of fieldEncodeList)
|
|
{
|
|
a.A(`val *= ${field.NumValues}; val += this.Get${field.name}${field.unit}Number();`);
|
|
}
|
|
|
|
a.A(``);
|
|
|
|
a.A(`// encode into power`);
|
|
a.A(`let powerVal = val % 19; val = Math.floor(val / 19);`);
|
|
a.A(`let powerDbm = this.wsprEncoded.EncodeNumToPower(powerVal);`);
|
|
a.A(``);
|
|
a.A(`// encode into grid`);
|
|
a.A(`let g4Val = val % 10; val = Math.floor(val / 10);`);
|
|
a.A(`let g3Val = val % 10; val = Math.floor(val / 10);`);
|
|
a.A(`let g2Val = val % 18; val = Math.floor(val / 18);`);
|
|
a.A(`let g1Val = val % 18; val = Math.floor(val / 18);`);
|
|
a.A(``);
|
|
a.A(`let g1 = String.fromCharCode("A".charCodeAt(0) + g1Val);`);
|
|
a.A(`let g2 = String.fromCharCode("A".charCodeAt(0) + g2Val);`);
|
|
a.A(`let g3 = String.fromCharCode("0".charCodeAt(0) + g3Val);`);
|
|
a.A(`let g4 = String.fromCharCode("0".charCodeAt(0) + g4Val);`);
|
|
a.A(`let grid = g1 + g2 + g3 + g4;`);
|
|
a.A(``);
|
|
a.A(`// encode into callsign`);
|
|
a.A(`let id6Val = val % 26; val = Math.floor(val / 26);`);
|
|
a.A(`let id5Val = val % 26; val = Math.floor(val / 26);`);
|
|
a.A(`let id4Val = val % 26; val = Math.floor(val / 26);`);
|
|
a.A(`let id2Val = val % 36; val = Math.floor(val / 36);`);
|
|
a.A(``);
|
|
a.A(`let id2 = this.wsprEncoded.EncodeBase36(id2Val);`);
|
|
a.A(`let id4 = String.fromCharCode("A".charCodeAt(0) + id4Val);`);
|
|
a.A(`let id5 = String.fromCharCode("A".charCodeAt(0) + id5Val);`);
|
|
a.A(`let id6 = String.fromCharCode("A".charCodeAt(0) + id6Val);`);
|
|
a.A(`let call = this.id13.at(0) + id2 + this.id13.at(1) + id4 + id5 + id6;`);
|
|
a.A(``);
|
|
a.A(`// capture results`);
|
|
a.A(`this.call = call;`);
|
|
a.A(`this.grid = grid;`);
|
|
a.A(`this.powerDbm = powerDbm;`);
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// Decode
|
|
|
|
// get an entire-list reversed copy of the encoded field order
|
|
let fieldDecodeList = fieldEncodeList.slice().reverse();
|
|
|
|
a.IncrIndent();
|
|
a.A(`Decode()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`// pull in inputs`);
|
|
a.A(`let call = this.GetCall();`);
|
|
a.A(`let grid = this.GetGrid();`);
|
|
a.A(`let powerDbm = this.GetPowerDbm();`);
|
|
a.A(``);
|
|
a.A(`// break call down`);
|
|
a.A(`let id2Val = this.wsprEncoded.DecodeBase36(call.charAt(1));`);
|
|
a.A(`let id4Val = call.charAt(3).charCodeAt(0) - "A".charCodeAt(0);`);
|
|
a.A(`let id5Val = call.charAt(4).charCodeAt(0) - "A".charCodeAt(0);`);
|
|
a.A(`let id6Val = call.charAt(5).charCodeAt(0) - "A".charCodeAt(0);`);
|
|
a.A(``);
|
|
a.A(`// break grid down`);
|
|
a.A(`let g1Val = grid.charAt(0).charCodeAt(0) - "A".charCodeAt(0);`);
|
|
a.A(`let g2Val = grid.charAt(1).charCodeAt(0) - "A".charCodeAt(0);`);
|
|
a.A(`let g3Val = grid.charAt(2).charCodeAt(0) - "0".charCodeAt(0);`);
|
|
a.A(`let g4Val = grid.charAt(3).charCodeAt(0) - "0".charCodeAt(0);`);
|
|
a.A(``);
|
|
a.A(`// break power down`);
|
|
a.A(`let powerVal = this.wsprEncoded.DecodePowerToNum(powerDbm);`);
|
|
a.A(``);
|
|
a.A(`// combine values into single integer`);
|
|
a.A(`let val = 0;`);
|
|
a.A(`val *= 36; val += id2Val;`);
|
|
a.A(`val *= 26; val += id4Val; // spaces aren't used, so 26 not 27`);
|
|
a.A(`val *= 26; val += id5Val; // spaces aren't used, so 26 not 27`);
|
|
a.A(`val *= 26; val += id6Val; // spaces aren't used, so 26 not 27`);
|
|
a.A(`val *= 18; val += g1Val;`);
|
|
a.A(`val *= 18; val += g2Val;`);
|
|
a.A(`val *= 10; val += g3Val;`);
|
|
a.A(`val *= 10; val += g4Val;`);
|
|
a.A(`val *= 19; val += powerVal;`);
|
|
a.A(``);
|
|
a.A(`// extract field values`);
|
|
|
|
for (let field of fieldDecodeList)
|
|
{
|
|
a.A(`this.Set${field.name}${field.unit}(this.Get${field.name}${field.unit}ValueFromNumber(val % ${field.NumValues})); val = Math.floor(val / ${field.NumValues});`);
|
|
}
|
|
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// SetCall
|
|
a.IncrIndent();
|
|
a.A(`SetCall(inputVal)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.call = inputVal;`)
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// GetCall
|
|
a.IncrIndent();
|
|
a.A(`GetCall()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`return this.call;`)
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// SetGrid
|
|
a.IncrIndent();
|
|
a.A(`SetGrid(inputVal)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.grid = inputVal;`)
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// GetGrid
|
|
a.IncrIndent();
|
|
a.A(`GetGrid()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`return this.grid;`)
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// SetPowerDbm
|
|
a.IncrIndent();
|
|
a.A(`SetPowerDbm(inputVal)`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`this.powerDbm = inputVal;`)
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(``);
|
|
|
|
// GetPowerDbm
|
|
a.IncrIndent();
|
|
a.A(`GetPowerDbm()`);
|
|
a.A(`{`);
|
|
a.IncrIndent();
|
|
a.A(`return parseInt(this.powerDbm);`)
|
|
a.DecrIndent();
|
|
a.A(`}`);
|
|
a.DecrIndent();
|
|
|
|
a.A(`}`);
|
|
|
|
let c = a.Get();
|
|
|
|
return c;
|
|
}
|
|
}
|
|
|