Files
protoloon/js/WsprCodec.js
2026-04-02 17:39:02 -06:00

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;
}
}