@springfield/ham-radio-utils 4.1.1 → 4.2.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 +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/memory-map-codec.d.ts +22 -0
- package/dist/memory/memory-map-codec.d.ts.map +1 -0
- package/dist/memory/memory-map-codec.js +397 -0
- package/dist/memory/memory-map-codec.js.map +1 -0
- package/dist/memory/memory-map-ui.d.ts +22 -0
- package/dist/memory/memory-map-ui.d.ts.map +1 -0
- package/dist/memory/memory-map-ui.js +46 -0
- package/dist/memory/memory-map-ui.js.map +1 -0
- package/dist/schemas/radio-memory-map-schema.json +151 -0
- package/dist/utils/band-plan.d.ts +13 -4
- package/dist/utils/band-plan.d.ts.map +1 -1
- package/dist/utils/band-plan.js +23 -4
- package/dist/utils/band-plan.js.map +1 -1
- package/dist/utils/schema-validator.d.ts +2 -0
- package/dist/utils/schema-validator.d.ts.map +1 -1
- package/dist/utils/schema-validator.js +9 -2
- package/dist/utils/schema-validator.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +3 -0
- package/src/memory/memory-map-codec.ts +522 -0
- package/src/memory/memory-map-ui.ts +71 -0
- package/src/schemas/radio-memory-map-schema.json +151 -0
- package/src/utils/band-plan.ts +29 -6
- package/src/utils/schema-validator.ts +11 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Radio Memory-Map DSL Schema",
|
|
4
|
+
"description": "Schema for defining radio EEPROM settings layout as JSON (Chirp MEM_FORMAT as data)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["structs"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Optional map version"
|
|
11
|
+
},
|
|
12
|
+
"description": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"structs": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"minItems": 1,
|
|
18
|
+
"items": {
|
|
19
|
+
"$ref": "#/definitions/struct"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"definitions": {
|
|
24
|
+
"seek": {
|
|
25
|
+
"oneOf": [
|
|
26
|
+
{ "type": "integer", "minimum": 0 },
|
|
27
|
+
{ "type": "string", "pattern": "^(0[xX][0-9a-fA-F]+|[0-9]+)$" }
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"ui": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"required": ["group", "label", "widget"],
|
|
33
|
+
"properties": {
|
|
34
|
+
"group": { "type": "string" },
|
|
35
|
+
"label": { "type": "string" },
|
|
36
|
+
"widget": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["integer", "select", "switch", "text", "number"]
|
|
39
|
+
},
|
|
40
|
+
"description": { "type": "string" },
|
|
41
|
+
"writable": { "type": "boolean" }
|
|
42
|
+
},
|
|
43
|
+
"additionalProperties": false
|
|
44
|
+
},
|
|
45
|
+
"value": {
|
|
46
|
+
"oneOf": [
|
|
47
|
+
{
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": ["kind"],
|
|
50
|
+
"properties": {
|
|
51
|
+
"kind": { "const": "integer" },
|
|
52
|
+
"min": { "type": "number" },
|
|
53
|
+
"max": { "type": "number" }
|
|
54
|
+
},
|
|
55
|
+
"additionalProperties": false
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"type": "object",
|
|
59
|
+
"required": ["kind"],
|
|
60
|
+
"properties": {
|
|
61
|
+
"kind": { "const": "boolean" }
|
|
62
|
+
},
|
|
63
|
+
"additionalProperties": false
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"type": "object",
|
|
67
|
+
"required": ["kind", "values"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"kind": { "const": "enum" },
|
|
70
|
+
"values": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"items": { "type": "string" },
|
|
73
|
+
"minItems": 1
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "object",
|
|
80
|
+
"required": ["kind", "length"],
|
|
81
|
+
"properties": {
|
|
82
|
+
"kind": { "const": "ascii" },
|
|
83
|
+
"length": { "type": "integer", "minimum": 1 }
|
|
84
|
+
},
|
|
85
|
+
"additionalProperties": false
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"type": "object",
|
|
89
|
+
"required": ["kind", "length"],
|
|
90
|
+
"properties": {
|
|
91
|
+
"kind": { "const": "digits" },
|
|
92
|
+
"length": { "type": "integer", "minimum": 1 },
|
|
93
|
+
"scale": { "type": "number" }
|
|
94
|
+
},
|
|
95
|
+
"additionalProperties": false
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "object",
|
|
99
|
+
"required": ["kind", "length"],
|
|
100
|
+
"properties": {
|
|
101
|
+
"kind": { "const": "dtmf" },
|
|
102
|
+
"length": { "type": "integer", "minimum": 1 },
|
|
103
|
+
"charset": { "type": "string" }
|
|
104
|
+
},
|
|
105
|
+
"additionalProperties": false
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"type": "object",
|
|
109
|
+
"required": ["kind", "length"],
|
|
110
|
+
"properties": {
|
|
111
|
+
"kind": { "const": "bbcd" },
|
|
112
|
+
"length": { "type": "integer", "minimum": 1 }
|
|
113
|
+
},
|
|
114
|
+
"additionalProperties": false
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
"field": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"required": ["id", "type"],
|
|
121
|
+
"properties": {
|
|
122
|
+
"id": { "type": "string", "minLength": 1 },
|
|
123
|
+
"type": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"enum": ["u8", "u16", "bits", "char"]
|
|
126
|
+
},
|
|
127
|
+
"width": { "type": "integer", "minimum": 1, "maximum": 8 },
|
|
128
|
+
"reserved": { "type": "boolean" },
|
|
129
|
+
"value": { "$ref": "#/definitions/value" },
|
|
130
|
+
"ui": { "$ref": "#/definitions/ui" }
|
|
131
|
+
},
|
|
132
|
+
"additionalProperties": false
|
|
133
|
+
},
|
|
134
|
+
"struct": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"required": ["id", "seek", "fields"],
|
|
137
|
+
"properties": {
|
|
138
|
+
"id": { "type": "string", "minLength": 1 },
|
|
139
|
+
"seek": { "$ref": "#/definitions/seek" },
|
|
140
|
+
"fields": {
|
|
141
|
+
"type": "array",
|
|
142
|
+
"minItems": 1,
|
|
143
|
+
"items": { "$ref": "#/definitions/field" }
|
|
144
|
+
},
|
|
145
|
+
"count": { "type": "integer", "minimum": 1 },
|
|
146
|
+
"stride": { "type": "integer", "minimum": 1 }
|
|
147
|
+
},
|
|
148
|
+
"additionalProperties": false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/utils/band-plan.ts
CHANGED
|
@@ -8,21 +8,36 @@ interface LicenseClass {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export class BandPlan {
|
|
11
|
-
private bands: SpectrumBand[];
|
|
12
|
-
private licenseClasses: Map<string, LicenseClass>;
|
|
11
|
+
private readonly bands: SpectrumBand[];
|
|
12
|
+
private readonly licenseClasses: Map<string, LicenseClass>;
|
|
13
|
+
private readonly frsLicenseClassId: string | undefined;
|
|
13
14
|
|
|
14
15
|
constructor() {
|
|
15
16
|
this.bands = bandsData as SpectrumBand[];
|
|
16
17
|
this.licenseClasses = new Map((licenseClassesData as LicenseClass[]).map((licenseClass) => [licenseClass.id, licenseClass]));
|
|
18
|
+
this.frsLicenseClassId = [...this.licenseClasses.values()].find((licenseClass) => licenseClass.name === 'FRS')?.id;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
* Finds the spectrum band that contains the given frequency
|
|
22
|
+
* Finds the spectrum band that contains the given frequency.
|
|
23
|
+
*
|
|
24
|
+
* Channelized allocations (FRS, GMRS, weather) interleave in the same
|
|
25
|
+
* MHz range. An exact channel match is preferred over a surrounding
|
|
26
|
+
* band envelope so FRS interstitial frequencies are not treated as GMRS
|
|
27
|
+
* repeater inputs.
|
|
28
|
+
*
|
|
21
29
|
* @param frequency The frequency in Hz to find the band for
|
|
22
30
|
* @returns The spectrum band containing the frequency, or undefined if no band contains it
|
|
23
31
|
*/
|
|
24
32
|
findBandByFrequency(frequency: number): SpectrumBand | undefined {
|
|
25
|
-
|
|
33
|
+
const frequencyHz = Math.round(frequency);
|
|
34
|
+
const channelMatch = this.bands.find((band) => band.channels?.some((channel) => channel.frequency === frequencyHz));
|
|
35
|
+
|
|
36
|
+
if (channelMatch) {
|
|
37
|
+
return channelMatch;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return this.bands.find((band) => frequencyHz >= band.lowerFrequency && frequencyHz <= band.upperFrequency);
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
/**
|
|
@@ -38,7 +53,9 @@ export class BandPlan {
|
|
|
38
53
|
* Returns whether the given license class may transmit on the frequency.
|
|
39
54
|
*
|
|
40
55
|
* Looks up the band that contains the frequency and checks that band's
|
|
41
|
-
* privileges list.
|
|
56
|
+
* privileges list. FRS is a license-free service, so any band that
|
|
57
|
+
* includes the FRS privilege is allowed for every operator. Frequencies
|
|
58
|
+
* outside every known band return false.
|
|
42
59
|
*
|
|
43
60
|
* @param frequencyHz - Frequency in Hz
|
|
44
61
|
* @param licenseClassId - Springfield license-class ID
|
|
@@ -51,6 +68,12 @@ export class BandPlan {
|
|
|
51
68
|
return false;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
const privileges = band.privileges as string[];
|
|
72
|
+
|
|
73
|
+
if (this.frsLicenseClassId && privileges.includes(this.frsLicenseClassId)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return privileges.includes(licenseClassId);
|
|
55
78
|
}
|
|
56
79
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Ajv } from 'ajv';
|
|
2
2
|
import addFormats from 'ajv-formats';
|
|
3
3
|
import radioProtocolSchema from '../schemas/radio-protocol-schema.json' with { type: 'json' };
|
|
4
|
+
import radioMemoryMapSchema from '../schemas/radio-memory-map-schema.json' with { type: 'json' };
|
|
4
5
|
|
|
5
6
|
export interface ValidationResult {
|
|
6
7
|
valid: boolean;
|
|
@@ -24,8 +25,16 @@ export class SchemaValidator {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
validateRadioProtocol(config: unknown): ValidationResult {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
return this.validateAgainst(radioProtocolSchema, config);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
validateMemoryMap(memoryMap: unknown): ValidationResult {
|
|
32
|
+
return this.validateAgainst(radioMemoryMapSchema, memoryMap);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private validateAgainst(schema: object, data: unknown): ValidationResult {
|
|
36
|
+
const validate = this.ajv.compile(schema);
|
|
37
|
+
const valid = validate(data);
|
|
29
38
|
|
|
30
39
|
if (valid) {
|
|
31
40
|
return { valid: true };
|