@springfield/ham-radio-utils 1.0.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/CHANGELOG.md +212 -0
- package/README.md +92 -0
- package/dist/db/bands.json +149 -0
- package/dist/db/license-classes.json +22 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/segmented-memory.d.ts +12 -0
- package/dist/memory/segmented-memory.d.ts.map +1 -0
- package/dist/memory/segmented-memory.js +39 -0
- package/dist/memory/segmented-memory.js.map +1 -0
- package/dist/test-utils/radio-channel-factory.d.ts +5 -0
- package/dist/test-utils/radio-channel-factory.d.ts.map +1 -0
- package/dist/test-utils/radio-channel-factory.js +16 -0
- package/dist/test-utils/radio-channel-factory.js.map +1 -0
- package/dist/test-utils/radio-programmed-channel-factory.d.ts +4 -0
- package/dist/test-utils/radio-programmed-channel-factory.d.ts.map +1 -0
- package/dist/test-utils/radio-programmed-channel-factory.js +7 -0
- package/dist/test-utils/radio-programmed-channel-factory.js.map +1 -0
- package/dist/utils/band-plan.d.ts +24 -0
- package/dist/utils/band-plan.d.ts.map +1 -0
- package/dist/utils/band-plan.js +27 -0
- package/dist/utils/band-plan.js.map +1 -0
- package/dist/utils/bcd-to-number.d.ts +2 -0
- package/dist/utils/bcd-to-number.d.ts.map +1 -0
- package/dist/utils/bcd-to-number.js +9 -0
- package/dist/utils/bcd-to-number.js.map +1 -0
- package/dist/utils/frequency-display.d.ts +3 -0
- package/dist/utils/frequency-display.d.ts.map +1 -0
- package/dist/utils/frequency-display.js +5 -0
- package/dist/utils/frequency-display.js.map +1 -0
- package/dist/utils/memory-channel-utils.d.ts +2 -0
- package/dist/utils/memory-channel-utils.d.ts.map +1 -0
- package/dist/utils/memory-channel-utils.js +6 -0
- package/dist/utils/memory-channel-utils.js.map +1 -0
- package/dist/utils/memory-segment-utils.d.ts +3 -0
- package/dist/utils/memory-segment-utils.d.ts.map +1 -0
- package/dist/utils/memory-segment-utils.js +6 -0
- package/dist/utils/memory-segment-utils.js.map +1 -0
- package/dist/utils/number-to-bcd.d.ts +2 -0
- package/dist/utils/number-to-bcd.d.ts.map +1 -0
- package/dist/utils/number-to-bcd.js +12 -0
- package/dist/utils/number-to-bcd.js.map +1 -0
- package/dist/utils/to-hex-words.d.ts +2 -0
- package/dist/utils/to-hex-words.d.ts.map +1 -0
- package/dist/utils/to-hex-words.js +12 -0
- package/dist/utils/to-hex-words.js.map +1 -0
- package/package.json +68 -0
- package/src/db/bands.json +149 -0
- package/src/db/license-classes.json +22 -0
- package/src/index.ts +12 -0
- package/src/memory/segmented-memory.ts +52 -0
- package/src/test-utils/radio-channel-factory.ts +17 -0
- package/src/test-utils/radio-programmed-channel-factory.ts +8 -0
- package/src/utils/band-plan.ts +36 -0
- package/src/utils/bcd-to-number.ts +10 -0
- package/src/utils/frequency-display.ts +6 -0
- package/src/utils/memory-channel-utils.ts +6 -0
- package/src/utils/memory-segment-utils.ts +7 -0
- package/src/utils/number-to-bcd.ts +15 -0
- package/src/utils/to-hex-words.ts +14 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SpectrumBand } from '@springfield/ham-radio-api';
|
|
2
|
+
interface LicenseClass {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class BandPlan {
|
|
7
|
+
private bands;
|
|
8
|
+
private licenseClasses;
|
|
9
|
+
constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Finds the spectrum band that contains the given frequency
|
|
12
|
+
* @param frequency The frequency in Hz to find the band for
|
|
13
|
+
* @returns The spectrum band containing the frequency, or undefined if no band contains it
|
|
14
|
+
*/
|
|
15
|
+
findBandByFrequency(frequency: number): SpectrumBand | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Finds a license class by its ID
|
|
18
|
+
* @param id The ID of the license class to find
|
|
19
|
+
* @returns The license class with the given ID, or undefined if not found
|
|
20
|
+
*/
|
|
21
|
+
findPrivilegeById(id: string): LicenseClass | undefined;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=band-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"band-plan.d.ts","sourceRoot":"","sources":["../../src/utils/band-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAI1D,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAA4B;;IAOlD;;;;OAIG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIhE;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import bandsData from '../db/bands.json' with { type: 'json' };
|
|
2
|
+
import licenseClassesData from '../db/license-classes.json' with { type: 'json' };
|
|
3
|
+
export class BandPlan {
|
|
4
|
+
bands;
|
|
5
|
+
licenseClasses;
|
|
6
|
+
constructor() {
|
|
7
|
+
this.bands = bandsData;
|
|
8
|
+
this.licenseClasses = new Map(licenseClassesData.map((licenseClass) => [licenseClass.id, licenseClass]));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Finds the spectrum band that contains the given frequency
|
|
12
|
+
* @param frequency The frequency in Hz to find the band for
|
|
13
|
+
* @returns The spectrum band containing the frequency, or undefined if no band contains it
|
|
14
|
+
*/
|
|
15
|
+
findBandByFrequency(frequency) {
|
|
16
|
+
return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Finds a license class by its ID
|
|
20
|
+
* @param id The ID of the license class to find
|
|
21
|
+
* @returns The license class with the given ID, or undefined if not found
|
|
22
|
+
*/
|
|
23
|
+
findPrivilegeById(id) {
|
|
24
|
+
return this.licenseClasses.get(id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=band-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"band-plan.js","sourceRoot":"","sources":["../../src/utils/band-plan.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,MAAM,kBAAkB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAC/D,OAAO,kBAAkB,MAAM,4BAA4B,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAOlF,MAAM,OAAO,QAAQ;IACX,KAAK,CAAiB;IACtB,cAAc,CAA4B;IAElD;QACE,IAAI,CAAC,KAAK,GAAG,SAA2B,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,CAAE,kBAAqC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/H,CAAC;IAED;;;;OAIG;IACH,mBAAmB,CAAC,SAAiB;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI,CAAC,cAAc,IAAI,SAAS,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC;IACzG,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,EAAU;QAC1B,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;CACF","sourcesContent":["import { SpectrumBand } from '@springfield/ham-radio-api';\nimport bandsData from '../db/bands.json' with { type: 'json' };\nimport licenseClassesData from '../db/license-classes.json' with { type: 'json' };\n\ninterface LicenseClass {\n id: string;\n name: string;\n}\n\nexport class BandPlan {\n private bands: SpectrumBand[];\n private licenseClasses: Map<string, LicenseClass>;\n\n constructor() {\n this.bands = bandsData as SpectrumBand[];\n this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));\n }\n\n /**\n * Finds the spectrum band that contains the given frequency\n * @param frequency The frequency in Hz to find the band for\n * @returns The spectrum band containing the frequency, or undefined if no band contains it\n */\n findBandByFrequency(frequency: number): SpectrumBand | undefined {\n return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);\n }\n\n /**\n * Finds a license class by its ID\n * @param id The ID of the license class to find\n * @returns The license class with the given ID, or undefined if not found\n */\n findPrivilegeById(id: string): LicenseClass | undefined {\n return this.licenseClasses.get(id);\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bcd-to-number.d.ts","sourceRoot":"","sources":["../../src/utils/bcd-to-number.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAStF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function bcdToNumber(data, offset, length) {
|
|
2
|
+
let bcd = 0;
|
|
3
|
+
const size = length ?? data.length;
|
|
4
|
+
for (let i = offset ?? 0; i < size; i++) {
|
|
5
|
+
bcd |= data[i] << (8 * i);
|
|
6
|
+
}
|
|
7
|
+
return parseInt(bcd.toString(16), 10) * 10;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=bcd-to-number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bcd-to-number.js","sourceRoot":"","sources":["../../src/utils/bcd-to-number.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,IAAgB,EAAE,MAAe,EAAE,MAAe;IAC5E,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,MAAM,IAAI,GAAG,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC;IAEnC,KAAK,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC;AAC7C,CAAC","sourcesContent":["export function bcdToNumber(data: Uint8Array, offset?: number, length?: number): number {\n let bcd = 0;\n const size = length ?? data.length;\n\n for (let i = offset ?? 0; i < size; i++) {\n bcd |= data[i] << (8 * i);\n }\n\n return parseInt(bcd.toString(16), 10) * 10;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frequency-display.d.ts","sourceRoot":"","sources":["../../src/utils/frequency-display.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAErE,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAGjF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frequency-display.js","sourceRoot":"","sources":["../../src/utils/frequency-display.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,SAAoB,EAAE,IAAkB;IACvE,MAAM,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAAC,8BAA8B,CAAC;IACzE,OAAO,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;AACvE,CAAC","sourcesContent":["import { Frequency, SpectrumBand } from '@springfield/ham-radio-api';\n\nexport function frequencyDisplay(frequency: Frequency, band: SpectrumBand): string {\n const frequencyDisplay = frequency / band.frequencyDisplayBaseMultiplier;\n return frequencyDisplay.toFixed(band.frequencyDisplayNumberDecimals);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-channel-utils.d.ts","sourceRoot":"","sources":["../../src/utils/memory-channel-utils.ts"],"names":[],"mappings":"AAGA,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAE7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-channel-utils.js","sourceRoot":"","sources":["../../src/utils/memory-channel-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,MAAM,UAAU,aAAa,CAAC,aAAqB,EAAE,IAAgB;IACnE,OAAO,OAAO,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9D,CAAC","sourcesContent":["import { sprintf } from 'sprintf-js';\nimport { toHexWords } from './to-hex-words.js';\n\nexport function formatChannel(channelNumber: number, data: Uint8Array): string {\n return sprintf('%2d : %s', channelNumber, toHexWords(data));\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-segment-utils.d.ts","sourceRoot":"","sources":["../../src/utils/memory-segment-utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,wBAAgB,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAEjE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-segment-utils.js","sourceRoot":"","sources":["../../src/utils/memory-segment-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/C,MAAM,UAAU,aAAa,CAAC,OAA2B;IACvD,OAAO,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import { sprintf } from 'sprintf-js';\nimport { toHexWords } from './to-hex-words.js';\nimport { RadioMemorySegment } from '@springfield/ham-radio-api';\n\nexport function formatSegment(segment: RadioMemorySegment): string {\n return sprintf('%04X : %s', segment.startAddress, toHexWords(segment.data));\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-to-bcd.d.ts","sourceRoot":"","sources":["../../src/utils/number-to-bcd.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAcrD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function numberToBcd(value) {
|
|
2
|
+
const digits = value.toString(10);
|
|
3
|
+
const numberOfBytes = Math.ceil(digits.length / 2.0);
|
|
4
|
+
const data = new Uint8Array(numberOfBytes);
|
|
5
|
+
let bcd = parseInt(digits, 16);
|
|
6
|
+
for (let i = 0; i < numberOfBytes; i++) {
|
|
7
|
+
data[i] = bcd & 0xff;
|
|
8
|
+
bcd >>= 8;
|
|
9
|
+
}
|
|
10
|
+
return data;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=number-to-bcd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-to-bcd.js","sourceRoot":"","sources":["../../src/utils/number-to-bcd.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,aAAa,CAAC,CAAC;IAE3C,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC;QACrB,GAAG,KAAK,CAAC,CAAC;IACZ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["export function numberToBcd(value: number): Uint8Array {\n const digits = value.toString(10);\n const numberOfBytes = Math.ceil(digits.length / 2.0);\n\n const data = new Uint8Array(numberOfBytes);\n\n let bcd = parseInt(digits, 16);\n\n for (let i = 0; i < numberOfBytes; i++) {\n data[i] = bcd & 0xff;\n bcd >>= 8;\n }\n\n return data;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-hex-words.d.ts","sourceRoot":"","sources":["../../src/utils/to-hex-words.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAanD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function toHexWords(data) {
|
|
2
|
+
const hexData = Buffer.from(data).toString('hex');
|
|
3
|
+
let formattedData = '';
|
|
4
|
+
for (let i = 0; i < hexData.length; i += 4) {
|
|
5
|
+
if (i != 0) {
|
|
6
|
+
formattedData += ' ';
|
|
7
|
+
}
|
|
8
|
+
formattedData += hexData.substring(i, i + 4);
|
|
9
|
+
}
|
|
10
|
+
return formattedData;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=to-hex-words.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-hex-words.js","sourceRoot":"","sources":["../../src/utils/to-hex-words.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,IAAI,aAAa,GAAG,EAAE,CAAC;IAEvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACX,aAAa,IAAI,GAAG,CAAC;QACvB,CAAC;QAED,aAAa,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC","sourcesContent":["export function toHexWords(data: Uint8Array): string {\n const hexData = Buffer.from(data).toString('hex');\n let formattedData = '';\n\n for (let i = 0; i < hexData.length; i += 4) {\n if (i != 0) {\n formattedData += ' ';\n }\n\n formattedData += hexData.substring(i, i + 4);\n }\n\n return formattedData;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@springfield/ham-radio-utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"author": "Bryan Hunt",
|
|
5
|
+
"packageManager": "yarn@4.9.1",
|
|
6
|
+
"readme": "README.md",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"src/",
|
|
12
|
+
"dist/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && cp src/db/* dist/db/",
|
|
16
|
+
"commitlint": "commitlint --edit",
|
|
17
|
+
"release": "semantic-release",
|
|
18
|
+
"prepare": "husky"
|
|
19
|
+
},
|
|
20
|
+
"release": {
|
|
21
|
+
"branches": [
|
|
22
|
+
"main",
|
|
23
|
+
{
|
|
24
|
+
"name": "beta",
|
|
25
|
+
"prerelease": true
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"plugins": [
|
|
29
|
+
[
|
|
30
|
+
"@semantic-release/commit-analyzer",
|
|
31
|
+
{
|
|
32
|
+
"preset": "conventionalcommits"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"@semantic-release/release-notes-generator",
|
|
37
|
+
{
|
|
38
|
+
"preset": "conventionalcommits"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"@semantic-release/changelog",
|
|
42
|
+
"semantic-release-yarn",
|
|
43
|
+
"@semantic-release/gitlab",
|
|
44
|
+
"@semantic-release/git"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@springfield/ham-radio-api": "^12.0.1",
|
|
52
|
+
"fishery": "^2.3.1",
|
|
53
|
+
"loglayer": "^6.4.2",
|
|
54
|
+
"sprintf-js": "^1.1.3"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@commitlint/cli": "19.8.1",
|
|
58
|
+
"@commitlint/config-conventional": "19.8.1",
|
|
59
|
+
"@semantic-release/changelog": "6.0.3",
|
|
60
|
+
"@semantic-release/git": "10.0.1",
|
|
61
|
+
"@semantic-release/gitlab": "13.2.5",
|
|
62
|
+
"@types/sprintf-js": "1.1.4",
|
|
63
|
+
"husky": "9.1.7",
|
|
64
|
+
"semantic-release": "23.1.1",
|
|
65
|
+
"semantic-release-yarn": "3.0.2",
|
|
66
|
+
"typescript": "5.8.3"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "d1a5c7e8-3f9b-4a6c-8d2e-1b3f7a9c5e8d",
|
|
4
|
+
"name": "160 Meter",
|
|
5
|
+
"wavelength": 160,
|
|
6
|
+
"lowerFrequency": 1800000000,
|
|
7
|
+
"upperFrequency": 2000000000,
|
|
8
|
+
"frequencyDisplayBaseMultiplier": 1000000,
|
|
9
|
+
"frequencyDisplayNumberDecimals": 3,
|
|
10
|
+
"privileges": [
|
|
11
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
12
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
13
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "e3b7a9c5-d1a5-4f8d-2e1b-c7e83f9b6a4c",
|
|
18
|
+
"name": "80 Meter",
|
|
19
|
+
"wavelength": 80,
|
|
20
|
+
"lowerFrequency": 3500000000,
|
|
21
|
+
"upperFrequency": 4000000000,
|
|
22
|
+
"frequencyDisplayBaseMultiplier": 1000000,
|
|
23
|
+
"frequencyDisplayNumberDecimals": 3,
|
|
24
|
+
"privileges": [
|
|
25
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
26
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
27
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "f0a4c5e8-d1b3-c7e83f9b-a6c8-d2e1b9c5f7ea",
|
|
32
|
+
"name": "60 Meter",
|
|
33
|
+
"wavelength": 60,
|
|
34
|
+
"lowerFrequency": 5332000,
|
|
35
|
+
"upperFrequency": 5405000,
|
|
36
|
+
"frequencyDisplayBaseMultiplier": 1000,
|
|
37
|
+
"frequencyDisplayNumberDecimals": 1,
|
|
38
|
+
"privileges": [
|
|
39
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
40
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
41
|
+
],
|
|
42
|
+
"channels": [
|
|
43
|
+
{
|
|
44
|
+
"name": "Channel 1",
|
|
45
|
+
"frequency": 5332
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Channel 2",
|
|
49
|
+
"frequency": 5348
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "Channel 3",
|
|
53
|
+
"frequency": 5358.5
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "Channel 4",
|
|
57
|
+
"frequency": 5373
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "Channel 5",
|
|
61
|
+
"frequency": 5405
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "c5ea-f0a4-d1b39-c7ec83fa-a6cd82eb19cf",
|
|
67
|
+
"name": "40 Meter",
|
|
68
|
+
"wavelength": 40,
|
|
69
|
+
"lowerFrequency": 7000000000,
|
|
70
|
+
"upperFrequency": 7300000000,
|
|
71
|
+
"frequencyDisplayBaseMultiplier": 1000000,
|
|
72
|
+
"frequencyDisplayNumberDecimals": 3,
|
|
73
|
+
"privileges": [
|
|
74
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
75
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
76
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "fa83ec-d12ab39-c75ea-a46cd82eb19cf",
|
|
81
|
+
"name": "20 Meter",
|
|
82
|
+
"wavelength": 20,
|
|
83
|
+
"lowerFrequency": 14000000000,
|
|
84
|
+
"upperFrequency": 14350000000,
|
|
85
|
+
"frequencyDisplayBaseMultiplier": 1000000,
|
|
86
|
+
"frequencyDisplayNumberDecimals": 3,
|
|
87
|
+
"privileges": [
|
|
88
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
89
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "a1b2c3d4-e5f6-7g8h-9i0j-k1l2m3n4o5p6",
|
|
94
|
+
"name": "15 Meter",
|
|
95
|
+
"wavelength": 15,
|
|
96
|
+
"lowerFrequency": 21000000,
|
|
97
|
+
"upperFrequency": 21450000,
|
|
98
|
+
"frequencyDisplayBaseMultiplier": 1000,
|
|
99
|
+
"frequencyDisplayNumberDecimals": 0,
|
|
100
|
+
"privileges": [
|
|
101
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
102
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
103
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d",
|
|
104
|
+
"a2c4b6f8-7e9d-3f5b-1c2a-9e7f8d3b6a4c"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "p6o5n4m3-l2k1-j0i9-h8g7-f6e5-d4c3b2a1",
|
|
109
|
+
"name": "10 Meter",
|
|
110
|
+
"wavelength": 10,
|
|
111
|
+
"lowerFrequency": 28000000,
|
|
112
|
+
"upperFrequency": 29700000,
|
|
113
|
+
"frequencyDisplayBaseMultiplier": 1000,
|
|
114
|
+
"frequencyDisplayNumberDecimals": 0,
|
|
115
|
+
"privileges": [
|
|
116
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
117
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
118
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"id": "z1y2x3w4-v5u6-t7s8-r9q0-p1o2n3m4l5k6",
|
|
123
|
+
"name": "2 Meter",
|
|
124
|
+
"wavelength": 2,
|
|
125
|
+
"lowerFrequency": 144000000,
|
|
126
|
+
"upperFrequency": 148000000,
|
|
127
|
+
"frequencyDisplayBaseMultiplier": 1000,
|
|
128
|
+
"frequencyDisplayNumberDecimals": 0,
|
|
129
|
+
"privileges": [
|
|
130
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
131
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
132
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "k6l5m4n3-o2p1-q0r9-s8t7-u6v5-w4x3y2z1",
|
|
137
|
+
"name": "70 Centimeter",
|
|
138
|
+
"wavelength": 0.7,
|
|
139
|
+
"lowerFrequency": 420000000,
|
|
140
|
+
"upperFrequency": 450000000,
|
|
141
|
+
"frequencyDisplayBaseMultiplier": 1000,
|
|
142
|
+
"frequencyDisplayNumberDecimals": 0,
|
|
143
|
+
"privileges": [
|
|
144
|
+
"b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
145
|
+
"f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
146
|
+
"d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d"
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "b7c9d9e4-5f0a-4f2d-9c2d-1e3b8e7a8b5f",
|
|
4
|
+
"name": "Technician"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"id": "f3a6c1d2-8e7f-4a5b-9d3e-2c4a9b6e7f8d",
|
|
8
|
+
"name": "General"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "d6e8f7a2-3b4c-5d9e-1f2a-8c3b7e4a9f6d",
|
|
12
|
+
"name": "Amateur Extra"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "a2c4b6f8-7e9d-3f5b-1c2a-9e7f8d3b6a4c",
|
|
16
|
+
"name": "Advanced (Grandfathered)"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "e7f9d3b6-a2c4-5f1a-8c3b-6f8d2a4b9e7f",
|
|
20
|
+
"name": "Novice (Grandfathered)"
|
|
21
|
+
}
|
|
22
|
+
]
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './memory/segmented-memory.js';
|
|
2
|
+
|
|
3
|
+
export * from './test-utils/radio-channel-factory.js';
|
|
4
|
+
export * from './test-utils/radio-programmed-channel-factory.js';
|
|
5
|
+
|
|
6
|
+
export * from './utils/bcd-to-number.js';
|
|
7
|
+
export * from './utils/band-plan.js';
|
|
8
|
+
export * from './utils/frequency-display.js';
|
|
9
|
+
export * from './utils/memory-channel-utils.js';
|
|
10
|
+
export * from './utils/memory-segment-utils.js';
|
|
11
|
+
export * from './utils/number-to-bcd.js';
|
|
12
|
+
export * from './utils/to-hex-words.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { RadioMemorySegment } from '@springfield/ham-radio-api';
|
|
2
|
+
|
|
3
|
+
export class RadioSegmentedMemory {
|
|
4
|
+
private segments: Map<number, RadioMemorySegment>;
|
|
5
|
+
|
|
6
|
+
constructor(segments?: RadioMemorySegment[]) {
|
|
7
|
+
this.segments = new Map();
|
|
8
|
+
|
|
9
|
+
if (segments != undefined) {
|
|
10
|
+
for (const segment of segments) {
|
|
11
|
+
this.segments.set(segment.startAddress, segment);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public addSegment(segment: RadioMemorySegment): void {
|
|
17
|
+
this.segments.set(segment.startAddress, segment);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getSegments(): IterableIterator<RadioMemorySegment> {
|
|
21
|
+
return this.segments.values();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public getSegment(startAddress: number): RadioMemorySegment | undefined {
|
|
25
|
+
return this.segments.get(startAddress);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public segmentCount(): number {
|
|
29
|
+
return this.segments.size;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public findSegment(address: number): RadioMemorySegment | undefined {
|
|
33
|
+
const iterator = this.segments.values();
|
|
34
|
+
let result = iterator.next();
|
|
35
|
+
|
|
36
|
+
while (!result.done) {
|
|
37
|
+
const segment = result.value;
|
|
38
|
+
|
|
39
|
+
if (segment.startAddress <= address && segment.startAddress + segment.length > address) {
|
|
40
|
+
return segment;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
result = iterator.next();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public toArray(): RadioMemorySegment[] {
|
|
50
|
+
return Array.from(this.segments.values());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CTCSS, Frequency, RadioChannel, RadioChannelId, RadioToneType } from '@springfield/ham-radio-api';
|
|
2
|
+
import { Factory } from 'fishery';
|
|
3
|
+
|
|
4
|
+
export function getRandomCTCSS(): CTCSS {
|
|
5
|
+
const values = Object.values(CTCSS); // Get all values from the enum
|
|
6
|
+
const randomIndex = Math.floor(Math.random() * values.length); // Generate a random index
|
|
7
|
+
return values[randomIndex] as CTCSS; // Return the randomly selected value
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const radioChannelFactory = Factory.define<RadioChannel>(({ sequence }) => ({
|
|
11
|
+
id: RadioChannelId(sequence.toString()),
|
|
12
|
+
name: `c-${sequence}`,
|
|
13
|
+
transmitFrequency: Frequency(144000),
|
|
14
|
+
receiveFrequency: Frequency(144000),
|
|
15
|
+
transmitTone: { type: RadioToneType.CTCSS, tone: getRandomCTCSS() },
|
|
16
|
+
receiveTone: { type: RadioToneType.CTCSS, tone: getRandomCTCSS() },
|
|
17
|
+
}));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RadioProgrammedChannel } from '@springfield/ham-radio-api';
|
|
2
|
+
import { Factory } from 'fishery';
|
|
3
|
+
import { radioChannelFactory } from './radio-channel-factory.js';
|
|
4
|
+
|
|
5
|
+
export const radioProgrammedChannelFactory = Factory.define<RadioProgrammedChannel>(({ sequence }) => ({
|
|
6
|
+
channelNumber: sequence,
|
|
7
|
+
radioChannel: radioChannelFactory.build(),
|
|
8
|
+
}));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SpectrumBand } from '@springfield/ham-radio-api';
|
|
2
|
+
import bandsData from '../db/bands.json' with { type: 'json' };
|
|
3
|
+
import licenseClassesData from '../db/license-classes.json' with { type: 'json' };
|
|
4
|
+
|
|
5
|
+
interface LicenseClass {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class BandPlan {
|
|
11
|
+
private bands: SpectrumBand[];
|
|
12
|
+
private licenseClasses: Map<string, LicenseClass>;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.bands = bandsData as SpectrumBand[];
|
|
16
|
+
this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Finds the spectrum band that contains the given frequency
|
|
21
|
+
* @param frequency The frequency in Hz to find the band for
|
|
22
|
+
* @returns The spectrum band containing the frequency, or undefined if no band contains it
|
|
23
|
+
*/
|
|
24
|
+
findBandByFrequency(frequency: number): SpectrumBand | undefined {
|
|
25
|
+
return this.bands.find((band) => frequency >= band.lowerFrequency && frequency <= band.upperFrequency);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Finds a license class by its ID
|
|
30
|
+
* @param id The ID of the license class to find
|
|
31
|
+
* @returns The license class with the given ID, or undefined if not found
|
|
32
|
+
*/
|
|
33
|
+
findPrivilegeById(id: string): LicenseClass | undefined {
|
|
34
|
+
return this.licenseClasses.get(id);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function bcdToNumber(data: Uint8Array, offset?: number, length?: number): number {
|
|
2
|
+
let bcd = 0;
|
|
3
|
+
const size = length ?? data.length;
|
|
4
|
+
|
|
5
|
+
for (let i = offset ?? 0; i < size; i++) {
|
|
6
|
+
bcd |= data[i] << (8 * i);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return parseInt(bcd.toString(16), 10) * 10;
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Frequency, SpectrumBand } from '@springfield/ham-radio-api';
|
|
2
|
+
|
|
3
|
+
export function frequencyDisplay(frequency: Frequency, band: SpectrumBand): string {
|
|
4
|
+
const frequencyDisplay = frequency / band.frequencyDisplayBaseMultiplier;
|
|
5
|
+
return frequencyDisplay.toFixed(band.frequencyDisplayNumberDecimals);
|
|
6
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { sprintf } from 'sprintf-js';
|
|
2
|
+
import { toHexWords } from './to-hex-words.js';
|
|
3
|
+
import { RadioMemorySegment } from '@springfield/ham-radio-api';
|
|
4
|
+
|
|
5
|
+
export function formatSegment(segment: RadioMemorySegment): string {
|
|
6
|
+
return sprintf('%04X : %s', segment.startAddress, toHexWords(segment.data));
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function numberToBcd(value: number): Uint8Array {
|
|
2
|
+
const digits = value.toString(10);
|
|
3
|
+
const numberOfBytes = Math.ceil(digits.length / 2.0);
|
|
4
|
+
|
|
5
|
+
const data = new Uint8Array(numberOfBytes);
|
|
6
|
+
|
|
7
|
+
let bcd = parseInt(digits, 16);
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < numberOfBytes; i++) {
|
|
10
|
+
data[i] = bcd & 0xff;
|
|
11
|
+
bcd >>= 8;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function toHexWords(data: Uint8Array): string {
|
|
2
|
+
const hexData = Buffer.from(data).toString('hex');
|
|
3
|
+
let formattedData = '';
|
|
4
|
+
|
|
5
|
+
for (let i = 0; i < hexData.length; i += 4) {
|
|
6
|
+
if (i != 0) {
|
|
7
|
+
formattedData += ' ';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
formattedData += hexData.substring(i, i + 4);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return formattedData;
|
|
14
|
+
}
|