@springfield/ham-radio-utils 4.6.0 → 4.7.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 +163 -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 +199 -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;AA2HpC;;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,CAwJxG;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,67 @@ 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
|
+
}
|
|
24
85
|
function asRecord(value) {
|
|
25
86
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
26
87
|
return undefined;
|
|
@@ -37,26 +98,35 @@ export function bindingsToChannels(memoryMap, settings) {
|
|
|
37
98
|
}
|
|
38
99
|
const records = settings[bindings.records];
|
|
39
100
|
const names = bindings.names ? settings[bindings.names] : undefined;
|
|
101
|
+
const extras = bindings.extras ? settings[bindings.extras] : undefined;
|
|
40
102
|
const recordItems = Array.isArray(records) ? records : [];
|
|
41
103
|
const nameItems = Array.isArray(names) ? names : [];
|
|
104
|
+
const extraItems = Array.isArray(extras) ? extras : [];
|
|
42
105
|
const nameField = bindings.nameField ?? 'name';
|
|
43
106
|
const boundFieldIds = new Set([
|
|
44
107
|
bindings.receiveFrequency,
|
|
45
108
|
bindings.transmitFrequency,
|
|
46
109
|
bindings.receiveTone,
|
|
47
110
|
bindings.transmitTone,
|
|
111
|
+
nameField,
|
|
48
112
|
]);
|
|
49
113
|
const channels = [];
|
|
50
114
|
for (let index = 0; index < recordItems.length; index += 1) {
|
|
51
115
|
const record = asRecord(recordItems[index]);
|
|
116
|
+
const extraRecord = asRecord(extraItems[index]);
|
|
117
|
+
if (bindings.extras && extraItems.length > 0 && extraRecord === undefined) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
52
120
|
if (!record) {
|
|
53
121
|
continue;
|
|
54
122
|
}
|
|
55
123
|
const nameRecord = asRecord(nameItems[index]);
|
|
56
|
-
const
|
|
57
|
-
const
|
|
124
|
+
const nameFromTable = typeof nameRecord?.[nameField] === 'string' ? nameRecord[nameField] : '';
|
|
125
|
+
const nameFromRecord = typeof record[nameField] === 'string' ? record[nameField] : '';
|
|
126
|
+
const name = nameFromTable || nameFromRecord;
|
|
58
127
|
const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
|
|
59
|
-
const transmitFrequency =
|
|
128
|
+
const transmitFrequency = transmitFrequencyFromRecord(record, bindings);
|
|
129
|
+
const tones = applyKenwoodToneMode(record, bindings);
|
|
60
130
|
const channelSettings = {};
|
|
61
131
|
for (const [fieldId, fieldValue] of Object.entries(record)) {
|
|
62
132
|
if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {
|
|
@@ -64,6 +134,17 @@ export function bindingsToChannels(memoryMap, settings) {
|
|
|
64
134
|
}
|
|
65
135
|
channelSettings[fieldId] = fieldValue;
|
|
66
136
|
}
|
|
137
|
+
if (extraRecord) {
|
|
138
|
+
for (const [fieldId, fieldValue] of Object.entries(extraRecord)) {
|
|
139
|
+
if (fieldId.startsWith('_') || fieldId === 'used') {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
channelSettings[fieldId] = fieldValue;
|
|
143
|
+
}
|
|
144
|
+
if (typeof extraRecord.lockout === 'boolean') {
|
|
145
|
+
channelSettings.skip = extraRecord.lockout ? 'S' : '';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
67
148
|
// Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.
|
|
68
149
|
if (typeof channelSettings.lowpower === 'number') {
|
|
69
150
|
channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;
|
|
@@ -74,12 +155,15 @@ export function bindingsToChannels(memoryMap, settings) {
|
|
|
74
155
|
if (typeof channelSettings.scan === 'boolean') {
|
|
75
156
|
channelSettings.skip = channelSettings.scan ? '' : 'S';
|
|
76
157
|
}
|
|
158
|
+
if (typeof channelSettings.skip === 'boolean') {
|
|
159
|
+
channelSettings.skip = channelSettings.skip ? 'S' : '';
|
|
160
|
+
}
|
|
77
161
|
const radioChannel = {
|
|
78
162
|
name,
|
|
79
163
|
receiveFrequency: Frequency(receiveFrequency),
|
|
80
164
|
transmitFrequency: Frequency(transmitFrequency),
|
|
81
|
-
receiveTone:
|
|
82
|
-
transmitTone:
|
|
165
|
+
receiveTone: tones.receiveTone,
|
|
166
|
+
transmitTone: tones.transmitTone,
|
|
83
167
|
};
|
|
84
168
|
channels.push({
|
|
85
169
|
channelNumber: index,
|
|
@@ -103,6 +187,9 @@ export function settingsWithoutChannels(memoryMap, settings) {
|
|
|
103
187
|
if (bindings.names) {
|
|
104
188
|
delete next[bindings.names];
|
|
105
189
|
}
|
|
190
|
+
if (bindings.extras) {
|
|
191
|
+
delete next[bindings.extras];
|
|
192
|
+
}
|
|
106
193
|
return next;
|
|
107
194
|
}
|
|
108
195
|
/**
|
|
@@ -115,10 +202,12 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
115
202
|
}
|
|
116
203
|
const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);
|
|
117
204
|
const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;
|
|
205
|
+
const extrasStruct = bindings.extras ? memoryMap.structs.find((struct) => struct.id === bindings.extras) : undefined;
|
|
118
206
|
const count = recordsStruct?.count ?? 0;
|
|
119
207
|
const nameField = bindings.nameField ?? 'name';
|
|
120
208
|
const records = Array.from({ length: count }, () => null);
|
|
121
209
|
const names = Array.from({ length: count }, () => null);
|
|
210
|
+
const extras = Array.from({ length: count }, () => null);
|
|
122
211
|
for (const programmed of program.channels) {
|
|
123
212
|
const index = programmed.channelNumber;
|
|
124
213
|
if (index < 0 || index >= count) {
|
|
@@ -129,12 +218,61 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
129
218
|
}
|
|
130
219
|
const channel = programmed.radioChannel;
|
|
131
220
|
const channelSettings = programmed.settings ?? {};
|
|
221
|
+
const receiveFrequency = Number(channel.receiveFrequency);
|
|
222
|
+
const transmitFrequency = Number(channel.transmitFrequency);
|
|
223
|
+
let offset = transmitFrequency;
|
|
224
|
+
let duplex = '';
|
|
225
|
+
let split = false;
|
|
226
|
+
if (transmitFrequency === receiveFrequency) {
|
|
227
|
+
offset = 0;
|
|
228
|
+
duplex = '';
|
|
229
|
+
}
|
|
230
|
+
else if (transmitFrequency > receiveFrequency) {
|
|
231
|
+
offset = transmitFrequency - receiveFrequency;
|
|
232
|
+
duplex = '+';
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
offset = receiveFrequency - transmitFrequency;
|
|
236
|
+
duplex = '-';
|
|
237
|
+
}
|
|
238
|
+
if (channelSettings.split === true) {
|
|
239
|
+
split = true;
|
|
240
|
+
duplex = '';
|
|
241
|
+
offset = transmitFrequency;
|
|
242
|
+
}
|
|
243
|
+
const usesOffset = 'duplex' in channelSettings || extrasStruct || structHasField(recordsStruct, 'duplex');
|
|
244
|
+
const usesKenwoodToneBits = 'tone_mode' in channelSettings ||
|
|
245
|
+
'ctcss_mode' in channelSettings ||
|
|
246
|
+
extrasStruct ||
|
|
247
|
+
structHasField(recordsStruct, 'tone_mode') ||
|
|
248
|
+
structHasField(recordsStruct, 'ctcss_mode');
|
|
132
249
|
const record = {
|
|
133
|
-
[bindings.receiveFrequency]:
|
|
134
|
-
[bindings.transmitFrequency]:
|
|
250
|
+
[bindings.receiveFrequency]: receiveFrequency,
|
|
251
|
+
[bindings.transmitFrequency]: usesOffset ? offset : transmitFrequency,
|
|
135
252
|
[bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),
|
|
136
253
|
[bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),
|
|
137
254
|
};
|
|
255
|
+
if (usesOffset) {
|
|
256
|
+
record.duplex = typeof channelSettings.duplex === 'string' ? channelSettings.duplex : duplex;
|
|
257
|
+
record.split = typeof channelSettings.split === 'boolean' ? channelSettings.split : split;
|
|
258
|
+
record.offset = offset;
|
|
259
|
+
}
|
|
260
|
+
if (usesKenwoodToneBits) {
|
|
261
|
+
record.tone_mode = false;
|
|
262
|
+
record.ctcss_mode = false;
|
|
263
|
+
record.dtcs_mode = false;
|
|
264
|
+
record.cross_mode = false;
|
|
265
|
+
if (channel.transmitTone?.type === RadioToneType.DCS || channel.receiveTone?.type === RadioToneType.DCS) {
|
|
266
|
+
record.dtcs_mode = true;
|
|
267
|
+
record.dtcs_code = radioToneToMapTone(channel.transmitTone?.type === RadioToneType.DCS ? channel.transmitTone : channel.receiveTone);
|
|
268
|
+
}
|
|
269
|
+
else if (!isNoneTone(channel.receiveTone) && !isNoneTone(channel.transmitTone)) {
|
|
270
|
+
record.ctcss_mode = true;
|
|
271
|
+
}
|
|
272
|
+
else if (!isNoneTone(channel.transmitTone)) {
|
|
273
|
+
record.tone_mode = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
138
276
|
for (const [key, value] of Object.entries(channelSettings)) {
|
|
139
277
|
if (key === 'transmitPower') {
|
|
140
278
|
record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;
|
|
@@ -142,10 +280,12 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
142
280
|
}
|
|
143
281
|
if (key === 'mode') {
|
|
144
282
|
record.wide = value === 'FM';
|
|
283
|
+
record.mode = value;
|
|
145
284
|
continue;
|
|
146
285
|
}
|
|
147
286
|
if (key === 'skip') {
|
|
148
287
|
record.scan = value !== 'S';
|
|
288
|
+
record.skip = value === 'S';
|
|
149
289
|
continue;
|
|
150
290
|
}
|
|
151
291
|
record[key] = value;
|
|
@@ -160,12 +300,27 @@ export function programToChannelSettings(memoryMap, program) {
|
|
|
160
300
|
record.scan = true;
|
|
161
301
|
}
|
|
162
302
|
records[index] = record;
|
|
163
|
-
|
|
303
|
+
if (namesStruct) {
|
|
304
|
+
names[index] = { [nameField]: channel.name ?? '' };
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
record[nameField] = channel.name ?? '';
|
|
308
|
+
}
|
|
309
|
+
if (extrasStruct) {
|
|
310
|
+
extras[index] = {
|
|
311
|
+
used: kenwoodUsedFlag(receiveFrequency, transmitFrequency),
|
|
312
|
+
lockout: channelSettings.skip === 'S' || channelSettings.lockout === true,
|
|
313
|
+
group: typeof channelSettings.group === 'number' ? channelSettings.group : 0,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
164
316
|
}
|
|
165
317
|
const bag = { ...program.settings, [bindings.records]: records };
|
|
166
318
|
if (bindings.names && namesStruct) {
|
|
167
319
|
bag[bindings.names] = names;
|
|
168
320
|
}
|
|
321
|
+
if (bindings.extras && extrasStruct) {
|
|
322
|
+
bag[bindings.extras] = extras;
|
|
323
|
+
}
|
|
169
324
|
return bag;
|
|
170
325
|
}
|
|
171
326
|
/**
|
|
@@ -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\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 extras[index] = {\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 }\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);
|