@springfield/ham-radio-utils 4.2.0 → 4.4.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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './memory/segmented-memory.js';
2
2
  export * from './memory/memory-map-codec.js';
3
+ export * from './memory/memory-map-channels.js';
3
4
  export * from './memory/memory-map-ui.js';
4
5
  export * from './test-utils/radio-channel-factory.js';
5
6
  export * from './test-utils/radio-programmed-channel-factory.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAuB;AAC5G,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wCAAwC,CAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAuB;AAC5G,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wCAAwC,CAAuB"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './memory/segmented-memory.js';
2
2
  export * from './memory/memory-map-codec.js';
3
+ export * from './memory/memory-map-channels.js';
3
4
  export * from './memory/memory-map-ui.js';
4
5
  export * from './test-utils/radio-channel-factory.js';
5
6
  export * from './test-utils/radio-programmed-channel-factory.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAE7C,mCAAmC;AACnC,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5G,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wCAAwC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\nexport * from './memory/memory-map-codec.js';\nexport * from './memory/memory-map-ui.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/operator-class-mapper.js';\nexport * from './utils/schema-validator.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n\n// Export the radio protocol schema\nexport { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };\nexport { default as radioMemoryMapSchema } from './schemas/radio-memory-map-schema.json' with { type: 'json' };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;AAE1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,kDAAkD,CAAC;AAEjE,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kCAAkC,CAAC;AACjD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAE7C,mCAAmC;AACnC,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,sCAAsC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5G,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wCAAwC,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC","sourcesContent":["export * from './memory/segmented-memory.js';\nexport * from './memory/memory-map-codec.js';\nexport * from './memory/memory-map-channels.js';\nexport * from './memory/memory-map-ui.js';\n\nexport * from './test-utils/radio-channel-factory.js';\nexport * from './test-utils/radio-programmed-channel-factory.js';\n\nexport * from './utils/band-plan.js';\nexport * from './utils/frequency-display.js';\nexport * from './utils/memory-channel-utils.js';\nexport * from './utils/memory-data-utils.js';\nexport * from './utils/operator-class-mapper.js';\nexport * from './utils/schema-validator.js';\nexport * from './utils/to-hex-words.js';\nexport * from './utils/ui-logger.js';\nexport * from './utils/ui-logger-factory.js';\n\n// Export the radio protocol schema\nexport { default as radioProtocolSchema } from './schemas/radio-protocol-schema.json' with { type: 'json' };\nexport { default as radioMemoryMapSchema } from './schemas/radio-memory-map-schema.json' with { type: 'json' };\n"]}
@@ -0,0 +1,23 @@
1
+ import { type RadioMemoryConfig, type RadioMemoryMap, type RadioProgram, type RadioProgrammedChannel, type RadioSettings } from '@springfield/ham-radio-api';
2
+ /**
3
+ * Project decoded memory-map channel structs into RadioProgrammedChannel[].
4
+ */
5
+ export declare function bindingsToChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioProgrammedChannel[];
6
+ /**
7
+ * Strip channel-bound structs from decoded settings so RadioProgram.settings
8
+ * only contains radio-wide settings.
9
+ */
10
+ export declare function settingsWithoutChannels(memoryMap: RadioMemoryMap, settings: RadioSettings): RadioSettings;
11
+ /**
12
+ * Merge RadioProgram channels back into a memory-map settings bag for encode.
13
+ */
14
+ export declare function programToChannelSettings(memoryMap: RadioMemoryMap, program: RadioProgram): RadioSettings;
15
+ /**
16
+ * Decode a full RadioProgram (channels + radio-wide settings) from memory.
17
+ */
18
+ export declare function decodeRadioProgram(memoryMap: RadioMemoryMap, contents: Uint8Array, memoryConfig: RadioMemoryConfig): RadioProgram;
19
+ /**
20
+ * Encode a RadioProgram into an existing memory image (patch in place).
21
+ */
22
+ export declare function encodeRadioProgram(memoryMap: RadioMemoryMap, program: RadioProgram, contents: Uint8Array, memoryConfig: RadioMemoryConfig): Uint8Array;
23
+ //# sourceMappingURL=memory-map-channels.d.ts.map
@@ -0,0 +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;AAuCpC;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,GACtB,sBAAsB,EAAE,CA0E1B;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,aAAa,GAAG,aAAa,CAezG;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,YAAY,GAAG,aAAa,CA6ExG;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"}
@@ -0,0 +1,188 @@
1
+ import { Frequency, RadioToneType, } from '@springfield/ham-radio-api';
2
+ import { decodeMemoryMap, encodeMemoryMap } from './memory-map-codec.js';
3
+ function isToneValue(value) {
4
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'mode' in value);
5
+ }
6
+ function toneToRadioTone(value) {
7
+ if (!isToneValue(value) || value.mode === 'none') {
8
+ return { tone: 0, type: RadioToneType.CTCSS };
9
+ }
10
+ if (value.mode === 'ctcss') {
11
+ return { tone: value.value, type: RadioToneType.CTCSS };
12
+ }
13
+ return { tone: value.code, type: RadioToneType.DCS };
14
+ }
15
+ function radioToneToMapTone(tone) {
16
+ if (!tone || !tone.tone) {
17
+ return { mode: 'none' };
18
+ }
19
+ if (tone.type === RadioToneType.DCS) {
20
+ return { mode: 'dcs', code: Number(tone.tone), polarity: 'N' };
21
+ }
22
+ return { mode: 'ctcss', value: Number(tone.tone) };
23
+ }
24
+ function asRecord(value) {
25
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
26
+ return undefined;
27
+ }
28
+ return value;
29
+ }
30
+ /**
31
+ * Project decoded memory-map channel structs into RadioProgrammedChannel[].
32
+ */
33
+ export function bindingsToChannels(memoryMap, settings) {
34
+ const bindings = memoryMap.channelBindings;
35
+ if (!bindings) {
36
+ return [];
37
+ }
38
+ const records = settings[bindings.records];
39
+ const names = bindings.names ? settings[bindings.names] : undefined;
40
+ const recordItems = Array.isArray(records) ? records : [];
41
+ const nameItems = Array.isArray(names) ? names : [];
42
+ const nameField = bindings.nameField ?? 'name';
43
+ const boundFieldIds = new Set([
44
+ bindings.receiveFrequency,
45
+ bindings.transmitFrequency,
46
+ bindings.receiveTone,
47
+ bindings.transmitTone,
48
+ ]);
49
+ const channels = [];
50
+ for (let index = 0; index < recordItems.length; index += 1) {
51
+ const record = asRecord(recordItems[index]);
52
+ if (!record) {
53
+ continue;
54
+ }
55
+ const nameRecord = asRecord(nameItems[index]);
56
+ const nameValue = nameRecord?.[nameField];
57
+ const name = typeof nameValue === 'string' ? nameValue : '';
58
+ const receiveFrequency = Number(record[bindings.receiveFrequency] ?? 0);
59
+ const transmitFrequency = Number(record[bindings.transmitFrequency] ?? 0);
60
+ const channelSettings = {};
61
+ for (const [fieldId, fieldValue] of Object.entries(record)) {
62
+ if (boundFieldIds.has(fieldId) || fieldId.startsWith('_')) {
63
+ continue;
64
+ }
65
+ channelSettings[fieldId] = fieldValue;
66
+ }
67
+ // Map Chirp lowpower index onto legacy transmitPower watts for UV-5R UI compatibility.
68
+ if (typeof channelSettings.lowpower === 'number') {
69
+ channelSettings.transmitPower = channelSettings.lowpower === 0 ? 5 : 1;
70
+ }
71
+ if (typeof channelSettings.wide === 'boolean') {
72
+ channelSettings.mode = channelSettings.wide ? 'FM' : 'NFM';
73
+ }
74
+ if (typeof channelSettings.scan === 'boolean') {
75
+ channelSettings.skip = channelSettings.scan ? '' : 'S';
76
+ }
77
+ const radioChannel = {
78
+ name,
79
+ receiveFrequency: Frequency(receiveFrequency),
80
+ transmitFrequency: Frequency(transmitFrequency),
81
+ receiveTone: toneToRadioTone(record[bindings.receiveTone]),
82
+ transmitTone: toneToRadioTone(record[bindings.transmitTone]),
83
+ };
84
+ channels.push({
85
+ channelNumber: index,
86
+ radioChannel,
87
+ settings: Object.keys(channelSettings).length > 0 ? channelSettings : undefined,
88
+ });
89
+ }
90
+ return channels;
91
+ }
92
+ /**
93
+ * Strip channel-bound structs from decoded settings so RadioProgram.settings
94
+ * only contains radio-wide settings.
95
+ */
96
+ export function settingsWithoutChannels(memoryMap, settings) {
97
+ const bindings = memoryMap.channelBindings;
98
+ if (!bindings) {
99
+ return settings;
100
+ }
101
+ const next = { ...settings };
102
+ delete next[bindings.records];
103
+ if (bindings.names) {
104
+ delete next[bindings.names];
105
+ }
106
+ return next;
107
+ }
108
+ /**
109
+ * Merge RadioProgram channels back into a memory-map settings bag for encode.
110
+ */
111
+ export function programToChannelSettings(memoryMap, program) {
112
+ const bindings = memoryMap.channelBindings;
113
+ if (!bindings) {
114
+ return program.settings;
115
+ }
116
+ const recordsStruct = memoryMap.structs.find((struct) => struct.id === bindings.records);
117
+ const namesStruct = bindings.names ? memoryMap.structs.find((struct) => struct.id === bindings.names) : undefined;
118
+ const count = recordsStruct?.count ?? 0;
119
+ const nameField = bindings.nameField ?? 'name';
120
+ const records = Array.from({ length: count }, () => null);
121
+ const names = Array.from({ length: count }, () => null);
122
+ for (const programmed of program.channels) {
123
+ const index = programmed.channelNumber;
124
+ if (index < 0 || index >= count) {
125
+ continue;
126
+ }
127
+ if (typeof programmed.radioChannel === 'string') {
128
+ continue;
129
+ }
130
+ const channel = programmed.radioChannel;
131
+ const channelSettings = programmed.settings ?? {};
132
+ const record = {
133
+ [bindings.receiveFrequency]: channel.receiveFrequency,
134
+ [bindings.transmitFrequency]: channel.transmitFrequency,
135
+ [bindings.receiveTone]: radioToneToMapTone(channel.receiveTone),
136
+ [bindings.transmitTone]: radioToneToMapTone(channel.transmitTone),
137
+ };
138
+ for (const [key, value] of Object.entries(channelSettings)) {
139
+ if (key === 'transmitPower') {
140
+ record.lowpower = value === 5 || value === 4 || value === 0 ? 0 : 1;
141
+ continue;
142
+ }
143
+ if (key === 'mode') {
144
+ record.wide = value === 'FM';
145
+ continue;
146
+ }
147
+ if (key === 'skip') {
148
+ record.scan = value !== 'S';
149
+ continue;
150
+ }
151
+ record[key] = value;
152
+ }
153
+ if (record.lowpower === undefined) {
154
+ record.lowpower = 0;
155
+ }
156
+ if (record.wide === undefined) {
157
+ record.wide = true;
158
+ }
159
+ if (record.scan === undefined) {
160
+ record.scan = true;
161
+ }
162
+ records[index] = record;
163
+ names[index] = { [nameField]: channel.name ?? '' };
164
+ }
165
+ const bag = { ...program.settings, [bindings.records]: records };
166
+ if (bindings.names && namesStruct) {
167
+ bag[bindings.names] = names;
168
+ }
169
+ return bag;
170
+ }
171
+ /**
172
+ * Decode a full RadioProgram (channels + radio-wide settings) from memory.
173
+ */
174
+ export function decodeRadioProgram(memoryMap, contents, memoryConfig) {
175
+ const decoded = decodeMemoryMap(memoryMap, contents, memoryConfig);
176
+ return {
177
+ channels: bindingsToChannels(memoryMap, decoded),
178
+ settings: settingsWithoutChannels(memoryMap, decoded),
179
+ };
180
+ }
181
+ /**
182
+ * Encode a RadioProgram into an existing memory image (patch in place).
183
+ */
184
+ export function encodeRadioProgram(memoryMap, program, contents, memoryConfig) {
185
+ const bag = programToChannelSettings(memoryMap, program);
186
+ return encodeMemoryMap(memoryMap, bag, contents, memoryConfig);
187
+ }
188
+ //# sourceMappingURL=memory-map-channels.js.map
@@ -0,0 +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 +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,EAId,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;AAmZD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,aAAa,CAoBf;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,UAAU,EACpB,YAAY,EAAE,iBAAiB,GAC9B,UAAU,CAsBZ"}
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,EAId,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;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"}
@@ -102,6 +102,39 @@ function decodeRawValue(kind, raw) {
102
102
  }
103
103
  return value;
104
104
  }
105
+ case 'lbcd': {
106
+ const bytes = typeof raw === 'number' ? [raw] : raw;
107
+ let word = 0;
108
+ for (let index = 0; index < bytes.length; index += 1) {
109
+ word |= bytes[index] << (8 * index);
110
+ }
111
+ if (word === 0xff_ff_ff_ff || bytes.every((byte) => byte === 0xff)) {
112
+ return null;
113
+ }
114
+ return Number.parseInt(word.toString(16), 10) * (kind.scale ?? 10);
115
+ }
116
+ case 'tone': {
117
+ const rawValue = typeof raw === 'number' ? raw : raw[0] | (raw[1] << 8);
118
+ const ctcssMin = kind.ctcssMin ?? 0x0258;
119
+ const reverseOffset = kind.reverseOffset ?? 0x69;
120
+ if (rawValue === 0 || rawValue === 0xffff) {
121
+ return { mode: 'none' };
122
+ }
123
+ if (rawValue >= ctcssMin) {
124
+ return { mode: 'ctcss', value: rawValue };
125
+ }
126
+ let index = rawValue;
127
+ let polarity = 'N';
128
+ if (rawValue > reverseOffset) {
129
+ index = rawValue - reverseOffset;
130
+ polarity = 'R';
131
+ }
132
+ else {
133
+ index = rawValue - 1;
134
+ }
135
+ const code = kind.values[index] ?? 0;
136
+ return { mode: 'dcs', code, polarity };
137
+ }
105
138
  default: {
106
139
  const exhaustive = kind;
107
140
  return exhaustive;
@@ -172,6 +205,43 @@ function encodeRawValue(kind, value, byteLength) {
172
205
  }
173
206
  return bytes;
174
207
  }
208
+ case 'lbcd': {
209
+ if (value === null || value === undefined) {
210
+ return Array.from({ length: byteLength }, () => 0xff);
211
+ }
212
+ const scale = kind.scale ?? 10;
213
+ const hz = typeof value === 'number' ? value : 0;
214
+ let word = Number.parseInt(Math.round(hz / scale).toString(10), 16);
215
+ for (let index = 0; index < byteLength; index += 1) {
216
+ bytes[index] = word & 0xff;
217
+ word >>= 8;
218
+ }
219
+ return bytes;
220
+ }
221
+ case 'tone': {
222
+ const ctcssMin = kind.ctcssMin ?? 0x0258;
223
+ const reverseOffset = kind.reverseOffset ?? 0x69;
224
+ let rawValue = 0;
225
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
226
+ const tone = value;
227
+ if (tone.mode === 'ctcss' && typeof tone.value === 'number') {
228
+ rawValue = tone.value;
229
+ }
230
+ else if (tone.mode === 'dcs' && typeof tone.code === 'number') {
231
+ const index = kind.values.indexOf(tone.code);
232
+ rawValue = index >= 0 ? index + 1 : 0;
233
+ if (tone.polarity === 'R') {
234
+ rawValue += reverseOffset;
235
+ }
236
+ }
237
+ }
238
+ if (rawValue >= ctcssMin || rawValue === 0) {
239
+ // CTCSS or none: write LE u16
240
+ }
241
+ bytes[0] = rawValue & 0xff;
242
+ bytes[1] = (rawValue >> 8) & 0xff;
243
+ return bytes;
244
+ }
175
245
  default: {
176
246
  const exhaustive = kind;
177
247
  return exhaustive;
@@ -188,9 +258,13 @@ function fieldByteLength(field) {
188
258
  if (field.value?.kind === 'ascii' ||
189
259
  field.value?.kind === 'digits' ||
190
260
  field.value?.kind === 'dtmf' ||
191
- field.value?.kind === 'bbcd') {
261
+ field.value?.kind === 'bbcd' ||
262
+ field.value?.kind === 'lbcd') {
192
263
  return field.value.length;
193
264
  }
265
+ if (field.value?.kind === 'tone') {
266
+ return 2;
267
+ }
194
268
  return 1;
195
269
  }
196
270
  /**
@@ -289,9 +363,8 @@ function decodeStruct(struct, contents, memoryConfig, instanceIndex) {
289
363
  bytes.push(readByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents)));
290
364
  }
291
365
  if (field.type === 'u16') {
292
- const raw = bytes[0] | (bytes[1] << 8);
293
366
  if (!field.reserved) {
294
- result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, raw);
367
+ result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, bytes);
295
368
  }
296
369
  }
297
370
  else if (!field.reserved) {
@@ -334,8 +407,14 @@ function encodeStruct(struct, values, contents, memoryConfig, instanceIndex) {
334
407
  }
335
408
  const settingValue = values[field.id];
336
409
  if (field.type === 'u16') {
337
- const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, 1);
338
- const value = encoded[0] & 0xffff;
410
+ const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, 2);
411
+ let value = 0;
412
+ if (field.value?.kind === 'tone') {
413
+ value = (encoded[0] & 0xff) | ((encoded[1] & 0xff) << 8);
414
+ }
415
+ else {
416
+ value = (encoded[0] ?? 0) & 0xffff;
417
+ }
339
418
  writeByte(contents, bufferOffsetFor(radioAddress, memoryConfig, contents), value & 0xff);
340
419
  writeByte(contents, bufferOffsetFor(radioAddress + 1, memoryConfig, contents), (value >> 8) & 0xff);
341
420
  }
@@ -351,6 +430,21 @@ function encodeStruct(struct, values, contents, memoryConfig, instanceIndex) {
351
430
  bitCursor.nextRadioAddress();
352
431
  }
353
432
  }
433
+ function isStructInstanceEmpty(struct, contents, memoryConfig, instanceIndex) {
434
+ if (!struct.emptyWhen) {
435
+ return false;
436
+ }
437
+ const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);
438
+ const firstByte = readByte(contents, bufferOffsetFor(base, memoryConfig, contents));
439
+ return firstByte === struct.emptyWhen.equals;
440
+ }
441
+ function clearStructInstance(struct, contents, memoryConfig, instanceIndex) {
442
+ const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);
443
+ const length = struct.stride ?? 16;
444
+ for (let index = 0; index < length; index += 1) {
445
+ writeByte(contents, bufferOffsetFor(base + index, memoryConfig, contents), 0xff);
446
+ }
447
+ }
354
448
  /**
355
449
  * Decode radio-wide settings from a memory image using a JSON memory map.
356
450
  */
@@ -361,7 +455,12 @@ export function decodeMemoryMap(memoryMap, contents, memoryConfig) {
361
455
  if (count > 1) {
362
456
  const items = [];
363
457
  for (let index = 0; index < count; index += 1) {
364
- items.push(decodeStruct(struct, contents, memoryConfig, index));
458
+ if (isStructInstanceEmpty(struct, contents, memoryConfig, index)) {
459
+ items.push(null);
460
+ }
461
+ else {
462
+ items.push(decodeStruct(struct, contents, memoryConfig, index));
463
+ }
365
464
  }
366
465
  settings[struct.id] = items;
367
466
  }
@@ -383,6 +482,12 @@ export function encodeMemoryMap(memoryMap, settings, contents, memoryConfig) {
383
482
  const items = Array.isArray(value) ? value : [];
384
483
  for (let index = 0; index < count; index += 1) {
385
484
  const item = items[index];
485
+ if (item === null || item === undefined) {
486
+ if (struct.clearEmpty) {
487
+ clearStructInstance(struct, contents, memoryConfig, index);
488
+ }
489
+ continue;
490
+ }
386
491
  const record = item && typeof item === 'object' && !Array.isArray(item) ? item : {};
387
492
  encodeStruct(struct, record, contents, memoryConfig, index);
388
493
  }
@@ -1 +1 @@
1
- {"version":3,"file":"memory-map-codec.js","sourceRoot":"","sources":["../../src/memory/memory-map-codec.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RadioMemoryConfig,\n RadioMemoryMap,\n RadioMemoryMapField,\n RadioMemoryMapStruct,\n RadioMemoryMapValueKind,\n RadioSettings,\n RadioSettingValue,\n} from '@springfield/ham-radio-api';\n\nconst DEFAULT_DTMF_CHARSET = '0123456789 *#ABCD';\n\n/**\n * Parse a seek address from a number or hex/decimal string.\n */\nexport function parseSeekAddress(seek: number | string): number {\n if (typeof seek === 'number') {\n return seek;\n }\n\n const trimmed = seek.trim();\n\n if (/^0x/i.test(trimmed)) {\n return Number.parseInt(trimmed, 16);\n }\n\n return Number.parseInt(trimmed, 10);\n}\n\n/**\n * Map a radio EEPROM address to an offset in the memory buffer.\n *\n * Packed buffers (driver read) concatenate segments in config order.\n * Sparse buffers that cover the highest segment end address use absolute offsets.\n */\nexport function radioAddressToBufferOffset(\n radioAddress: number,\n memoryConfig: RadioMemoryConfig,\n bufferLength: number,\n): number {\n const segments = Object.values(memoryConfig.segments);\n const maxEndAddress = Math.max(...segments.map((segment) => segment.endAddress));\n\n if (bufferLength >= maxEndAddress + 1) {\n return radioAddress;\n }\n\n let offset = 0;\n\n for (const segment of segments) {\n if (radioAddress >= segment.startAddress && radioAddress <= segment.endAddress) {\n return offset + (radioAddress - segment.startAddress);\n }\n\n offset += segment.endAddress - segment.startAddress + 1;\n }\n\n throw new Error(`Radio address 0x${radioAddress.toString(16)} is not in any memory segment`);\n}\n\nfunction bufferOffsetFor(radioAddress: number, memoryConfig: RadioMemoryConfig, contents: Uint8Array): number {\n return radioAddressToBufferOffset(radioAddress, memoryConfig, contents.length);\n}\n\nfunction readByte(contents: Uint8Array, offset: number): number {\n if (offset < 0 || offset >= contents.length) {\n throw new RangeError(`Memory-map read out of bounds at offset ${offset}`);\n }\n\n return contents[offset];\n}\n\nfunction writeByte(contents: Uint8Array, offset: number, value: number): void {\n if (offset < 0 || offset >= contents.length) {\n throw new RangeError(`Memory-map write out of bounds at offset ${offset}`);\n }\n\n contents[offset] = value & 0xff;\n}\n\nfunction decodeRawValue(kind: RadioMemoryMapValueKind | undefined, raw: number | number[]): RadioSettingValue {\n if (!kind) {\n return typeof raw === 'number' ? raw : raw;\n }\n\n switch (kind.kind) {\n case 'integer':\n return typeof raw === 'number' ? raw : raw[0];\n case 'boolean':\n return (typeof raw === 'number' ? raw : raw[0]) !== 0;\n case 'enum': {\n const index = typeof raw === 'number' ? raw : raw[0];\n return kind.values[index] ?? kind.values[0] ?? '';\n }\n case 'ascii': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let text = '';\n\n for (const byte of bytes) {\n if (byte === 0xff || byte === 0x00) {\n break;\n }\n\n text += String.fromCodePoint(byte);\n }\n\n return text.trimEnd();\n }\n case 'digits': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let value = 0;\n\n for (const digit of bytes) {\n value = value * 10 + (digit & 0x0f);\n }\n\n return value * (kind.scale ?? 1);\n }\n case 'dtmf': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n const charset = kind.charset ?? DEFAULT_DTMF_CHARSET;\n let text = '';\n\n for (const byte of bytes) {\n if (byte >= 0x1f) {\n break;\n }\n\n if (byte < charset.length) {\n text += charset[byte];\n }\n }\n\n return text;\n }\n case 'bbcd': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let value = 0;\n\n for (const byte of bytes) {\n value = value * 100 + ((byte >> 4) & 0x0f) * 10 + (byte & 0x0f);\n }\n\n return value;\n }\n default: {\n const exhaustive: never = kind;\n return exhaustive;\n }\n }\n}\n\nfunction encodeRawValue(kind: RadioMemoryMapValueKind | undefined, value: RadioSettingValue, byteLength: number): number[] {\n const bytes = Array.from({ length: byteLength }, () => 0xff);\n\n if (!kind) {\n if (typeof value === 'number') {\n bytes[0] = value & 0xff;\n }\n\n return bytes;\n }\n\n switch (kind.kind) {\n case 'integer': {\n bytes[0] = typeof value === 'number' ? value & 0xff : 0;\n return bytes;\n }\n case 'boolean': {\n bytes[0] = value ? 1 : 0;\n return bytes;\n }\n case 'enum': {\n const index = typeof value === 'string' ? kind.values.indexOf(value) : -1;\n bytes[0] = index >= 0 ? index : 0;\n return bytes;\n }\n case 'ascii': {\n const text = typeof value === 'string' ? value : '';\n\n for (let index = 0; index < byteLength; index += 1) {\n bytes[index] = index < text.length ? (text.codePointAt(index) ?? 0xff) : 0xff;\n }\n\n return bytes;\n }\n case 'digits': {\n const scale = kind.scale ?? 1;\n let numeric = typeof value === 'number' ? Math.round(value / scale) : 0;\n\n for (let index = byteLength - 1; index >= 0; index -= 1) {\n bytes[index] = numeric % 10;\n numeric = Math.floor(numeric / 10);\n }\n\n return bytes;\n }\n case 'dtmf': {\n const text = typeof value === 'string' ? value : '';\n const charset = kind.charset ?? DEFAULT_DTMF_CHARSET;\n\n for (let index = 0; index < byteLength; index += 1) {\n if (index < text.length) {\n const charIndex = charset.indexOf(text[index]);\n bytes[index] = charIndex >= 0 ? charIndex : 0xff;\n } else {\n bytes[index] = 0xff;\n }\n }\n\n return bytes;\n }\n case 'bbcd': {\n let numeric = typeof value === 'number' ? value : 0;\n const digits: number[] = [];\n\n for (let index = 0; index < byteLength * 2; index += 1) {\n digits.unshift(numeric % 10);\n numeric = Math.floor(numeric / 10);\n }\n\n for (let index = 0; index < byteLength; index += 1) {\n bytes[index] = ((digits[index * 2] & 0x0f) << 4) | (digits[index * 2 + 1] & 0x0f);\n }\n\n return bytes;\n }\n default: {\n const exhaustive: never = kind;\n return exhaustive;\n }\n }\n}\n\nfunction fieldByteLength(field: RadioMemoryMapField): number {\n if (field.type === 'u16') {\n return 2;\n }\n\n if (field.type === 'bits') {\n return 0;\n }\n\n if (\n field.value?.kind === 'ascii' ||\n field.value?.kind === 'digits' ||\n field.value?.kind === 'dtmf' ||\n field.value?.kind === 'bbcd'\n ) {\n return field.value.length;\n }\n\n return 1;\n}\n\n/**\n * Chirp-style bitfields: declaration order is MSB → LSB within each byte.\n */\nclass BitfieldCursor {\n private radioAddress: number;\n private bitIndex = -1;\n private currentByte = 0;\n private dirty = false;\n\n constructor(\n private readonly contents: Uint8Array,\n private readonly memoryConfig: RadioMemoryConfig,\n startRadioAddress: number,\n ) {\n this.radioAddress = startRadioAddress;\n }\n\n private load(): void {\n if (this.bitIndex >= 0) {\n return;\n }\n\n this.currentByte = readByte(this.contents, bufferOffsetFor(this.radioAddress, this.memoryConfig, this.contents));\n this.bitIndex = 7;\n this.dirty = false;\n }\n\n private flushByte(): void {\n if (this.dirty) {\n writeByte(this.contents, bufferOffsetFor(this.radioAddress, this.memoryConfig, this.contents), this.currentByte);\n this.dirty = false;\n }\n }\n\n read(width: number): number {\n let result = 0;\n\n for (let bit = 0; bit < width; bit += 1) {\n this.load();\n result = (result << 1) | ((this.currentByte >> this.bitIndex) & 1);\n this.bitIndex -= 1;\n\n if (this.bitIndex < 0) {\n this.radioAddress += 1;\n }\n }\n\n return result;\n }\n\n write(width: number, value: number): void {\n for (let bit = width - 1; bit >= 0; bit -= 1) {\n this.load();\n const mask = 1 << this.bitIndex;\n\n if ((value >> bit) & 1) {\n this.currentByte |= mask;\n } else {\n this.currentByte &= ~mask;\n }\n\n this.dirty = true;\n this.bitIndex -= 1;\n\n if (this.bitIndex < 0) {\n this.flushByte();\n this.radioAddress += 1;\n }\n }\n }\n\n /**\n * Absolute radio address of the next unused byte after this bitfield group.\n */\n nextRadioAddress(): number {\n this.flushByte();\n\n if (this.bitIndex >= 0) {\n return this.radioAddress + 1;\n }\n\n return this.radioAddress;\n }\n}\n\nfunction decodeStruct(\n struct: RadioMemoryMapStruct,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n instanceIndex: number,\n): Record<string, RadioSettingValue> {\n const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);\n const result: Record<string, RadioSettingValue> = {};\n let radioAddress = base;\n let bitCursor: BitfieldCursor | undefined;\n\n for (const field of struct.fields) {\n if (field.type === 'bits') {\n if (!bitCursor) {\n bitCursor = new BitfieldCursor(contents, memoryConfig, radioAddress);\n }\n\n const raw = bitCursor.read(field.width ?? 1);\n\n if (!field.reserved) {\n result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, raw);\n }\n\n continue;\n }\n\n if (bitCursor) {\n radioAddress = bitCursor.nextRadioAddress();\n bitCursor = undefined;\n }\n\n const length = fieldByteLength(field);\n const bytes: number[] = [];\n\n for (let index = 0; index < length; index += 1) {\n bytes.push(readByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents)));\n }\n\n if (field.type === 'u16') {\n const raw = bytes[0] | (bytes[1] << 8);\n\n if (!field.reserved) {\n result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, raw);\n }\n } else if (!field.reserved) {\n result[field.id] = decodeRawValue(field.value, length === 1 ? bytes[0] : bytes);\n }\n\n radioAddress += length;\n }\n\n return result;\n}\n\nfunction encodeStruct(\n struct: RadioMemoryMapStruct,\n values: Record<string, RadioSettingValue>,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n instanceIndex: number,\n): void {\n const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);\n let radioAddress = base;\n let bitCursor: BitfieldCursor | undefined;\n let bitStartAddress = base;\n\n for (const field of struct.fields) {\n if (field.type === 'bits') {\n if (!bitCursor) {\n bitStartAddress = radioAddress;\n bitCursor = new BitfieldCursor(contents, memoryConfig, bitStartAddress);\n }\n\n const width = field.width ?? 1;\n\n if (field.reserved) {\n // Advance by reading existing bits (preserves memory)\n bitCursor.read(width);\n } else {\n const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, values[field.id] ?? 0, 1);\n bitCursor.write(width, encoded[0]);\n }\n\n continue;\n }\n\n if (bitCursor) {\n radioAddress = bitCursor.nextRadioAddress();\n bitCursor = undefined;\n }\n\n const length = fieldByteLength(field);\n\n if (field.reserved) {\n radioAddress += length;\n continue;\n }\n\n const settingValue = values[field.id];\n\n if (field.type === 'u16') {\n const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, 1);\n const value = encoded[0] & 0xffff;\n writeByte(contents, bufferOffsetFor(radioAddress, memoryConfig, contents), value & 0xff);\n writeByte(contents, bufferOffsetFor(radioAddress + 1, memoryConfig, contents), (value >> 8) & 0xff);\n } else {\n const encoded = encodeRawValue(field.value, settingValue ?? (field.value?.kind === 'ascii' ? '' : 0), length);\n\n for (let index = 0; index < length; index += 1) {\n writeByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents), encoded[index]);\n }\n }\n\n radioAddress += length;\n }\n\n if (bitCursor) {\n bitCursor.nextRadioAddress();\n }\n}\n\n/**\n * Decode radio-wide settings from a memory image using a JSON memory map.\n */\nexport function decodeMemoryMap(\n memoryMap: RadioMemoryMap,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): RadioSettings {\n const settings: RadioSettings = {};\n\n for (const struct of memoryMap.structs) {\n const count = struct.count ?? 1;\n\n if (count > 1) {\n const items: RadioSettingValue[] = [];\n\n for (let index = 0; index < count; index += 1) {\n items.push(decodeStruct(struct, contents, memoryConfig, index));\n }\n\n settings[struct.id] = items;\n } else {\n settings[struct.id] = decodeStruct(struct, contents, memoryConfig, 0);\n }\n }\n\n return settings;\n}\n\n/**\n * Encode radio-wide settings into an existing memory image in place.\n * Returns the same buffer for convenience.\n */\nexport function encodeMemoryMap(\n memoryMap: RadioMemoryMap,\n settings: RadioSettings,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): Uint8Array {\n for (const struct of memoryMap.structs) {\n const count = struct.count ?? 1;\n const value = settings[struct.id];\n\n if (count > 1) {\n const items = Array.isArray(value) ? value : [];\n\n for (let index = 0; index < count; index += 1) {\n const item = items[index];\n const record =\n item && typeof item === 'object' && !Array.isArray(item) ? (item as Record<string, RadioSettingValue>) : {};\n encodeStruct(struct, record, contents, memoryConfig, index);\n }\n } else {\n const record =\n value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, RadioSettingValue>) : {};\n encodeStruct(struct, record, contents, memoryConfig, 0);\n }\n }\n\n return contents;\n}\n"]}
1
+ {"version":3,"file":"memory-map-codec.js","sourceRoot":"","sources":["../../src/memory/memory-map-codec.ts"],"names":[],"mappings":"AAUA,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAqB;IACpD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CACxC,YAAoB,EACpB,YAA+B,EAC/B,YAAoB;IAEpB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjF,IAAI,YAAY,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACtC,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,YAAY,IAAI,OAAO,CAAC,YAAY,IAAI,YAAY,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAC/E,OAAO,MAAM,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,IAAI,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,mBAAmB,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC;AAC/F,CAAC;AAED,SAAS,eAAe,CAAC,YAAoB,EAAE,YAA+B,EAAE,QAAoB;IAClG,OAAO,0BAA0B,CAAC,YAAY,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,QAAQ,CAAC,QAAoB,EAAE,MAAc;IACpD,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC5C,MAAM,IAAI,UAAU,CAAC,2CAA2C,MAAM,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,SAAS,CAAC,QAAoB,EAAE,MAAc,EAAE,KAAa;IACpE,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC5C,MAAM,IAAI,UAAU,CAAC,4CAA4C,MAAM,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC;AAClC,CAAC;AAED,SAAS,cAAc,CAAC,IAAyC,EAAE,GAAsB;IACvF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7C,CAAC;IAED,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChD,KAAK,SAAS;YACZ,OAAO,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACrD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpD,CAAC;QACD,KAAK,OAAO,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACpD,IAAI,IAAI,GAAG,EAAE,CAAC;YAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBACnC,MAAM;gBACR,CAAC;gBAED,IAAI,IAAI,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;YAED,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;QACD,KAAK,QAAQ,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACpD,IAAI,KAAK,GAAG,CAAC,CAAC;YAEd,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC1B,KAAK,GAAG,KAAK,GAAG,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;YACtC,CAAC;YAED,OAAO,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;QACnC,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,oBAAoB,CAAC;YACrD,IAAI,IAAI,GAAG,EAAE,CAAC;YAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;oBACjB,MAAM;gBACR,CAAC;gBAED,IAAI,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;oBAC1B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;YAED,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACpD,IAAI,KAAK,GAAG,CAAC,CAAC;YAEd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,KAAK,GAAG,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;YAClE,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACpD,IAAI,IAAI,GAAG,CAAC,CAAC;YAEb,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACrD,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;YACtC,CAAC;YAED,IAAI,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACnE,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC;YACzC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;YAEjD,IAAI,QAAQ,KAAK,CAAC,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;gBAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC1B,CAAC;YAED,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;gBACzB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;YAC5C,CAAC;YAED,IAAI,KAAK,GAAG,QAAQ,CAAC;YACrB,IAAI,QAAQ,GAAc,GAAG,CAAC;YAE9B,IAAI,QAAQ,GAAG,aAAa,EAAE,CAAC;gBAC7B,KAAK,GAAG,QAAQ,GAAG,aAAa,CAAC;gBACjC,QAAQ,GAAG,GAAG,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACrC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QACzC,CAAC;QACD,SAAS,CAAC;YACR,MAAM,UAAU,GAAU,IAAI,CAAC;YAC/B,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,IAAyC,EAAE,KAAwB,EAAE,UAAkB;IAC7G,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAE7D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC;QAC1B,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,SAAS,EAAE,CAAC;YACf,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACxD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,SAAS,EAAE,CAAC;YACf,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACzB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1E,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAEpD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACnD,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAChF,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,QAAQ,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;YAC9B,IAAI,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAExE,KAAK,IAAI,KAAK,GAAG,UAAU,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACxD,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,CAAC;gBAC5B,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;YACrC,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,oBAAoB,CAAC;YAErD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACnD,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;oBACxB,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;oBAC/C,KAAK,CAAC,KAAK,CAAC,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;gBACnD,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;gBACtB,CAAC;YACH,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,IAAI,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACpD,MAAM,MAAM,GAAa,EAAE,CAAC;YAE5B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACvD,MAAM,CAAC,OAAO,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;gBAC7B,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;YACrC,CAAC;YAED,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACnD,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;YACpF,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;YACxD,CAAC;YAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/B,MAAM,EAAE,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACjD,IAAI,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAEpE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBACnD,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;gBAC3B,IAAI,KAAK,CAAC,CAAC;YACb,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QACD,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC;YACzC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;YACjD,IAAI,QAAQ,GAAG,CAAC,CAAC;YAEjB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChE,MAAM,IAAI,GAAG,KAA4E,CAAC;gBAE1F,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC5D,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC;gBACxB,CAAC;qBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAChE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC7C,QAAQ,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAEtC,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG,EAAE,CAAC;wBAC1B,QAAQ,IAAI,aAAa,CAAC;oBAC5B,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBAC3C,8BAA8B;YAChC,CAAC;YAED,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC;YAC3B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;YAClC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,CAAC;YACR,MAAM,UAAU,GAAU,IAAI,CAAC;YAC/B,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,KAA0B;IACjD,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IACE,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO;QAC7B,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,QAAQ;QAC9B,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM;QAC5B,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM;QAC5B,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM,EAC5B,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;GAEG;AACH,MAAM,cAAc;IAOC,QAAQ;IACR,YAAY;IAPvB,YAAY,CAAS;IACrB,QAAQ,GAAG,CAAC,CAAC,CAAC;IACd,WAAW,GAAG,CAAC,CAAC;IAChB,KAAK,GAAG,KAAK,CAAC;IAEtB,YACmB,QAAoB,EACpB,YAA+B,EAChD,iBAAyB;wBAFR,QAAQ;4BACR,YAAY;QAG7B,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC;IACxC,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACjH,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEO,SAAS;QACf,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACjH,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,KAAa;QAChB,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;YACnE,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;YAEnB,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,KAAa,EAAE,KAAa;QAChC,KAAK,IAAI,GAAG,GAAG,KAAK,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YAC7C,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC;YAEhC,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC;YAC5B,CAAC;YAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;YAEnB,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjB,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,gBAAgB;QACd,IAAI,CAAC,SAAS,EAAE,CAAC;QAEjB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;QAC/B,CAAC;QAED,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,SAAS,YAAY,CACnB,MAA4B,EAC5B,QAAoB,EACpB,YAA+B,EAC/B,aAAqB;IAErB,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,aAAa,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClF,MAAM,MAAM,GAAsC,EAAE,CAAC;IACrD,IAAI,YAAY,GAAG,IAAI,CAAC;IACxB,IAAI,SAAqC,CAAC;IAE1C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS,GAAG,IAAI,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC;YAE7C,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACpB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,CAAC,CAAC;YAC7E,CAAC;YAED,SAAS;QACX,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,YAAY,GAAG,SAAS,CAAC,gBAAgB,EAAE,CAAC;YAC5C,SAAS,GAAG,SAAS,CAAC;QACxB,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YAC/C,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,GAAG,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QAChG,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACpB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClF,CAAC;QAED,YAAY,IAAI,MAAM,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,YAAY,CACnB,MAA4B,EAC5B,MAAyC,EACzC,QAAoB,EACpB,YAA+B,EAC/B,aAAqB;IAErB,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,aAAa,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClF,IAAI,YAAY,GAAG,IAAI,CAAC;IACxB,IAAI,SAAqC,CAAC;IAC1C,IAAI,eAAe,GAAG,IAAI,CAAC;IAE3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,eAAe,GAAG,YAAY,CAAC;gBAC/B,SAAS,GAAG,IAAI,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;YAC1E,CAAC;YAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YAE/B,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,sDAAsD;gBACtD,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC7F,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACrC,CAAC;YAED,SAAS;QACX,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,YAAY,GAAG,SAAS,CAAC,gBAAgB,EAAE,CAAC;YAC5C,SAAS,GAAG,SAAS,CAAC;QACxB,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QAEtC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,YAAY,IAAI,MAAM,CAAC;YACvB,SAAS;QACX,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAEtC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,YAAY,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACzF,IAAI,KAAK,GAAG,CAAC,CAAC;YAEd,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;gBACjC,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACN,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC;YACrC,CAAC;YAED,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;YACzF,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,GAAG,CAAC,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACtG,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YAE9G,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC/C,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC,YAAY,GAAG,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACrG,CAAC;QACH,CAAC;QAED,YAAY,IAAI,MAAM,CAAC;IACzB,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,SAAS,CAAC,gBAAgB,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC5B,MAA4B,EAC5B,QAAoB,EACpB,YAA+B,EAC/B,aAAqB;IAErB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,aAAa,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC;IACpF,OAAO,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;AAC/C,CAAC;AAED,SAAS,mBAAmB,CAC1B,MAA4B,EAC5B,QAAoB,EACpB,YAA+B,EAC/B,aAAqB;IAErB,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,aAAa,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAClF,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IAEnC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC/C,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAC,IAAI,GAAG,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAyB,EACzB,QAAoB,EACpB,YAA+B;IAE/B,MAAM,QAAQ,GAAkB,EAAE,CAAC;IAEnC,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;QAEhC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,KAAK,GAAwB,EAAE,CAAC;YAEtC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC9C,IAAI,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;oBACjE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;gBAClE,CAAC;YACH,CAAC;YAED,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAyB,EACzB,QAAuB,EACvB,QAAoB,EACpB,YAA+B;IAE/B,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAElC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAEhD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;gBAE1B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACxC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;wBACtB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;oBAC7D,CAAC;oBAED,SAAS;gBACX,CAAC;gBAED,MAAM,MAAM,GACV,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,IAA0C,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9G,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GACV,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAA2C,CAAC,CAAC,CAAC,EAAE,CAAC;YAClH,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","sourcesContent":["import type {\n RadioMemoryConfig,\n RadioMemoryMap,\n RadioMemoryMapField,\n RadioMemoryMapStruct,\n RadioMemoryMapValueKind,\n RadioSettings,\n RadioSettingValue,\n} from '@springfield/ham-radio-api';\n\nconst DEFAULT_DTMF_CHARSET = '0123456789 *#ABCD';\n\n/**\n * Parse a seek address from a number or hex/decimal string.\n */\nexport function parseSeekAddress(seek: number | string): number {\n if (typeof seek === 'number') {\n return seek;\n }\n\n const trimmed = seek.trim();\n\n if (/^0x/i.test(trimmed)) {\n return Number.parseInt(trimmed, 16);\n }\n\n return Number.parseInt(trimmed, 10);\n}\n\n/**\n * Map a radio EEPROM address to an offset in the memory buffer.\n *\n * Packed buffers (driver read) concatenate segments in config order.\n * Sparse buffers that cover the highest segment end address use absolute offsets.\n */\nexport function radioAddressToBufferOffset(\n radioAddress: number,\n memoryConfig: RadioMemoryConfig,\n bufferLength: number,\n): number {\n const segments = Object.values(memoryConfig.segments);\n const maxEndAddress = Math.max(...segments.map((segment) => segment.endAddress));\n\n if (bufferLength >= maxEndAddress + 1) {\n return radioAddress;\n }\n\n let offset = 0;\n\n for (const segment of segments) {\n if (radioAddress >= segment.startAddress && radioAddress <= segment.endAddress) {\n return offset + (radioAddress - segment.startAddress);\n }\n\n offset += segment.endAddress - segment.startAddress + 1;\n }\n\n throw new Error(`Radio address 0x${radioAddress.toString(16)} is not in any memory segment`);\n}\n\nfunction bufferOffsetFor(radioAddress: number, memoryConfig: RadioMemoryConfig, contents: Uint8Array): number {\n return radioAddressToBufferOffset(radioAddress, memoryConfig, contents.length);\n}\n\nfunction readByte(contents: Uint8Array, offset: number): number {\n if (offset < 0 || offset >= contents.length) {\n throw new RangeError(`Memory-map read out of bounds at offset ${offset}`);\n }\n\n return contents[offset];\n}\n\nfunction writeByte(contents: Uint8Array, offset: number, value: number): void {\n if (offset < 0 || offset >= contents.length) {\n throw new RangeError(`Memory-map write out of bounds at offset ${offset}`);\n }\n\n contents[offset] = value & 0xff;\n}\n\nfunction decodeRawValue(kind: RadioMemoryMapValueKind | undefined, raw: number | number[]): RadioSettingValue {\n if (!kind) {\n return typeof raw === 'number' ? raw : raw;\n }\n\n switch (kind.kind) {\n case 'integer':\n return typeof raw === 'number' ? raw : raw[0];\n case 'boolean':\n return (typeof raw === 'number' ? raw : raw[0]) !== 0;\n case 'enum': {\n const index = typeof raw === 'number' ? raw : raw[0];\n return kind.values[index] ?? kind.values[0] ?? '';\n }\n case 'ascii': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let text = '';\n\n for (const byte of bytes) {\n if (byte === 0xff || byte === 0x00) {\n break;\n }\n\n text += String.fromCodePoint(byte);\n }\n\n return text.trimEnd();\n }\n case 'digits': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let value = 0;\n\n for (const digit of bytes) {\n value = value * 10 + (digit & 0x0f);\n }\n\n return value * (kind.scale ?? 1);\n }\n case 'dtmf': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n const charset = kind.charset ?? DEFAULT_DTMF_CHARSET;\n let text = '';\n\n for (const byte of bytes) {\n if (byte >= 0x1f) {\n break;\n }\n\n if (byte < charset.length) {\n text += charset[byte];\n }\n }\n\n return text;\n }\n case 'bbcd': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let value = 0;\n\n for (const byte of bytes) {\n value = value * 100 + ((byte >> 4) & 0x0f) * 10 + (byte & 0x0f);\n }\n\n return value;\n }\n case 'lbcd': {\n const bytes = typeof raw === 'number' ? [raw] : raw;\n let word = 0;\n\n for (let index = 0; index < bytes.length; index += 1) {\n word |= bytes[index] << (8 * index);\n }\n\n if (word === 0xff_ff_ff_ff || bytes.every((byte) => byte === 0xff)) {\n return null;\n }\n\n return Number.parseInt(word.toString(16), 10) * (kind.scale ?? 10);\n }\n case 'tone': {\n const rawValue = typeof raw === 'number' ? raw : raw[0] | (raw[1] << 8);\n const ctcssMin = kind.ctcssMin ?? 0x0258;\n const reverseOffset = kind.reverseOffset ?? 0x69;\n\n if (rawValue === 0 || rawValue === 0xffff) {\n return { mode: 'none' };\n }\n\n if (rawValue >= ctcssMin) {\n return { mode: 'ctcss', value: rawValue };\n }\n\n let index = rawValue;\n let polarity: 'N' | 'R' = 'N';\n\n if (rawValue > reverseOffset) {\n index = rawValue - reverseOffset;\n polarity = 'R';\n } else {\n index = rawValue - 1;\n }\n\n const code = kind.values[index] ?? 0;\n return { mode: 'dcs', code, polarity };\n }\n default: {\n const exhaustive: never = kind;\n return exhaustive;\n }\n }\n}\n\nfunction encodeRawValue(kind: RadioMemoryMapValueKind | undefined, value: RadioSettingValue, byteLength: number): number[] {\n const bytes = Array.from({ length: byteLength }, () => 0xff);\n\n if (!kind) {\n if (typeof value === 'number') {\n bytes[0] = value & 0xff;\n }\n\n return bytes;\n }\n\n switch (kind.kind) {\n case 'integer': {\n bytes[0] = typeof value === 'number' ? value & 0xff : 0;\n return bytes;\n }\n case 'boolean': {\n bytes[0] = value ? 1 : 0;\n return bytes;\n }\n case 'enum': {\n const index = typeof value === 'string' ? kind.values.indexOf(value) : -1;\n bytes[0] = index >= 0 ? index : 0;\n return bytes;\n }\n case 'ascii': {\n const text = typeof value === 'string' ? value : '';\n\n for (let index = 0; index < byteLength; index += 1) {\n bytes[index] = index < text.length ? (text.codePointAt(index) ?? 0xff) : 0xff;\n }\n\n return bytes;\n }\n case 'digits': {\n const scale = kind.scale ?? 1;\n let numeric = typeof value === 'number' ? Math.round(value / scale) : 0;\n\n for (let index = byteLength - 1; index >= 0; index -= 1) {\n bytes[index] = numeric % 10;\n numeric = Math.floor(numeric / 10);\n }\n\n return bytes;\n }\n case 'dtmf': {\n const text = typeof value === 'string' ? value : '';\n const charset = kind.charset ?? DEFAULT_DTMF_CHARSET;\n\n for (let index = 0; index < byteLength; index += 1) {\n if (index < text.length) {\n const charIndex = charset.indexOf(text[index]);\n bytes[index] = charIndex >= 0 ? charIndex : 0xff;\n } else {\n bytes[index] = 0xff;\n }\n }\n\n return bytes;\n }\n case 'bbcd': {\n let numeric = typeof value === 'number' ? value : 0;\n const digits: number[] = [];\n\n for (let index = 0; index < byteLength * 2; index += 1) {\n digits.unshift(numeric % 10);\n numeric = Math.floor(numeric / 10);\n }\n\n for (let index = 0; index < byteLength; index += 1) {\n bytes[index] = ((digits[index * 2] & 0x0f) << 4) | (digits[index * 2 + 1] & 0x0f);\n }\n\n return bytes;\n }\n case 'lbcd': {\n if (value === null || value === undefined) {\n return Array.from({ length: byteLength }, () => 0xff);\n }\n\n const scale = kind.scale ?? 10;\n const hz = typeof value === 'number' ? value : 0;\n let word = Number.parseInt(Math.round(hz / scale).toString(10), 16);\n\n for (let index = 0; index < byteLength; index += 1) {\n bytes[index] = word & 0xff;\n word >>= 8;\n }\n\n return bytes;\n }\n case 'tone': {\n const ctcssMin = kind.ctcssMin ?? 0x0258;\n const reverseOffset = kind.reverseOffset ?? 0x69;\n let rawValue = 0;\n\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const tone = value as { mode?: string; value?: number; code?: number; polarity?: string };\n\n if (tone.mode === 'ctcss' && typeof tone.value === 'number') {\n rawValue = tone.value;\n } else if (tone.mode === 'dcs' && typeof tone.code === 'number') {\n const index = kind.values.indexOf(tone.code);\n rawValue = index >= 0 ? index + 1 : 0;\n\n if (tone.polarity === 'R') {\n rawValue += reverseOffset;\n }\n }\n }\n\n if (rawValue >= ctcssMin || rawValue === 0) {\n // CTCSS or none: write LE u16\n }\n\n bytes[0] = rawValue & 0xff;\n bytes[1] = (rawValue >> 8) & 0xff;\n return bytes;\n }\n default: {\n const exhaustive: never = kind;\n return exhaustive;\n }\n }\n}\n\nfunction fieldByteLength(field: RadioMemoryMapField): number {\n if (field.type === 'u16') {\n return 2;\n }\n\n if (field.type === 'bits') {\n return 0;\n }\n\n if (\n field.value?.kind === 'ascii' ||\n field.value?.kind === 'digits' ||\n field.value?.kind === 'dtmf' ||\n field.value?.kind === 'bbcd' ||\n field.value?.kind === 'lbcd'\n ) {\n return field.value.length;\n }\n\n if (field.value?.kind === 'tone') {\n return 2;\n }\n\n return 1;\n}\n\n/**\n * Chirp-style bitfields: declaration order is MSB → LSB within each byte.\n */\nclass BitfieldCursor {\n private radioAddress: number;\n private bitIndex = -1;\n private currentByte = 0;\n private dirty = false;\n\n constructor(\n private readonly contents: Uint8Array,\n private readonly memoryConfig: RadioMemoryConfig,\n startRadioAddress: number,\n ) {\n this.radioAddress = startRadioAddress;\n }\n\n private load(): void {\n if (this.bitIndex >= 0) {\n return;\n }\n\n this.currentByte = readByte(this.contents, bufferOffsetFor(this.radioAddress, this.memoryConfig, this.contents));\n this.bitIndex = 7;\n this.dirty = false;\n }\n\n private flushByte(): void {\n if (this.dirty) {\n writeByte(this.contents, bufferOffsetFor(this.radioAddress, this.memoryConfig, this.contents), this.currentByte);\n this.dirty = false;\n }\n }\n\n read(width: number): number {\n let result = 0;\n\n for (let bit = 0; bit < width; bit += 1) {\n this.load();\n result = (result << 1) | ((this.currentByte >> this.bitIndex) & 1);\n this.bitIndex -= 1;\n\n if (this.bitIndex < 0) {\n this.radioAddress += 1;\n }\n }\n\n return result;\n }\n\n write(width: number, value: number): void {\n for (let bit = width - 1; bit >= 0; bit -= 1) {\n this.load();\n const mask = 1 << this.bitIndex;\n\n if ((value >> bit) & 1) {\n this.currentByte |= mask;\n } else {\n this.currentByte &= ~mask;\n }\n\n this.dirty = true;\n this.bitIndex -= 1;\n\n if (this.bitIndex < 0) {\n this.flushByte();\n this.radioAddress += 1;\n }\n }\n }\n\n /**\n * Absolute radio address of the next unused byte after this bitfield group.\n */\n nextRadioAddress(): number {\n this.flushByte();\n\n if (this.bitIndex >= 0) {\n return this.radioAddress + 1;\n }\n\n return this.radioAddress;\n }\n}\n\nfunction decodeStruct(\n struct: RadioMemoryMapStruct,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n instanceIndex: number,\n): Record<string, RadioSettingValue> {\n const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);\n const result: Record<string, RadioSettingValue> = {};\n let radioAddress = base;\n let bitCursor: BitfieldCursor | undefined;\n\n for (const field of struct.fields) {\n if (field.type === 'bits') {\n if (!bitCursor) {\n bitCursor = new BitfieldCursor(contents, memoryConfig, radioAddress);\n }\n\n const raw = bitCursor.read(field.width ?? 1);\n\n if (!field.reserved) {\n result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, raw);\n }\n\n continue;\n }\n\n if (bitCursor) {\n radioAddress = bitCursor.nextRadioAddress();\n bitCursor = undefined;\n }\n\n const length = fieldByteLength(field);\n const bytes: number[] = [];\n\n for (let index = 0; index < length; index += 1) {\n bytes.push(readByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents)));\n }\n\n if (field.type === 'u16') {\n if (!field.reserved) {\n result[field.id] = decodeRawValue(field.value ?? { kind: 'integer' }, bytes);\n }\n } else if (!field.reserved) {\n result[field.id] = decodeRawValue(field.value, length === 1 ? bytes[0] : bytes);\n }\n\n radioAddress += length;\n }\n\n return result;\n}\n\nfunction encodeStruct(\n struct: RadioMemoryMapStruct,\n values: Record<string, RadioSettingValue>,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n instanceIndex: number,\n): void {\n const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);\n let radioAddress = base;\n let bitCursor: BitfieldCursor | undefined;\n let bitStartAddress = base;\n\n for (const field of struct.fields) {\n if (field.type === 'bits') {\n if (!bitCursor) {\n bitStartAddress = radioAddress;\n bitCursor = new BitfieldCursor(contents, memoryConfig, bitStartAddress);\n }\n\n const width = field.width ?? 1;\n\n if (field.reserved) {\n // Advance by reading existing bits (preserves memory)\n bitCursor.read(width);\n } else {\n const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, values[field.id] ?? 0, 1);\n bitCursor.write(width, encoded[0]);\n }\n\n continue;\n }\n\n if (bitCursor) {\n radioAddress = bitCursor.nextRadioAddress();\n bitCursor = undefined;\n }\n\n const length = fieldByteLength(field);\n\n if (field.reserved) {\n radioAddress += length;\n continue;\n }\n\n const settingValue = values[field.id];\n\n if (field.type === 'u16') {\n const encoded = encodeRawValue(field.value ?? { kind: 'integer' }, settingValue ?? 0, 2);\n let value = 0;\n\n if (field.value?.kind === 'tone') {\n value = (encoded[0] & 0xff) | ((encoded[1] & 0xff) << 8);\n } else {\n value = (encoded[0] ?? 0) & 0xffff;\n }\n\n writeByte(contents, bufferOffsetFor(radioAddress, memoryConfig, contents), value & 0xff);\n writeByte(contents, bufferOffsetFor(radioAddress + 1, memoryConfig, contents), (value >> 8) & 0xff);\n } else {\n const encoded = encodeRawValue(field.value, settingValue ?? (field.value?.kind === 'ascii' ? '' : 0), length);\n\n for (let index = 0; index < length; index += 1) {\n writeByte(contents, bufferOffsetFor(radioAddress + index, memoryConfig, contents), encoded[index]);\n }\n }\n\n radioAddress += length;\n }\n\n if (bitCursor) {\n bitCursor.nextRadioAddress();\n }\n}\n\nfunction isStructInstanceEmpty(\n struct: RadioMemoryMapStruct,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n instanceIndex: number,\n): boolean {\n if (!struct.emptyWhen) {\n return false;\n }\n\n const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);\n const firstByte = readByte(contents, bufferOffsetFor(base, memoryConfig, contents));\n return firstByte === struct.emptyWhen.equals;\n}\n\nfunction clearStructInstance(\n struct: RadioMemoryMapStruct,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n instanceIndex: number,\n): void {\n const base = parseSeekAddress(struct.seek) + instanceIndex * (struct.stride ?? 0);\n const length = struct.stride ?? 16;\n\n for (let index = 0; index < length; index += 1) {\n writeByte(contents, bufferOffsetFor(base + index, memoryConfig, contents), 0xff);\n }\n}\n\n/**\n * Decode radio-wide settings from a memory image using a JSON memory map.\n */\nexport function decodeMemoryMap(\n memoryMap: RadioMemoryMap,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): RadioSettings {\n const settings: RadioSettings = {};\n\n for (const struct of memoryMap.structs) {\n const count = struct.count ?? 1;\n\n if (count > 1) {\n const items: RadioSettingValue[] = [];\n\n for (let index = 0; index < count; index += 1) {\n if (isStructInstanceEmpty(struct, contents, memoryConfig, index)) {\n items.push(null);\n } else {\n items.push(decodeStruct(struct, contents, memoryConfig, index));\n }\n }\n\n settings[struct.id] = items;\n } else {\n settings[struct.id] = decodeStruct(struct, contents, memoryConfig, 0);\n }\n }\n\n return settings;\n}\n\n/**\n * Encode radio-wide settings into an existing memory image in place.\n * Returns the same buffer for convenience.\n */\nexport function encodeMemoryMap(\n memoryMap: RadioMemoryMap,\n settings: RadioSettings,\n contents: Uint8Array,\n memoryConfig: RadioMemoryConfig,\n): Uint8Array {\n for (const struct of memoryMap.structs) {\n const count = struct.count ?? 1;\n const value = settings[struct.id];\n\n if (count > 1) {\n const items = Array.isArray(value) ? value : [];\n\n for (let index = 0; index < count; index += 1) {\n const item = items[index];\n\n if (item === null || item === undefined) {\n if (struct.clearEmpty) {\n clearStructInstance(struct, contents, memoryConfig, index);\n }\n\n continue;\n }\n\n const record =\n item && typeof item === 'object' && !Array.isArray(item) ? (item as Record<string, RadioSettingValue>) : {};\n encodeStruct(struct, record, contents, memoryConfig, index);\n }\n } else {\n const record =\n value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, RadioSettingValue>) : {};\n encodeStruct(struct, record, contents, memoryConfig, 0);\n }\n }\n\n return contents;\n}\n"]}