@springfield/ham-radio-utils 4.5.0 → 4.6.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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/memory-map-radio-codec.d.ts +34 -0
- package/dist/memory/memory-map-radio-codec.d.ts.map +1 -0
- package/dist/memory/memory-map-radio-codec.js +72 -0
- package/dist/memory/memory-map-radio-codec.js.map +1 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/memory/memory-map-radio-codec.ts +96 -0
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './memory/segmented-memory.js';
|
|
|
2
2
|
export * from './memory/memory-map-codec.js';
|
|
3
3
|
export * from './memory/memory-map-channels.js';
|
|
4
4
|
export * from './memory/memory-map-ui.js';
|
|
5
|
+
export * from './memory/memory-map-radio-codec.js';
|
|
5
6
|
export * from './test-utils/radio-channel-factory.js';
|
|
6
7
|
export * from './test-utils/radio-programmed-channel-factory.js';
|
|
7
8
|
export * from './utils/band-plan.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;
|
|
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;AAC1C,cAAc,oCAAoC,CAAC;AAEnD,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
|
@@ -2,6 +2,7 @@ export * from './memory/segmented-memory.js';
|
|
|
2
2
|
export * from './memory/memory-map-codec.js';
|
|
3
3
|
export * from './memory/memory-map-channels.js';
|
|
4
4
|
export * from './memory/memory-map-ui.js';
|
|
5
|
+
export * from './memory/memory-map-radio-codec.js';
|
|
5
6
|
export * from './test-utils/radio-channel-factory.js';
|
|
6
7
|
export * from './test-utils/radio-programmed-channel-factory.js';
|
|
7
8
|
export * from './utils/band-plan.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,iCAAiC,CAAC;AAChD,cAAc,2BAA2B,CAAC;
|
|
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;AAC1C,cAAc,oCAAoC,CAAC;AAEnD,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';\nexport * from './memory/memory-map-radio-codec.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,34 @@
|
|
|
1
|
+
import type { RadioCodec, RadioMemory, RadioMemoryConfig, RadioMemoryMap, RadioModelId, RadioProgram } from '@springfield/ham-radio-api';
|
|
2
|
+
import type { ILogLayer } from 'loglayer';
|
|
3
|
+
/**
|
|
4
|
+
* Compute the sparse image size that covers every configured memory segment.
|
|
5
|
+
*/
|
|
6
|
+
export declare function memoryImageSize(memoryConfig: RadioMemoryConfig): number;
|
|
7
|
+
/**
|
|
8
|
+
* Create a Chirp-like empty EEPROM image filled with 0xFF.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createEmptyMemoryImage(totalSize: number): Uint8Array;
|
|
11
|
+
export interface MemoryMapRadioCodecOptions {
|
|
12
|
+
radioModel: RadioModelId;
|
|
13
|
+
memoryMap: RadioMemoryMap;
|
|
14
|
+
memoryConfig: RadioMemoryConfig;
|
|
15
|
+
logger?: ILogLayer;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generic RadioCodec driven entirely by a JSON memory map + memoryConfig.
|
|
19
|
+
* Radio modules that ship DSL JSON do not need TypeScript codecs.
|
|
20
|
+
*/
|
|
21
|
+
export declare class MemoryMapRadioCodec implements RadioCodec {
|
|
22
|
+
private readonly radioModel;
|
|
23
|
+
private readonly memoryMap;
|
|
24
|
+
private readonly memoryConfig;
|
|
25
|
+
private readonly logger;
|
|
26
|
+
constructor(options: MemoryMapRadioCodecOptions);
|
|
27
|
+
decode(memory: RadioMemory): RadioProgram;
|
|
28
|
+
encode(program: RadioProgram, memory: RadioMemory): RadioMemory;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a memory-map codec from hydrated radio config fields.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createMemoryMapCodec(options: MemoryMapRadioCodecOptions): RadioCodec;
|
|
34
|
+
//# sourceMappingURL=memory-map-radio-codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-map-radio-codec.d.ts","sourceRoot":"","sources":["../../src/memory/memory-map-radio-codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,YAAY,EACb,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,iBAAiB,GAAG,MAAM,CAGvE;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAIpE;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,YAAY,CAAC;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,YAAY,EAAE,iBAAiB,CAAC;IAChC,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IACpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAE/C,YAAY,OAAO,EAAE,0BAA0B,EAK9C;IAED,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAOxC;IAED,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,CA0B9D;CACF;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,0BAA0B,GAAG,UAAU,CAEpF"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { decodeRadioProgram, encodeRadioProgram } from './memory-map-channels.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compute the sparse image size that covers every configured memory segment.
|
|
4
|
+
*/
|
|
5
|
+
export function memoryImageSize(memoryConfig) {
|
|
6
|
+
const ends = Object.values(memoryConfig.segments).map((segment) => segment.endAddress);
|
|
7
|
+
return Math.max(...ends) + 1;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create a Chirp-like empty EEPROM image filled with 0xFF.
|
|
11
|
+
*/
|
|
12
|
+
export function createEmptyMemoryImage(totalSize) {
|
|
13
|
+
const memory = new Uint8Array(totalSize);
|
|
14
|
+
memory.fill(0xff);
|
|
15
|
+
return memory;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generic RadioCodec driven entirely by a JSON memory map + memoryConfig.
|
|
19
|
+
* Radio modules that ship DSL JSON do not need TypeScript codecs.
|
|
20
|
+
*/
|
|
21
|
+
export class MemoryMapRadioCodec {
|
|
22
|
+
radioModel;
|
|
23
|
+
memoryMap;
|
|
24
|
+
memoryConfig;
|
|
25
|
+
logger;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.radioModel = options.radioModel;
|
|
28
|
+
this.memoryMap = options.memoryMap;
|
|
29
|
+
this.memoryConfig = options.memoryConfig;
|
|
30
|
+
this.logger = options.logger;
|
|
31
|
+
}
|
|
32
|
+
decode(memory) {
|
|
33
|
+
try {
|
|
34
|
+
return decodeRadioProgram(this.memoryMap, memory.contents, this.memoryConfig);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.logger?.withError(error).warn('Failed to decode radio program from memory map');
|
|
38
|
+
return { channels: [], settings: {} };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
encode(program, memory) {
|
|
42
|
+
const totalSize = memoryImageSize(this.memoryConfig);
|
|
43
|
+
const contents = memory.contents.length > 0 ? new Uint8Array(memory.contents) : createEmptyMemoryImage(totalSize);
|
|
44
|
+
if (contents.length < totalSize) {
|
|
45
|
+
const expanded = createEmptyMemoryImage(totalSize);
|
|
46
|
+
expanded.set(contents);
|
|
47
|
+
try {
|
|
48
|
+
encodeRadioProgram(this.memoryMap, program, expanded, this.memoryConfig);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.logger?.withError(error).warn('Failed to encode radio program into memory map');
|
|
52
|
+
}
|
|
53
|
+
this.logger?.debug(`Memory size: ${expanded.length} bytes`);
|
|
54
|
+
return { contents: expanded, radioModel: this.radioModel };
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
encodeRadioProgram(this.memoryMap, program, contents, this.memoryConfig);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logger?.withError(error).warn('Failed to encode radio program into memory map');
|
|
61
|
+
}
|
|
62
|
+
this.logger?.debug(`Memory size: ${contents.length} bytes`);
|
|
63
|
+
return { contents, radioModel: this.radioModel };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a memory-map codec from hydrated radio config fields.
|
|
68
|
+
*/
|
|
69
|
+
export function createMemoryMapCodec(options) {
|
|
70
|
+
return new MemoryMapRadioCodec(options);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=memory-map-radio-codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-map-radio-codec.js","sourceRoot":"","sources":["../../src/memory/memory-map-radio-codec.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n RadioCodec,\n RadioMemory,\n RadioMemoryConfig,\n RadioMemoryMap,\n RadioModelId,\n RadioProgram,\n} from '@springfield/ham-radio-api';\nimport type { ILogLayer } from 'loglayer';\nimport { decodeRadioProgram, encodeRadioProgram } from './memory-map-channels.js';\n\n/**\n * Compute the sparse image size that covers every configured memory segment.\n */\nexport function memoryImageSize(memoryConfig: RadioMemoryConfig): number {\n const ends = Object.values(memoryConfig.segments).map((segment) => segment.endAddress);\n return Math.max(...ends) + 1;\n}\n\n/**\n * Create a Chirp-like empty EEPROM image filled with 0xFF.\n */\nexport function createEmptyMemoryImage(totalSize: number): Uint8Array {\n const memory = new Uint8Array(totalSize);\n memory.fill(0xff);\n return memory;\n}\n\nexport interface MemoryMapRadioCodecOptions {\n radioModel: RadioModelId;\n memoryMap: RadioMemoryMap;\n memoryConfig: RadioMemoryConfig;\n logger?: ILogLayer;\n}\n\n/**\n * Generic RadioCodec driven entirely by a JSON memory map + memoryConfig.\n * Radio modules that ship DSL JSON do not need TypeScript codecs.\n */\nexport class MemoryMapRadioCodec implements RadioCodec {\n private readonly radioModel: RadioModelId;\n private readonly memoryMap: RadioMemoryMap;\n private readonly memoryConfig: RadioMemoryConfig;\n private readonly logger: ILogLayer | undefined;\n\n constructor(options: MemoryMapRadioCodecOptions) {\n this.radioModel = options.radioModel;\n this.memoryMap = options.memoryMap;\n this.memoryConfig = options.memoryConfig;\n this.logger = options.logger;\n }\n\n decode(memory: RadioMemory): RadioProgram {\n try {\n return decodeRadioProgram(this.memoryMap, memory.contents, this.memoryConfig);\n } catch (error) {\n this.logger?.withError(error).warn('Failed to decode radio program from memory map');\n return { channels: [], settings: {} };\n }\n }\n\n encode(program: RadioProgram, memory: RadioMemory): RadioMemory {\n const totalSize = memoryImageSize(this.memoryConfig);\n const contents =\n memory.contents.length > 0 ? new Uint8Array(memory.contents) : createEmptyMemoryImage(totalSize);\n\n if (contents.length < totalSize) {\n const expanded = createEmptyMemoryImage(totalSize);\n expanded.set(contents);\n try {\n encodeRadioProgram(this.memoryMap, program, expanded, this.memoryConfig);\n } catch (error) {\n this.logger?.withError(error).warn('Failed to encode radio program into memory map');\n }\n\n this.logger?.debug(`Memory size: ${expanded.length} bytes`);\n return { contents: expanded, radioModel: this.radioModel };\n }\n\n try {\n encodeRadioProgram(this.memoryMap, program, contents, this.memoryConfig);\n } catch (error) {\n this.logger?.withError(error).warn('Failed to encode radio program into memory map');\n }\n\n this.logger?.debug(`Memory size: ${contents.length} bytes`);\n return { contents, radioModel: this.radioModel };\n }\n}\n\n/**\n * Create a memory-map codec from hydrated radio config fields.\n */\nexport function createMemoryMapCodec(options: MemoryMapRadioCodecOptions): RadioCodec {\n return new MemoryMapRadioCodec(options);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springfield/ham-radio-utils",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.0",
|
|
4
4
|
"author": "Bryan Hunt",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"test:unit": "tsx --test test/unit/**/*.test.ts"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@springfield/ham-radio-api": "^17.
|
|
33
|
+
"@springfield/ham-radio-api": "^17.3.0",
|
|
34
34
|
"ajv": "^8.20.0",
|
|
35
35
|
"ajv-formats": "^3.0.1",
|
|
36
36
|
"fishery": "^2.4.0",
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './memory/segmented-memory.js';
|
|
|
2
2
|
export * from './memory/memory-map-codec.js';
|
|
3
3
|
export * from './memory/memory-map-channels.js';
|
|
4
4
|
export * from './memory/memory-map-ui.js';
|
|
5
|
+
export * from './memory/memory-map-radio-codec.js';
|
|
5
6
|
|
|
6
7
|
export * from './test-utils/radio-channel-factory.js';
|
|
7
8
|
export * from './test-utils/radio-programmed-channel-factory.js';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RadioCodec,
|
|
3
|
+
RadioMemory,
|
|
4
|
+
RadioMemoryConfig,
|
|
5
|
+
RadioMemoryMap,
|
|
6
|
+
RadioModelId,
|
|
7
|
+
RadioProgram,
|
|
8
|
+
} from '@springfield/ham-radio-api';
|
|
9
|
+
import type { ILogLayer } from 'loglayer';
|
|
10
|
+
import { decodeRadioProgram, encodeRadioProgram } from './memory-map-channels.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Compute the sparse image size that covers every configured memory segment.
|
|
14
|
+
*/
|
|
15
|
+
export function memoryImageSize(memoryConfig: RadioMemoryConfig): number {
|
|
16
|
+
const ends = Object.values(memoryConfig.segments).map((segment) => segment.endAddress);
|
|
17
|
+
return Math.max(...ends) + 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a Chirp-like empty EEPROM image filled with 0xFF.
|
|
22
|
+
*/
|
|
23
|
+
export function createEmptyMemoryImage(totalSize: number): Uint8Array {
|
|
24
|
+
const memory = new Uint8Array(totalSize);
|
|
25
|
+
memory.fill(0xff);
|
|
26
|
+
return memory;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MemoryMapRadioCodecOptions {
|
|
30
|
+
radioModel: RadioModelId;
|
|
31
|
+
memoryMap: RadioMemoryMap;
|
|
32
|
+
memoryConfig: RadioMemoryConfig;
|
|
33
|
+
logger?: ILogLayer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generic RadioCodec driven entirely by a JSON memory map + memoryConfig.
|
|
38
|
+
* Radio modules that ship DSL JSON do not need TypeScript codecs.
|
|
39
|
+
*/
|
|
40
|
+
export class MemoryMapRadioCodec implements RadioCodec {
|
|
41
|
+
private readonly radioModel: RadioModelId;
|
|
42
|
+
private readonly memoryMap: RadioMemoryMap;
|
|
43
|
+
private readonly memoryConfig: RadioMemoryConfig;
|
|
44
|
+
private readonly logger: ILogLayer | undefined;
|
|
45
|
+
|
|
46
|
+
constructor(options: MemoryMapRadioCodecOptions) {
|
|
47
|
+
this.radioModel = options.radioModel;
|
|
48
|
+
this.memoryMap = options.memoryMap;
|
|
49
|
+
this.memoryConfig = options.memoryConfig;
|
|
50
|
+
this.logger = options.logger;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
decode(memory: RadioMemory): RadioProgram {
|
|
54
|
+
try {
|
|
55
|
+
return decodeRadioProgram(this.memoryMap, memory.contents, this.memoryConfig);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
this.logger?.withError(error).warn('Failed to decode radio program from memory map');
|
|
58
|
+
return { channels: [], settings: {} };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
encode(program: RadioProgram, memory: RadioMemory): RadioMemory {
|
|
63
|
+
const totalSize = memoryImageSize(this.memoryConfig);
|
|
64
|
+
const contents =
|
|
65
|
+
memory.contents.length > 0 ? new Uint8Array(memory.contents) : createEmptyMemoryImage(totalSize);
|
|
66
|
+
|
|
67
|
+
if (contents.length < totalSize) {
|
|
68
|
+
const expanded = createEmptyMemoryImage(totalSize);
|
|
69
|
+
expanded.set(contents);
|
|
70
|
+
try {
|
|
71
|
+
encodeRadioProgram(this.memoryMap, program, expanded, this.memoryConfig);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
this.logger?.withError(error).warn('Failed to encode radio program into memory map');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.logger?.debug(`Memory size: ${expanded.length} bytes`);
|
|
77
|
+
return { contents: expanded, radioModel: this.radioModel };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
encodeRadioProgram(this.memoryMap, program, contents, this.memoryConfig);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.logger?.withError(error).warn('Failed to encode radio program into memory map');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.logger?.debug(`Memory size: ${contents.length} bytes`);
|
|
87
|
+
return { contents, radioModel: this.radioModel };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a memory-map codec from hydrated radio config fields.
|
|
93
|
+
*/
|
|
94
|
+
export function createMemoryMapCodec(options: MemoryMapRadioCodecOptions): RadioCodec {
|
|
95
|
+
return new MemoryMapRadioCodec(options);
|
|
96
|
+
}
|