@springfield/ham-radio-utils 4.6.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/memory/memory-map-channels.d.ts.map +1 -1
- package/dist/memory/memory-map-channels.js +186 -8
- package/dist/memory/memory-map-channels.js.map +1 -1
- package/dist/memory/memory-map-codec.d.ts +6 -1
- package/dist/memory/memory-map-codec.d.ts.map +1 -1
- package/dist/memory/memory-map-codec.js +73 -19
- package/dist/memory/memory-map-codec.js.map +1 -1
- package/dist/memory/memory-map-ui.d.ts.map +1 -1
- package/dist/memory/memory-map-ui.js +18 -9
- package/dist/memory/memory-map-ui.js.map +1 -1
- package/dist/schemas/radio-memory-map-schema.json +33 -3
- package/dist/schemas/radio-protocol-schema.json +15 -0
- package/package.json +2 -2
- package/src/memory/memory-map-channels.ts +230 -8
- package/src/memory/memory-map-codec.ts +85 -19
- package/src/memory/memory-map-ui.ts +20 -9
- package/src/schemas/radio-memory-map-schema.json +33 -3
- package/src/schemas/radio-protocol-schema.json +15 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-map-channels.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAInB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"memory-map-channels.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAInB,MAAM,4BAA4B,CAAC;AAoJpC;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,GACtB,sBAAsB,EAAE,CAsG1B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,GAAG,aAAa,CAmBzG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CA8JxG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,YAAY,CAOd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CAGZ"}
|
|
@@ -21,6 +21,86 @@ function radioToneToMapTone(tone) {
|
|
|
21
21
|
}
|
|
22
22
|
return { mode: 'ctcss', value: Number(tone.tone) };
|
|
23
23
|
}
|
|
24
|
+
function noneTone() {
|
|
25
|
+
return { tone: 0, type: RadioToneType.CTCSS };
|
|
26
|
+
}
|
|
27
|
+
function isNoneTone(tone) {
|
|
28
|
+
return !tone || !tone.tone;
|
|
29
|
+
}
|
|
30
|
+
function structHasField(struct, fieldId) {
|
|
31
|
+
return Boolean(struct?.fields.some((field) => field.id === fieldId));
|
|
32
|
+
}
|
|
33
|
+
function transmitFrequencyFromRecord(record, bindings) {
|
|
34
|
+
const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
|
|
35
|
+
const offsetOrTransmit = Number(record[bindings.transmitFrequency] ?? 0);
|
|
36
|
+
if (record.split === true) {
|
|
37
|
+
return offsetOrTransmit;
|
|
38
|
+
}
|
|
39
|
+
if (record.duplex === '+') {
|
|
40
|
+
return receiveFrequency + offsetOrTransmit;
|
|
41
|
+
}
|
|
42
|
+
if (record.duplex === '-') {
|
|
43
|
+
return receiveFrequency - offsetOrTransmit;
|
|
44
|
+
}
|
|
45
|
+
// Kenwood stores simplex as duplex "" and offset 0. Empty duplex is not an absolute TX frequency.
|
|
46
|
+
if (record.duplex === '') {
|
|
47
|
+
return receiveFrequency;
|
|
48
|
+
}
|
|
49
|
+
if (record.duplex === undefined) {
|
|
50
|
+
return bindings.transmitFrequency === bindings.receiveFrequency ? receiveFrequency : offsetOrTransmit;
|
|
51
|
+
}
|
|
52
|
+
return offsetOrTransmit;
|
|
53
|
+
}
|
|
54
|
+
function applyKenwoodToneMode(record, bindings) {
|
|
55
|
+
const hasToneModeBits = 'tone_mode' in record || 'ctcss_mode' in record || 'dtcs_mode' in record;
|
|
56
|
+
if (!hasToneModeBits) {
|
|
57
|
+
return {
|
|
58
|
+
receiveTone: toneToRadioTone(record[bindings.receiveTone]),
|
|
59
|
+
transmitTone: toneToRadioTone(record[bindings.transmitTone]),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (record.tone_mode === true) {
|
|
63
|
+
return { receiveTone: noneTone(), transmitTone: toneToRadioTone(record[bindings.transmitTone]) };
|
|
64
|
+
}
|
|
65
|
+
if (record.ctcss_mode === true) {
|
|
66
|
+
const tone = toneToRadioTone(record[bindings.receiveTone]);
|
|
67
|
+
return { receiveTone: tone, transmitTone: tone };
|
|
68
|
+
}
|
|
69
|
+
if (record.dtcs_mode === true) {
|
|
70
|
+
const tone = toneToRadioTone(record.dtcs_code);
|
|
71
|
+
return { receiveTone: tone, transmitTone: tone };
|
|
72
|
+
}
|
|
73
|
+
return { receiveTone: noneTone(), transmitTone: noneTone() };
|
|
74
|
+
}
|
|
75
|
+
function kenwoodUsedFlag(receiveFrequency, transmitFrequency) {
|
|
76
|
+
const frequency = transmitFrequency || receiveFrequency;
|
|
77
|
+
if (frequency < 150_000_000) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
if (frequency < 400_000_000) {
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
return 2;
|
|
84
|
+
}
|
|
85
|
+
/** TM-D710 chmap band codes: 0=118 MHz, 5=144, 6=200, 7=300, 8=400, 9=800. */
|
|
86
|
+
function kenwoodChmapBand(receiveFrequency) {
|
|
87
|
+
if (receiveFrequency < 136_000_000) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
if (receiveFrequency < 200_000_000) {
|
|
91
|
+
return 5;
|
|
92
|
+
}
|
|
93
|
+
if (receiveFrequency < 300_000_000) {
|
|
94
|
+
return 6;
|
|
95
|
+
}
|
|
96
|
+
if (receiveFrequency < 400_000_000) {
|
|
97
|
+
return 7;
|
|
98
|
+
}
|
|
99
|
+
if (receiveFrequency < 800_000_000) {
|
|
100
|
+
return 8;
|
|
101
|
+
}
|
|
102
|
+
return 9;
|
|
103
|
+
}
|
|
24
104
|
function asRecord(value) {
|
|
25
105
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
26
106
|
return undefined;
|
|
@@ -37,26 +117,35 @@ export function bindingsToChannels(memoryMap, settings) {
|
|
|
37
117
|
}
|
|
38
118
|
const records = settings[bindings.records];
|
|
39
119
|
const names = bindings.names ? settings[bindings.names] : undefined;
|
|
120
|
+
const extras = bindings.extras ? settings[bindings.extras] : undefined;
|
|
40
121
|
const recordItems = Array.isArray(records) ? records : [];
|
|
41
122
|
const nameItems = Array.isArray(names) ? names : [];
|
|
123
|
+
const extraItems = Array.isArray(extras) ? extras : [];
|
|
42
124
|
const nameField = bindings.nameField ?? 'name';
|
|
43
125
|
const boundFieldIds = new Set([
|
|
44
126
|
bindings.receiveFrequency,
|
|
45
127
|
bindings.transmitFrequency,
|
|
46
128
|
bindings.receiveTone,
|
|
47
129
|
bindings.transmitTone,
|
|
130
|
+
nameField,
|
|
48
131
|
]);
|
|
49
132
|
const channels = [];
|
|
50
133
|
for (let index = 0; index < recordItems.length; index += 1) {
|
|
51
134
|
const record = asRecord(recordItems[index]);
|
|
135
|
+
const extraRecord = asRecord(extraItems[index]);
|
|
136
|
+
if (bindings.extras && extraItems.length > 0 && extraRecord === undefined) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
52
139
|
if (!record) {
|
|
53
140
|
continue;
|
|
54
141
|
}
|
|
55
142
|
const nameRecord = asRecord(nameItems[index]);
|
|
56
|
-
const
|
|
57
|
-
const
|
|
143
|
+
const nameFromTable = typeof nameRecord?.[nameField] === 'string' ? nameRecord[nameField] : '';
|
|
144
|
+
const nameFromRecord = typeof record[nameField] === 'string' ? record[nameField] : '';
|
|
145
|
+
const name = nameFromTable || nameFromRecord;
|
|
58
146
|
const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
|
|
59
|
-
const transmitFrequency =
|
|
147
|
+
const transmitFrequency = transmitFrequencyFromRecord(record, bindings);
|
|
148
|
+
const tones = applyKenwoodToneMode(record, bindings);
|
|
60
149
|
const channelSettings = {};
|
|
61
150
|
for (const [fieldId, fieldValue] of Object.entries(record)) {
|
|
62
151
|
if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {
|
|
@@ -64,6 +153,17 @@ export function bindingsToChannels(memoryMap, settings) {
|
|
|
64
153
|
}
|
|
65
154
|
channelSettings[fieldId] = fieldValue;
|
|
66
155
|
}
|
|
156
|
+
if (extraRecord) {
|
|
157
|
+
for (const [fieldId, fieldValue] of Object.entries(extraRecord)) {
|
|
158
|
+
if (fieldId.startsWith('_') || fieldId === 'used') {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
channelSettings[fieldId] = fieldValue;
|
|
162
|
+
}
|
|
163
|
+
if (typeof extraRecord.lockout === 'boolean') {
|
|
164
|
+
channelSettings.skip = extraRecord.lockout ? 'S' : '';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
67
167
|
// Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.
|
|
68
168
|
if (typeof channelSettings.lowpower === 'number') {
|
|
69
169
|
channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;
|
|
@@ -74,12 +174,15 @@ export function bindingsToChannels(memoryMap, settings) {
|
|
|
74
174
|
if (typeof channelSettings.scan === 'boolean') {
|
|
75
175
|
channelSettings.skip = channelSettings.scan ? '' : 'S';
|
|
76
176
|
}
|
|
177
|
+
if (typeof channelSettings.skip === 'boolean') {
|
|
178
|
+
channelSettings.skip = channelSettings.skip ? 'S' : '';
|
|
179
|
+
}
|
|
77
180
|
const radioChannel = {
|
|
78
181
|
name,
|
|
79
182
|
receiveFrequency: Frequency(receiveFrequency),
|
|
80
183
|
transmitFrequency: Frequency(transmitFrequency),
|
|
81
|
-
receiveTone:
|
|
82
|
-
transmitTone:
|
|
184
|
+
receiveTone: tones.receiveTone,
|
|
185
|
+
transmitTone: tones.transmitTone,
|
|
83
186
|
};
|
|
84
187
|
channels.push({
|
|
85
188
|
channelNumber: index,
|
|
@@ -103,6 +206,9 @@ export function settingsWithoutChannels(memoryMap, settings) {
|
|
|
103
206
|
if (bindings.names) {
|
|
104
207
|
delete next[bindings.names];
|
|
105
208
|
}
|
|
209
|
+
if (bindings.extras) {
|
|
210
|
+
delete next[bindings.extras];
|
|
211
|
+
}
|
|
106
212
|
return next;
|
|
107
213
|
}
|
|
108
214
|
/**
|
|
@@ -115,10 +221,12 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
115
221
|
}
|
|
116
222
|
const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);
|
|
117
223
|
const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;
|
|
224
|
+
const extrasStruct = bindings.extras ? memoryMap.structs.find((struct) => struct.id === bindings.extras) : undefined;
|
|
118
225
|
const count = recordsStruct?.count ?? 0;
|
|
119
226
|
const nameField = bindings.nameField ?? 'name';
|
|
120
227
|
const records = Array.from({ length: count }, () => null);
|
|
121
228
|
const names = Array.from({ length: count }, () => null);
|
|
229
|
+
const extras = Array.from({ length: count }, () => null);
|
|
122
230
|
for (const programmed of program.channels) {
|
|
123
231
|
const index = programmed.channelNumber;
|
|
124
232
|
if (index < 0 || index >= count) {
|
|
@@ -129,12 +237,61 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
129
237
|
}
|
|
130
238
|
const channel = programmed.radioChannel;
|
|
131
239
|
const channelSettings = programmed.settings ?? {};
|
|
240
|
+
const receiveFrequency = Number(channel.receiveFrequency);
|
|
241
|
+
const transmitFrequency = Number(channel.transmitFrequency);
|
|
242
|
+
let offset = transmitFrequency;
|
|
243
|
+
let duplex = '';
|
|
244
|
+
let split = false;
|
|
245
|
+
if (transmitFrequency === receiveFrequency) {
|
|
246
|
+
offset = 0;
|
|
247
|
+
duplex = '';
|
|
248
|
+
}
|
|
249
|
+
else if (transmitFrequency > receiveFrequency) {
|
|
250
|
+
offset = transmitFrequency - receiveFrequency;
|
|
251
|
+
duplex = '+';
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
offset = receiveFrequency - transmitFrequency;
|
|
255
|
+
duplex = '-';
|
|
256
|
+
}
|
|
257
|
+
if (channelSettings.split === true) {
|
|
258
|
+
split = true;
|
|
259
|
+
duplex = '';
|
|
260
|
+
offset = transmitFrequency;
|
|
261
|
+
}
|
|
262
|
+
const usesOffset = 'duplex' in channelSettings || extrasStruct || structHasField(recordsStruct, 'duplex');
|
|
263
|
+
const usesKenwoodToneBits = 'tone_mode' in channelSettings ||
|
|
264
|
+
'ctcss_mode' in channelSettings ||
|
|
265
|
+
extrasStruct ||
|
|
266
|
+
structHasField(recordsStruct, 'tone_mode') ||
|
|
267
|
+
structHasField(recordsStruct, 'ctcss_mode');
|
|
132
268
|
const record = {
|
|
133
|
-
[bindings.receiveFrequency]:
|
|
134
|
-
[bindings.transmitFrequency]:
|
|
269
|
+
[bindings.receiveFrequency]: receiveFrequency,
|
|
270
|
+
[bindings.transmitFrequency]: usesOffset ? offset : transmitFrequency,
|
|
135
271
|
[bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),
|
|
136
272
|
[bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),
|
|
137
273
|
};
|
|
274
|
+
if (usesOffset) {
|
|
275
|
+
record.duplex = typeof channelSettings.duplex === 'string' ? channelSettings.duplex : duplex;
|
|
276
|
+
record.split = typeof channelSettings.split === 'boolean' ? channelSettings.split : split;
|
|
277
|
+
record.offset = offset;
|
|
278
|
+
}
|
|
279
|
+
if (usesKenwoodToneBits) {
|
|
280
|
+
record.tone_mode = false;
|
|
281
|
+
record.ctcss_mode = false;
|
|
282
|
+
record.dtcs_mode = false;
|
|
283
|
+
record.cross_mode = false;
|
|
284
|
+
if (channel.transmitTone?.type === RadioToneType.DCS || channel.receiveTone?.type === RadioToneType.DCS) {
|
|
285
|
+
record.dtcs_mode = true;
|
|
286
|
+
record.dtcs_code = radioToneToMapTone(channel.transmitTone?.type === RadioToneType.DCS ? channel.transmitTone : channel.receiveTone);
|
|
287
|
+
}
|
|
288
|
+
else if (!isNoneTone(channel.receiveTone) && !isNoneTone(channel.transmitTone)) {
|
|
289
|
+
record.ctcss_mode = true;
|
|
290
|
+
}
|
|
291
|
+
else if (!isNoneTone(channel.transmitTone)) {
|
|
292
|
+
record.tone_mode = true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
138
295
|
for (const [key, value] of Object.entries(channelSettings)) {
|
|
139
296
|
if (key === 'transmitPower') {
|
|
140
297
|
record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;
|
|
@@ -142,10 +299,12 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
142
299
|
}
|
|
143
300
|
if (key === 'mode') {
|
|
144
301
|
record.wide = value === 'FM';
|
|
302
|
+
record.mode = value;
|
|
145
303
|
continue;
|
|
146
304
|
}
|
|
147
305
|
if (key === 'skip') {
|
|
148
306
|
record.scan = value !== 'S';
|
|
307
|
+
record.skip = value === 'S';
|
|
149
308
|
continue;
|
|
150
309
|
}
|
|
151
310
|
record[key] = value;
|
|
@@ -160,12 +319,31 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
160
319
|
record.scan = true;
|
|
161
320
|
}
|
|
162
321
|
records[index] = record;
|
|
163
|
-
|
|
322
|
+
if (namesStruct) {
|
|
323
|
+
names[index] = { [nameField]: channel.name ?? '' };
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
record[nameField] = channel.name ?? '';
|
|
327
|
+
}
|
|
328
|
+
if (extrasStruct) {
|
|
329
|
+
const extraRecord = {
|
|
330
|
+
used: kenwoodUsedFlag(receiveFrequency, transmitFrequency),
|
|
331
|
+
lockout: channelSettings.skip === 'S' || channelSettings.lockout === true,
|
|
332
|
+
group: typeof channelSettings.group === 'number' ? channelSettings.group : 0,
|
|
333
|
+
};
|
|
334
|
+
if (structHasField(extrasStruct, 'band')) {
|
|
335
|
+
extraRecord.band = typeof channelSettings.band === 'number' ? channelSettings.band : kenwoodChmapBand(receiveFrequency);
|
|
336
|
+
}
|
|
337
|
+
extras[index] = extraRecord;
|
|
338
|
+
}
|
|
164
339
|
}
|
|
165
340
|
const bag = { ...program.settings, [bindings.records]: records };
|
|
166
341
|
if (bindings.names && namesStruct) {
|
|
167
342
|
bag[bindings.names] = names;
|
|
168
343
|
}
|
|
344
|
+
if (bindings.extras && extrasStruct) {
|
|
345
|
+
bag[bindings.extras] = extras;
|
|
346
|
+
}
|
|
169
347
|
return bag;
|
|
170
348
|
}
|
|
171
349
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-map-channels.js","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Frequency,\n type RadioChannel,\n type RadioMemoryConfig,\n type RadioMemoryMap,\n type RadioMemoryMapToneValue,\n type RadioProgram,\n type RadioProgrammedChannel,\n type RadioSettings,\n type RadioSettingValue,\n type RadioTone,\n RadioToneType,\n} from '@springfield/ham-radio-api';\nimport { decodeMemoryMap, encodeMemoryMap } from './memory-map-codec.js';\n\nfunction isToneValue(value: RadioSettingValue | undefined): value is RadioMemoryMapToneValue {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'mode' in value);\n}\n\nfunction toneToRadioTone(value: RadioSettingValue | undefined): RadioTone {\n if (!isToneValue(value) || value.mode === 'none') {\n return { tone: 0, type: RadioToneType.CTCSS };\n }\n\n if (value.mode === 'ctcss') {\n return { tone: value.value, type: RadioToneType.CTCSS };\n }\n\n return { tone: value.code, type: RadioToneType.DCS };\n}\n\nfunction radioToneToMapTone(tone: RadioTone | undefined): RadioMemoryMapToneValue {\n if (!tone || !tone.tone) {\n return { mode: 'none' };\n }\n\n if (tone.type === RadioToneType.DCS) {\n return { mode: 'dcs', code: Number(tone.tone), polarity: 'N' };\n }\n\n return { mode: 'ctcss', value: Number(tone.tone) };\n}\n\nfunction asRecord(value: RadioSettingValue | undefined): Record<string, RadioSettingValue> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n return value as Record<string, RadioSettingValue>;\n}\n\n/**\n * Project decoded memory-map channel structs into RadioProgrammedChannel[].\n */\nexport function bindingsToChannels(\n memoryMap: RadioMemoryMap,\n settings: RadioSettings,\n): RadioProgrammedChannel[] {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return [];\n }\n\n const records = settings[bindings.records];\n const names = bindings.names ? settings[bindings.names] : undefined;\n const recordItems = Array.isArray(records) ? records : [];\n const nameItems = Array.isArray(names) ? names : [];\n const nameField = bindings.nameField ?? 'name';\n const boundFieldIds = new Set([\n bindings.receiveFrequency,\n bindings.transmitFrequency,\n bindings.receiveTone,\n bindings.transmitTone,\n ]);\n\n const channels: RadioProgrammedChannel[] = [];\n\n for (let index = 0; index < recordItems.length; index += 1) {\n const record = asRecord(recordItems[index]);\n\n if (!record) {\n continue;\n }\n\n const nameRecord = asRecord(nameItems[index]);\n const nameValue = nameRecord?.[nameField];\n const name = typeof nameValue === 'string' ? nameValue : '';\n\n const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);\n const transmitFrequency = Number(record[bindings.transmitFrequency] ?? 0);\n\n const channelSettings: RadioSettings = {};\n\n for (const [fieldId, fieldValue] of Object.entries(record)) {\n if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {\n continue;\n }\n\n channelSettings[fieldId] = fieldValue;\n }\n\n // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.\n if (typeof channelSettings.lowpower === 'number') {\n channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;\n }\n\n if (typeof channelSettings.wide === 'boolean') {\n channelSettings.mode = channelSettings.wide ? 'FM' : 'NFM';\n }\n\n if (typeof channelSettings.scan === 'boolean') {\n channelSettings.skip = channelSettings.scan ? '' : 'S';\n }\n\n const radioChannel: RadioChannel = {\n name,\n receiveFrequency: Frequency(receiveFrequency),\n transmitFrequency: Frequency(transmitFrequency),\n receiveTone: toneToRadioTone(record[bindings.receiveTone]),\n transmitTone: toneToRadioTone(record[bindings.transmitTone]),\n };\n\n channels.push({\n channelNumber: index,\n radioChannel,\n settings: Object.keys(channelSettings).length > 0 ? channelSettings : undefined,\n });\n }\n\n return channels;\n}\n\n/**\n * Strip channel-bound structs from decoded settings so RadioProgram.settings\n * only contains radio-wide settings.\n */\nexport function settingsWithoutChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return settings;\n }\n\n const next: RadioSettings = { ...settings };\n delete next[bindings.records];\n\n if (bindings.names) {\n delete next[bindings.names];\n }\n\n return next;\n}\n\n/**\n * Merge RadioProgram channels back into a memory-map settings bag for encode.\n */\nexport function programToChannelSettings(memoryMap: RadioMemoryMap, program: RadioProgram): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return program.settings;\n }\n\n const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);\n const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;\n const count = recordsStruct?.count ?? 0;\n const nameField = bindings.nameField ?? 'name';\n\n const records: RadioSettingValue[] = Array.from({ length: count }, () => null);\n const names: RadioSettingValue[] = Array.from({ length: count }, () => null);\n\n for (const programmed of program.channels) {\n const index = programmed.channelNumber;\n\n if (index < 0 || index >= count) {\n continue;\n }\n\n if (typeof programmed.radioChannel === 'string') {\n continue;\n }\n\n const channel = programmed.radioChannel;\n const channelSettings = programmed.settings ?? {};\n const record: Record<string, RadioSettingValue> = {\n [bindings.receiveFrequency]: channel.receiveFrequency,\n [bindings.transmitFrequency]: channel.transmitFrequency,\n [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),\n [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),\n };\n\n for (const [key, value] of Object.entries(channelSettings)) {\n if (key === 'transmitPower') {\n record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;\n continue;\n }\n\n if (key === 'mode') {\n record.wide = value === 'FM';\n continue;\n }\n\n if (key === 'skip') {\n record.scan = value !== 'S';\n continue;\n }\n\n record[key] = value;\n }\n\n if (record.lowpower === undefined) {\n record.lowpower = 0;\n }\n\n if (record.wide === undefined) {\n record.wide = true;\n }\n\n if (record.scan === undefined) {\n record.scan = true;\n }\n\n records[index] = record;\n names[index] = { [nameField]: channel.name ?? '' };\n }\n\n const bag: RadioSettings = { ...program.settings, [bindings.records]: records };\n\n if (bindings.names && namesStruct) {\n bag[bindings.names] = names;\n }\n\n return bag;\n}\n\n/**\n * Decode a full RadioProgram (channels + radio-wide settings) from memory.\n */\nexport function decodeRadioProgram(\n memoryMap: RadioMemoryMap,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): RadioProgram {\n const decoded = decodeMemoryMap(memoryMap, contents, memoryConfig);\n\n return {\n channels: bindingsToChannels(memoryMap, decoded),\n settings: settingsWithoutChannels(memoryMap, decoded),\n };\n}\n\n/**\n * Encode a RadioProgram into an existing memory image (patch in place).\n */\nexport function encodeRadioProgram(\n memoryMap: RadioMemoryMap,\n program: RadioProgram,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): Uint8Array {\n const bag = programToChannelSettings(memoryMap, program);\n return encodeMemoryMap(memoryMap, bag, contents, memoryConfig);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"memory-map-channels.js","sourceRoot":"","sources":["../../src/memory/memory-map-channels.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Frequency,\n type RadioChannel,\n type RadioMemoryConfig,\n type RadioMemoryMap,\n type RadioMemoryMapToneValue,\n type RadioProgram,\n type RadioProgrammedChannel,\n type RadioSettings,\n type RadioSettingValue,\n type RadioTone,\n RadioToneType,\n} from '@springfield/ham-radio-api';\nimport { decodeMemoryMap, encodeMemoryMap } from './memory-map-codec.js';\n\nfunction isToneValue(value: RadioSettingValue | undefined): value is RadioMemoryMapToneValue {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'mode' in value);\n}\n\nfunction toneToRadioTone(value: RadioSettingValue | undefined): RadioTone {\n if (!isToneValue(value) || value.mode === 'none') {\n return { tone: 0, type: RadioToneType.CTCSS };\n }\n\n if (value.mode === 'ctcss') {\n return { tone: value.value, type: RadioToneType.CTCSS };\n }\n\n return { tone: value.code, type: RadioToneType.DCS };\n}\n\nfunction radioToneToMapTone(tone: RadioTone | undefined): RadioMemoryMapToneValue {\n if (!tone || !tone.tone) {\n return { mode: 'none' };\n }\n\n if (tone.type === RadioToneType.DCS) {\n return { mode: 'dcs', code: Number(tone.tone), polarity: 'N' };\n }\n\n return { mode: 'ctcss', value: Number(tone.tone) };\n}\n\nfunction noneTone(): RadioTone {\n return { tone: 0, type: RadioToneType.CTCSS };\n}\n\nfunction isNoneTone(tone: RadioTone | undefined): boolean {\n return !tone || !tone.tone;\n}\n\nfunction structHasField(struct: { fields: { id: string }[] } | undefined, fieldId: string): boolean {\n return Boolean(struct?.fields.some((field) => field.id === fieldId));\n}\n\nfunction transmitFrequencyFromRecord(record: Record<string, RadioSettingValue>, bindings: NonNullable<RadioMemoryMap['channelBindings']>): number {\n const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);\n const offsetOrTransmit = Number(record[bindings.transmitFrequency] ?? 0);\n\n if (record.split === true) {\n return offsetOrTransmit;\n }\n\n if (record.duplex === '+') {\n return receiveFrequency + offsetOrTransmit;\n }\n\n if (record.duplex === '-') {\n return receiveFrequency - offsetOrTransmit;\n }\n\n // Kenwood stores simplex as duplex \"\" and offset 0. Empty duplex is not an absolute TX frequency.\n if (record.duplex === '') {\n return receiveFrequency;\n }\n\n if (record.duplex === undefined) {\n return bindings.transmitFrequency === bindings.receiveFrequency ? receiveFrequency : offsetOrTransmit;\n }\n\n return offsetOrTransmit;\n}\n\nfunction applyKenwoodToneMode(\n record: Record<string, RadioSettingValue>,\n bindings: NonNullable<RadioMemoryMap['channelBindings']>,\n): { receiveTone: RadioTone; transmitTone: RadioTone } {\n const hasToneModeBits = 'tone_mode' in record || 'ctcss_mode' in record || 'dtcs_mode' in record;\n\n if (!hasToneModeBits) {\n return {\n receiveTone: toneToRadioTone(record[bindings.receiveTone]),\n transmitTone: toneToRadioTone(record[bindings.transmitTone]),\n };\n }\n\n if (record.tone_mode === true) {\n return { receiveTone: noneTone(), transmitTone: toneToRadioTone(record[bindings.transmitTone]) };\n }\n\n if (record.ctcss_mode === true) {\n const tone = toneToRadioTone(record[bindings.receiveTone]);\n return { receiveTone: tone, transmitTone: tone };\n }\n\n if (record.dtcs_mode === true) {\n const tone = toneToRadioTone(record.dtcs_code);\n return { receiveTone: tone, transmitTone: tone };\n }\n\n return { receiveTone: noneTone(), transmitTone: noneTone() };\n}\n\nfunction kenwoodUsedFlag(receiveFrequency: number, transmitFrequency: number): number {\n const frequency = transmitFrequency || receiveFrequency;\n\n if (frequency < 150_000_000) {\n return 0;\n }\n\n if (frequency < 400_000_000) {\n return 1;\n }\n\n return 2;\n}\n\n/** TM-D710 chmap band codes: 0=118 MHz, 5=144, 6=200, 7=300, 8=400, 9=800. */\nfunction kenwoodChmapBand(receiveFrequency: number): number {\n if (receiveFrequency < 136_000_000) {\n return 0;\n }\n\n if (receiveFrequency < 200_000_000) {\n return 5;\n }\n\n if (receiveFrequency < 300_000_000) {\n return 6;\n }\n\n if (receiveFrequency < 400_000_000) {\n return 7;\n }\n\n if (receiveFrequency < 800_000_000) {\n return 8;\n }\n\n return 9;\n}\n\nfunction asRecord(value: RadioSettingValue | undefined): Record<string, RadioSettingValue> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n return value as Record<string, RadioSettingValue>;\n}\n\n/**\n * Project decoded memory-map channel structs into RadioProgrammedChannel[].\n */\nexport function bindingsToChannels(\n memoryMap: RadioMemoryMap,\n settings: RadioSettings,\n): RadioProgrammedChannel[] {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return [];\n }\n\n const records = settings[bindings.records];\n const names = bindings.names ? settings[bindings.names] : undefined;\n const extras = bindings.extras ? settings[bindings.extras] : undefined;\n const recordItems = Array.isArray(records) ? records : [];\n const nameItems = Array.isArray(names) ? names : [];\n const extraItems = Array.isArray(extras) ? extras : [];\n const nameField = bindings.nameField ?? 'name';\n const boundFieldIds = new Set([\n bindings.receiveFrequency,\n bindings.transmitFrequency,\n bindings.receiveTone,\n bindings.transmitTone,\n nameField,\n ]);\n\n const channels: RadioProgrammedChannel[] = [];\n\n for (let index = 0; index < recordItems.length; index += 1) {\n const record = asRecord(recordItems[index]);\n const extraRecord = asRecord(extraItems[index]);\n\n if (bindings.extras && extraItems.length > 0 && extraRecord === undefined) {\n continue;\n }\n\n if (!record) {\n continue;\n }\n\n const nameRecord = asRecord(nameItems[index]);\n const nameFromTable = typeof nameRecord?.[nameField] === 'string' ? nameRecord[nameField] : '';\n const nameFromRecord = typeof record[nameField] === 'string' ? record[nameField] : '';\n const name = nameFromTable || nameFromRecord;\n\n const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);\n const transmitFrequency = transmitFrequencyFromRecord(record, bindings);\n const tones = applyKenwoodToneMode(record, bindings);\n\n const channelSettings: RadioSettings = {};\n\n for (const [fieldId, fieldValue] of Object.entries(record)) {\n if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {\n continue;\n }\n\n channelSettings[fieldId] = fieldValue;\n }\n\n if (extraRecord) {\n for (const [fieldId, fieldValue] of Object.entries(extraRecord)) {\n if (fieldId.startsWith('_') || fieldId === 'used') {\n continue;\n }\n\n channelSettings[fieldId] = fieldValue;\n }\n\n if (typeof extraRecord.lockout === 'boolean') {\n channelSettings.skip = extraRecord.lockout ? 'S' : '';\n }\n }\n\n // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.\n if (typeof channelSettings.lowpower === 'number') {\n channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;\n }\n\n if (typeof channelSettings.wide === 'boolean') {\n channelSettings.mode = channelSettings.wide ? 'FM' : 'NFM';\n }\n\n if (typeof channelSettings.scan === 'boolean') {\n channelSettings.skip = channelSettings.scan ? '' : 'S';\n }\n\n if (typeof channelSettings.skip === 'boolean') {\n channelSettings.skip = channelSettings.skip ? 'S' : '';\n }\n\n const radioChannel: RadioChannel = {\n name,\n receiveFrequency: Frequency(receiveFrequency),\n transmitFrequency: Frequency(transmitFrequency),\n receiveTone: tones.receiveTone,\n transmitTone: tones.transmitTone,\n };\n\n channels.push({\n channelNumber: index,\n radioChannel,\n settings: Object.keys(channelSettings).length > 0 ? channelSettings : undefined,\n });\n }\n\n return channels;\n}\n\n/**\n * Strip channel-bound structs from decoded settings so RadioProgram.settings\n * only contains radio-wide settings.\n */\nexport function settingsWithoutChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return settings;\n }\n\n const next: RadioSettings = { ...settings };\n delete next[bindings.records];\n\n if (bindings.names) {\n delete next[bindings.names];\n }\n\n if (bindings.extras) {\n delete next[bindings.extras];\n }\n\n return next;\n}\n\n/**\n * Merge RadioProgram channels back into a memory-map settings bag for encode.\n */\nexport function programToChannelSettings(memoryMap: RadioMemoryMap, program: RadioProgram): RadioSettings {\n const bindings = memoryMap.channelBindings;\n\n if (!bindings) {\n return program.settings;\n }\n\n const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);\n const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;\n const extrasStruct = bindings.extras ? memoryMap.structs.find((struct) => struct.id === bindings.extras) : undefined;\n const count = recordsStruct?.count ?? 0;\n const nameField = bindings.nameField ?? 'name';\n\n const records: RadioSettingValue[] = Array.from({ length: count }, () => null);\n const names: RadioSettingValue[] = Array.from({ length: count }, () => null);\n const extras: RadioSettingValue[] = Array.from({ length: count }, () => null);\n\n for (const programmed of program.channels) {\n const index = programmed.channelNumber;\n\n if (index < 0 || index >= count) {\n continue;\n }\n\n if (typeof programmed.radioChannel === 'string') {\n continue;\n }\n\n const channel = programmed.radioChannel;\n const channelSettings = programmed.settings ?? {};\n const receiveFrequency = Number(channel.receiveFrequency);\n const transmitFrequency = Number(channel.transmitFrequency);\n let offset = transmitFrequency;\n let duplex: RadioSettingValue = '';\n let split = false;\n\n if (transmitFrequency === receiveFrequency) {\n offset = 0;\n duplex = '';\n } else if (transmitFrequency > receiveFrequency) {\n offset = transmitFrequency - receiveFrequency;\n duplex = '+';\n } else {\n offset = receiveFrequency - transmitFrequency;\n duplex = '-';\n }\n\n if (channelSettings.split === true) {\n split = true;\n duplex = '';\n offset = transmitFrequency;\n }\n\n const usesOffset =\n 'duplex' in channelSettings || extrasStruct || structHasField(recordsStruct, 'duplex');\n const usesKenwoodToneBits =\n 'tone_mode' in channelSettings ||\n 'ctcss_mode' in channelSettings ||\n extrasStruct ||\n structHasField(recordsStruct, 'tone_mode') ||\n structHasField(recordsStruct, 'ctcss_mode');\n\n const record: Record<string, RadioSettingValue> = {\n [bindings.receiveFrequency]: receiveFrequency,\n [bindings.transmitFrequency]: usesOffset ? offset : transmitFrequency,\n [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),\n [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),\n };\n\n if (usesOffset) {\n record.duplex = typeof channelSettings.duplex === 'string' ? channelSettings.duplex : duplex;\n record.split = typeof channelSettings.split === 'boolean' ? channelSettings.split : split;\n record.offset = offset;\n }\n\n if (usesKenwoodToneBits) {\n record.tone_mode = false;\n record.ctcss_mode = false;\n record.dtcs_mode = false;\n record.cross_mode = false;\n\n if (channel.transmitTone?.type === RadioToneType.DCS || channel.receiveTone?.type === RadioToneType.DCS) {\n record.dtcs_mode = true;\n record.dtcs_code = radioToneToMapTone(channel.transmitTone?.type === RadioToneType.DCS ? channel.transmitTone : channel.receiveTone);\n } else if (!isNoneTone(channel.receiveTone) && !isNoneTone(channel.transmitTone)) {\n record.ctcss_mode = true;\n } else if (!isNoneTone(channel.transmitTone)) {\n record.tone_mode = true;\n }\n }\n\n for (const [key, value] of Object.entries(channelSettings)) {\n if (key === 'transmitPower') {\n record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;\n continue;\n }\n\n if (key === 'mode') {\n record.wide = value === 'FM';\n record.mode = value;\n continue;\n }\n\n if (key === 'skip') {\n record.scan = value !== 'S';\n record.skip = value === 'S';\n continue;\n }\n\n record[key] = value;\n }\n\n if (record.lowpower === undefined) {\n record.lowpower = 0;\n }\n\n if (record.wide === undefined) {\n record.wide = true;\n }\n\n if (record.scan === undefined) {\n record.scan = true;\n }\n\n records[index] = record;\n\n if (namesStruct) {\n names[index] = { [nameField]: channel.name ?? '' };\n } else {\n record[nameField] = channel.name ?? '';\n }\n\n if (extrasStruct) {\n const extraRecord: Record<string, RadioSettingValue> = {\n used: kenwoodUsedFlag(receiveFrequency, transmitFrequency),\n lockout: channelSettings.skip === 'S' || channelSettings.lockout === true,\n group: typeof channelSettings.group === 'number' ? channelSettings.group : 0,\n };\n\n if (structHasField(extrasStruct, 'band')) {\n extraRecord.band = typeof channelSettings.band === 'number' ? channelSettings.band : kenwoodChmapBand(receiveFrequency);\n }\n\n extras[index] = extraRecord;\n }\n }\n\n const bag: RadioSettings = { ...program.settings, [bindings.records]: records };\n\n if (bindings.names && namesStruct) {\n bag[bindings.names] = names;\n }\n\n if (bindings.extras && extrasStruct) {\n bag[bindings.extras] = extras;\n }\n\n return bag;\n}\n\n/**\n * Decode a full RadioProgram (channels + radio-wide settings) from memory.\n */\nexport function decodeRadioProgram(\n memoryMap: RadioMemoryMap,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): RadioProgram {\n const decoded = decodeMemoryMap(memoryMap, contents, memoryConfig);\n\n return {\n channels: bindingsToChannels(memoryMap, decoded),\n settings: settingsWithoutChannels(memoryMap, decoded),\n };\n}\n\n/**\n * Encode a RadioProgram into an existing memory image (patch in place).\n */\nexport function encodeRadioProgram(\n memoryMap: RadioMemoryMap,\n program: RadioProgram,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): Uint8Array {\n const bag = programToChannelSettings(memoryMap, program);\n return encodeMemoryMap(memoryMap, bag, contents, memoryConfig);\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RadioMemoryConfig, RadioMemoryMap, RadioSettings } from '@springfield/ham-radio-api';
|
|
1
|
+
import type { RadioMemoryConfig, RadioMemoryMap, RadioMemoryMapStruct, RadioSettings } from '@springfield/ham-radio-api';
|
|
2
2
|
/**
|
|
3
3
|
* Parse a seek address from a number or hex/decimal string.
|
|
4
4
|
*/
|
|
@@ -10,6 +10,11 @@ export declare function parseSeekAddress(seek: number | string): number;
|
|
|
10
10
|
* Sparse buffers that cover the highest segment end address use absolute offsets.
|
|
11
11
|
*/
|
|
12
12
|
export declare function radioAddressToBufferOffset(radioAddress: number, memoryConfig: RadioMemoryConfig, bufferLength: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Radio address of repeated-struct instance `index`, including Kenwood-style
|
|
15
|
+
* grouped stride (N records + pad bytes per clone block).
|
|
16
|
+
*/
|
|
17
|
+
export declare function structInstanceAddress(struct: RadioMemoryMapStruct, instanceIndex: number): number;
|
|
13
18
|
/**
|
|
14
19
|
* Decode radio-wide settings from a memory image using a JSON memory map.
|
|
15
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-map-codec.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,
|
|
1
|
+
{"version":3,"file":"memory-map-codec.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EAEd,oBAAoB,EAEpB,aAAa,EAEd,MAAM,4BAA4B,CAAC;AAIpC;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAY9D;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,iBAAiB,EAC/B,YAAY,EAAE,MAAM,GACnB,MAAM,CAmBR;AAgDD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAcjG;AA8gBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,aAAa,CAwBf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CA+BZ"}
|
|
@@ -48,13 +48,48 @@ function writeByte(contents, offset, value) {
|
|
|
48
48
|
}
|
|
49
49
|
contents[offset] = value & 0xff;
|
|
50
50
|
}
|
|
51
|
+
function decodeInteger(raw) {
|
|
52
|
+
if (typeof raw === 'number') {
|
|
53
|
+
return raw;
|
|
54
|
+
}
|
|
55
|
+
let value = 0;
|
|
56
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
57
|
+
value += raw[index] * 256 ** index;
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function encodeInteger(value, byteLength) {
|
|
62
|
+
let numeric = typeof value === 'number' ? value : 0;
|
|
63
|
+
const bytes = [];
|
|
64
|
+
for (let index = 0; index < byteLength; index += 1) {
|
|
65
|
+
bytes.push(numeric & 0xff);
|
|
66
|
+
numeric = Math.floor(numeric / 256);
|
|
67
|
+
}
|
|
68
|
+
return bytes;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Radio address of repeated-struct instance `index`, including Kenwood-style
|
|
72
|
+
* grouped stride (N records + pad bytes per clone block).
|
|
73
|
+
*/
|
|
74
|
+
export function structInstanceAddress(struct, instanceIndex) {
|
|
75
|
+
const seek = parseSeekAddress(struct.seek);
|
|
76
|
+
const stride = struct.stride ?? 0;
|
|
77
|
+
const groupSize = struct.groupSize;
|
|
78
|
+
if (!groupSize) {
|
|
79
|
+
return seek + instanceIndex * stride;
|
|
80
|
+
}
|
|
81
|
+
const groupPad = struct.groupPad ?? 0;
|
|
82
|
+
const groupIndex = Math.floor(instanceIndex / groupSize);
|
|
83
|
+
const indexInGroup = instanceIndex % groupSize;
|
|
84
|
+
return seek + groupIndex * (stride * groupSize + groupPad) + indexInGroup * stride;
|
|
85
|
+
}
|
|
51
86
|
function decodeRawValue(kind, raw) {
|
|
52
87
|
if (!kind) {
|
|
53
88
|
return typeof raw === 'number' ? raw : raw;
|
|
54
89
|
}
|
|
55
90
|
switch (kind.kind) {
|
|
56
91
|
case 'integer':
|
|
57
|
-
return
|
|
92
|
+
return decodeInteger(raw);
|
|
58
93
|
case 'boolean':
|
|
59
94
|
return (typeof raw === 'number' ? raw : raw[0]) !== 0;
|
|
60
95
|
case 'enum': {
|
|
@@ -135,6 +170,16 @@ function decodeRawValue(kind, raw) {
|
|
|
135
170
|
const code = kind.values[index] ?? 0;
|
|
136
171
|
return { mode: 'dcs', code, polarity };
|
|
137
172
|
}
|
|
173
|
+
case 'ctcss-index': {
|
|
174
|
+
const index = decodeInteger(raw);
|
|
175
|
+
const tenths = kind.values[index] ?? kind.values[0] ?? 0;
|
|
176
|
+
return { mode: 'ctcss', value: tenths };
|
|
177
|
+
}
|
|
178
|
+
case 'dcs-index': {
|
|
179
|
+
const index = decodeInteger(raw);
|
|
180
|
+
const code = kind.values[index] ?? 0;
|
|
181
|
+
return { mode: 'dcs', code, polarity: 'N' };
|
|
182
|
+
}
|
|
138
183
|
default: {
|
|
139
184
|
const exhaustive = kind;
|
|
140
185
|
return exhaustive;
|
|
@@ -151,8 +196,7 @@ function encodeRawValue(kind, value, byteLength) {
|
|
|
151
196
|
}
|
|
152
197
|
switch (kind.kind) {
|
|
153
198
|
case 'integer': {
|
|
154
|
-
|
|
155
|
-
return bytes;
|
|
199
|
+
return encodeInteger(value, byteLength);
|
|
156
200
|
}
|
|
157
201
|
case 'boolean': {
|
|
158
202
|
bytes[0] = value ? 1 : 0;
|
|
@@ -165,8 +209,9 @@ function encodeRawValue(kind, value, byteLength) {
|
|
|
165
209
|
}
|
|
166
210
|
case 'ascii': {
|
|
167
211
|
const text = typeof value === 'string' ? value : '';
|
|
212
|
+
const pad = kind.pad ?? 0xff;
|
|
168
213
|
for (let index = 0; index < byteLength; index += 1) {
|
|
169
|
-
bytes[index] = index < text.length ? (text.codePointAt(index) ??
|
|
214
|
+
bytes[index] = index < text.length ? (text.codePointAt(index) ?? pad) : pad;
|
|
170
215
|
}
|
|
171
216
|
return bytes;
|
|
172
217
|
}
|
|
@@ -242,6 +287,18 @@ function encodeRawValue(kind, value, byteLength) {
|
|
|
242
287
|
bytes[1] = (rawValue >> 8) & 0xff;
|
|
243
288
|
return bytes;
|
|
244
289
|
}
|
|
290
|
+
case 'ctcss-index': {
|
|
291
|
+
const tone = value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
292
|
+
const tenths = tone?.mode === 'ctcss' && typeof tone.value === 'number' ? tone.value : 0;
|
|
293
|
+
const index = kind.values.indexOf(tenths);
|
|
294
|
+
return encodeInteger(index >= 0 ? index : 0, byteLength);
|
|
295
|
+
}
|
|
296
|
+
case 'dcs-index': {
|
|
297
|
+
const tone = value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
298
|
+
const code = tone?.mode === 'dcs' && typeof tone.code === 'number' ? tone.code : 0;
|
|
299
|
+
const index = kind.values.indexOf(code);
|
|
300
|
+
return encodeInteger(index >= 0 ? index : 0, byteLength);
|
|
301
|
+
}
|
|
245
302
|
default: {
|
|
246
303
|
const exhaustive = kind;
|
|
247
304
|
return exhaustive;
|
|
@@ -249,6 +306,9 @@ function encodeRawValue(kind, value, byteLength) {
|
|
|
249
306
|
}
|
|
250
307
|
}
|
|
251
308
|
function fieldByteLength(field) {
|
|
309
|
+
if (field.type === 'u32') {
|
|
310
|
+
return 4;
|
|
311
|
+
}
|
|
252
312
|
if (field.type === 'u16') {
|
|
253
313
|
return 2;
|
|
254
314
|
}
|
|
@@ -338,7 +398,7 @@ class BitfieldCursor {
|
|
|
338
398
|
}
|
|
339
399
|
}
|
|
340
400
|
function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
|
|
341
|
-
const base =
|
|
401
|
+
const base = structInstanceAddress(struct, instanceIndex);
|
|
342
402
|
const result = {};
|
|
343
403
|
let radioAddress = base;
|
|
344
404
|
let bitCursor;
|
|
@@ -362,7 +422,7 @@ function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
|
|
|
362
422
|
for (let index = 0; index < length; index += 1) {
|
|
363
423
|
bytes.push(readByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents)));
|
|
364
424
|
}
|
|
365
|
-
if (field.type === 'u16') {
|
|
425
|
+
if (field.type === 'u16' || field.type === 'u32') {
|
|
366
426
|
if (!field.reserved) {
|
|
367
427
|
result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, bytes);
|
|
368
428
|
}
|
|
@@ -375,7 +435,7 @@ function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
|
|
|
375
435
|
return result;
|
|
376
436
|
}
|
|
377
437
|
function encodeStruct(struct, values, contents, memoryConfig, instanceIndex) {
|
|
378
|
-
const base =
|
|
438
|
+
const base = structInstanceAddress(struct, instanceIndex);
|
|
379
439
|
let radioAddress = base;
|
|
380
440
|
let bitCursor;
|
|
381
441
|
let bitStartAddress = base;
|
|
@@ -406,17 +466,11 @@ function encodeStruct(struct, values, contents, memoryConfig, instanceIndex) {
|
|
|
406
466
|
continue;
|
|
407
467
|
}
|
|
408
468
|
const settingValue = values[field.id];
|
|
409
|
-
if (field.type === 'u16') {
|
|
410
|
-
const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0,
|
|
411
|
-
let
|
|
412
|
-
|
|
413
|
-
value = (encoded[0] & 0xff) | ((encoded[1] & 0xff) << 8);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
value = (encoded[0] ?? 0) & 0xffff;
|
|
469
|
+
if (field.type === 'u16' || field.type === 'u32') {
|
|
470
|
+
const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, length);
|
|
471
|
+
for (let index = 0; index < length; index += 1) {
|
|
472
|
+
writeByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents), encoded[index] ?? 0);
|
|
417
473
|
}
|
|
418
|
-
writeByte(contents, bufferOffsetFor(radioAddress, memoryConfig, contents), value & 0xff);
|
|
419
|
-
writeByte(contents, bufferOffsetFor(radioAddress + 1, memoryConfig, contents), (value >> 8) & 0xff);
|
|
420
474
|
}
|
|
421
475
|
else {
|
|
422
476
|
const encoded = encodeRawValue(field.value, settingValue ?? (field.value?.kind === 'ascii' ? '' : 0), length);
|
|
@@ -434,12 +488,12 @@ function isStructInstanceEmpty(struct, contents, memoryConfig, instanceIndex) {
|
|
|
434
488
|
if (!struct.emptyWhen) {
|
|
435
489
|
return false;
|
|
436
490
|
}
|
|
437
|
-
const base =
|
|
491
|
+
const base = structInstanceAddress(struct, instanceIndex);
|
|
438
492
|
const firstByte = readByte(contents, bufferOffsetFor(base, memoryConfig, contents));
|
|
439
493
|
return firstByte === struct.emptyWhen.equals;
|
|
440
494
|
}
|
|
441
495
|
function clearStructInstance(struct, contents, memoryConfig, instanceIndex) {
|
|
442
|
-
const base =
|
|
496
|
+
const base = structInstanceAddress(struct, instanceIndex);
|
|
443
497
|
const length = struct.stride ?? 16;
|
|
444
498
|
for (let index = 0; index < length; index += 1) {
|
|
445
499
|
writeByte(contents, bufferOffsetFor(base + index, memoryConfig, contents), 0xff);
|