@springfield/ham-radio-utils 2.0.2 → 2.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.
@@ -0,0 +1,538 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Radio Protocol DSL Schema",
4
+ "description": "Schema for defining radio communication protocols in a declarative JSON format",
5
+ "type": "object",
6
+ "required": [
7
+ "id",
8
+ "version",
9
+ "description",
10
+ "serialConfig",
11
+ "memoryConfig",
12
+ "settingsSchema",
13
+ "readMemory",
14
+ "writeMemory"
15
+ ],
16
+ "properties": {
17
+ "id": {
18
+ "type": "object",
19
+ "description": "Radio identification information",
20
+ "required": [
21
+ "model",
22
+ "name",
23
+ "manufacturer"
24
+ ],
25
+ "properties": {
26
+ "model": {
27
+ "type": "string",
28
+ "description": "Radio model identifier"
29
+ },
30
+ "name": {
31
+ "type": "string",
32
+ "description": "Human-readable radio name"
33
+ },
34
+ "manufacturer": {
35
+ "type": "string",
36
+ "description": "Radio manufacturer name"
37
+ }
38
+ }
39
+ },
40
+ "version": {
41
+ "type": "string",
42
+ "description": "Version of the radio configuration",
43
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
44
+ },
45
+ "description": {
46
+ "type": "string",
47
+ "description": "Human-readable description of the radio configuration"
48
+ },
49
+ "settingsSchema": {
50
+ "type": "object",
51
+ "description": "Schema definitions for radio settings and channels",
52
+ "required": [
53
+ "model",
54
+ "settingsSchema",
55
+ "channelSchema"
56
+ ],
57
+ "properties": {
58
+ "model": {
59
+ "type": "string",
60
+ "description": "Radio model identifier"
61
+ },
62
+ "settingsSchema": {
63
+ "type": "object",
64
+ "description": "JSON schema for radio settings"
65
+ },
66
+ "channelSchema": {
67
+ "type": "object",
68
+ "description": "JSON schema for radio channels"
69
+ }
70
+ }
71
+ },
72
+ "serialConfig": {
73
+ "type": "object",
74
+ "description": "Serial port configuration",
75
+ "required": [
76
+ "baudRate"
77
+ ],
78
+ "properties": {
79
+ "baudRate": {
80
+ "type": "integer",
81
+ "description": "Serial baud rate",
82
+ "minimum": 1200,
83
+ "maximum": 115200
84
+ },
85
+ "dataBits": {
86
+ "type": "integer",
87
+ "description": "Number of data bits",
88
+ "enum": [
89
+ 5,
90
+ 6,
91
+ 7,
92
+ 8
93
+ ],
94
+ "default": 8
95
+ },
96
+ "stopBits": {
97
+ "oneOf": [
98
+ {
99
+ "type": "integer",
100
+ "enum": [
101
+ 1,
102
+ 2
103
+ ]
104
+ },
105
+ {
106
+ "type": "number",
107
+ "enum": [
108
+ 1.5
109
+ ]
110
+ }
111
+ ],
112
+ "description": "Number of stop bits",
113
+ "default": 1
114
+ },
115
+ "parity": {
116
+ "type": "string",
117
+ "enum": [
118
+ "none",
119
+ "even",
120
+ "odd"
121
+ ],
122
+ "default": "none"
123
+ }
124
+ }
125
+ },
126
+ "memoryConfig": {
127
+ "type": "object",
128
+ "description": "Memory configuration for radio segments",
129
+ "required": [
130
+ "chunkSize",
131
+ "segments"
132
+ ],
133
+ "properties": {
134
+ "chunkSize": {
135
+ "type": "integer",
136
+ "description": "Size of memory chunks in bytes",
137
+ "minimum": 1
138
+ },
139
+ "segments": {
140
+ "type": "object",
141
+ "description": "Memory segment definitions",
142
+ "additionalProperties": {
143
+ "type": "object",
144
+ "required": [
145
+ "startAddress",
146
+ "endAddress"
147
+ ],
148
+ "properties": {
149
+ "startAddress": {
150
+ "type": "integer",
151
+ "description": "Starting address of the memory segment",
152
+ "minimum": 0
153
+ },
154
+ "endAddress": {
155
+ "type": "integer",
156
+ "description": "Ending address (inclusive) of the memory segment",
157
+ "minimum": 0
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ },
164
+ "readMemory": {
165
+ "type": "array",
166
+ "description": "Protocol steps for reading memory",
167
+ "items": {
168
+ "$ref": "#/definitions/protocolStep"
169
+ }
170
+ },
171
+ "writeMemory": {
172
+ "type": "array",
173
+ "description": "Protocol steps for writing memory",
174
+ "items": {
175
+ "$ref": "#/definitions/protocolStep"
176
+ }
177
+ }
178
+ },
179
+ "definitions": {
180
+ "protocolStep": {
181
+ "type": "object",
182
+ "description": "A single protocol step",
183
+ "oneOf": [
184
+ {
185
+ "type": "object",
186
+ "required": [
187
+ "sendReceive"
188
+ ],
189
+ "properties": {
190
+ "sendReceive": {
191
+ "$ref": "#/definitions/sendReceiveStep"
192
+ }
193
+ }
194
+ },
195
+ {
196
+ "type": "object",
197
+ "required": [
198
+ "send"
199
+ ],
200
+ "properties": {
201
+ "send": {
202
+ "$ref": "#/definitions/sendStep"
203
+ }
204
+ }
205
+ },
206
+ {
207
+ "type": "object",
208
+ "required": [
209
+ "receive"
210
+ ],
211
+ "properties": {
212
+ "receive": {
213
+ "$ref": "#/definitions/receiveStep"
214
+ }
215
+ }
216
+ },
217
+ {
218
+ "type": "object",
219
+ "required": [
220
+ "readSegment"
221
+ ],
222
+ "properties": {
223
+ "readSegment": {
224
+ "$ref": "#/definitions/readSegmentStep"
225
+ }
226
+ }
227
+ },
228
+ {
229
+ "type": "object",
230
+ "required": [
231
+ "writeSegment"
232
+ ],
233
+ "properties": {
234
+ "writeSegment": {
235
+ "$ref": "#/definitions/writeSegmentStep"
236
+ }
237
+ }
238
+ },
239
+ {
240
+ "type": "object",
241
+ "required": [
242
+ "setVariable"
243
+ ],
244
+ "properties": {
245
+ "setVariable": {
246
+ "$ref": "#/definitions/setVariableStep"
247
+ }
248
+ }
249
+ }
250
+ ]
251
+ },
252
+ "sendReceiveStep": {
253
+ "type": "object",
254
+ "description": "Send data and receive response",
255
+ "required": [
256
+ "send",
257
+ "receive"
258
+ ],
259
+ "properties": {
260
+ "send": {
261
+ "$ref": "#/definitions/sendData"
262
+ },
263
+ "receive": {
264
+ "$ref": "#/definitions/receiveConfig"
265
+ },
266
+ "timeout": {
267
+ "type": "integer",
268
+ "description": "Timeout in milliseconds",
269
+ "minimum": 1,
270
+ "default": 5000
271
+ },
272
+ "description": {
273
+ "type": "string",
274
+ "description": "Human-readable description of the step"
275
+ }
276
+ }
277
+ },
278
+ "sendStep": {
279
+ "type": "object",
280
+ "description": "Send data only",
281
+ "required": [
282
+ "data"
283
+ ],
284
+ "properties": {
285
+ "data": {
286
+ "$ref": "#/definitions/sendData"
287
+ },
288
+ "description": {
289
+ "type": "string",
290
+ "description": "Human-readable description of the step"
291
+ }
292
+ }
293
+ },
294
+ "receiveStep": {
295
+ "type": "object",
296
+ "description": "Receive data only",
297
+ "required": [
298
+ "type",
299
+ "length"
300
+ ],
301
+ "properties": {
302
+ "type": {
303
+ "type": "string",
304
+ "enum": [
305
+ "exact",
306
+ "variable",
307
+ "pattern",
308
+ "any"
309
+ ],
310
+ "description": "Type of receive operation"
311
+ },
312
+ "value": {
313
+ "type": "integer",
314
+ "description": "Expected value for exact match",
315
+ "minimum": 0,
316
+ "maximum": 255
317
+ },
318
+ "length": {
319
+ "type": "integer",
320
+ "description": "Expected length in bytes",
321
+ "minimum": 1
322
+ },
323
+ "pattern": {
324
+ "type": "array",
325
+ "description": "Pattern for structured responses",
326
+ "items": {
327
+ "oneOf": [
328
+ {
329
+ "type": "string",
330
+ "description": "Literal value (1 byte)"
331
+ },
332
+ {
333
+ "type": "number",
334
+ "description": "Literal value (1 byte)"
335
+ },
336
+ {
337
+ "type": "object",
338
+ "required": [
339
+ "field",
340
+ "size"
341
+ ],
342
+ "properties": {
343
+ "field": {
344
+ "type": "string",
345
+ "description": "Field name for extracted data"
346
+ },
347
+ "size": {
348
+ "type": "integer",
349
+ "description": "Size in bytes (0 for variable length)",
350
+ "minimum": 0
351
+ }
352
+ }
353
+ }
354
+ ]
355
+ }
356
+ },
357
+ "description": {
358
+ "type": "string",
359
+ "description": "Human-readable description of the step"
360
+ }
361
+ }
362
+ },
363
+ "readSegmentStep": {
364
+ "type": "object",
365
+ "description": "Read memory segments",
366
+ "required": [
367
+ "segments",
368
+ "startChunk",
369
+ "endChunk"
370
+ ],
371
+ "properties": {
372
+ "segments": {
373
+ "type": "array",
374
+ "description": "List of segment names to read",
375
+ "items": {
376
+ "type": "string"
377
+ }
378
+ },
379
+ "startChunk": {
380
+ "$ref": "#/definitions/sendReceiveStep",
381
+ "description": "Protocol for starting a chunk read"
382
+ },
383
+ "endChunk": {
384
+ "$ref": "#/definitions/sendReceiveStep",
385
+ "description": "Protocol for ending a chunk read"
386
+ },
387
+ "description": {
388
+ "type": "string",
389
+ "description": "Human-readable description of the step"
390
+ }
391
+ }
392
+ },
393
+ "writeSegmentStep": {
394
+ "type": "object",
395
+ "description": "Write memory segments",
396
+ "required": [
397
+ "segments",
398
+ "send",
399
+ "data",
400
+ "receive"
401
+ ],
402
+ "properties": {
403
+ "segments": {
404
+ "type": "array",
405
+ "description": "List of segment names to write",
406
+ "items": {
407
+ "type": "string"
408
+ }
409
+ },
410
+ "send": {
411
+ "$ref": "#/definitions/sendData",
412
+ "description": "Data to send for write command"
413
+ },
414
+ "data": {
415
+ "type": "string",
416
+ "description": "Expression for segment data to write"
417
+ },
418
+ "receive": {
419
+ "$ref": "#/definitions/receiveConfig",
420
+ "description": "Expected response for write command"
421
+ },
422
+ "description": {
423
+ "type": "string",
424
+ "description": "Human-readable description of the step"
425
+ }
426
+ }
427
+ },
428
+ "setVariableStep": {
429
+ "type": "object",
430
+ "description": "Set a variable value",
431
+ "required": [
432
+ "name",
433
+ "value"
434
+ ],
435
+ "properties": {
436
+ "name": {
437
+ "type": "string",
438
+ "description": "Variable name"
439
+ },
440
+ "value": {
441
+ "oneOf": [
442
+ {
443
+ "type": "string",
444
+ "description": "Expression or literal value"
445
+ },
446
+ {
447
+ "type": "number",
448
+ "description": "Numeric value"
449
+ }
450
+ ],
451
+ "description": "Value to assign to the variable"
452
+ }
453
+ }
454
+ },
455
+ "sendData": {
456
+ "type": "array",
457
+ "description": "Array of data to send",
458
+ "items": {
459
+ "oneOf": [
460
+ {
461
+ "type": "string",
462
+ "description": "String literal or expression"
463
+ },
464
+ {
465
+ "type": "number",
466
+ "description": "Numeric literal or expression",
467
+ "minimum": 0,
468
+ "maximum": 255
469
+ }
470
+ ]
471
+ }
472
+ },
473
+ "receiveConfig": {
474
+ "type": "object",
475
+ "description": "Configuration for receiving data",
476
+ "required": [
477
+ "type"
478
+ ],
479
+ "properties": {
480
+ "type": {
481
+ "type": "string",
482
+ "enum": [
483
+ "exact",
484
+ "variable",
485
+ "pattern",
486
+ "any"
487
+ ],
488
+ "description": "Type of receive operation"
489
+ },
490
+ "value": {
491
+ "type": "integer",
492
+ "description": "Expected value for exact match",
493
+ "minimum": 0,
494
+ "maximum": 255
495
+ },
496
+ "length": {
497
+ "type": "integer",
498
+ "description": "Expected length in bytes",
499
+ "minimum": 1
500
+ },
501
+ "pattern": {
502
+ "type": "array",
503
+ "description": "Pattern for structured responses",
504
+ "items": {
505
+ "oneOf": [
506
+ {
507
+ "type": "string",
508
+ "description": "Literal value (1 byte)"
509
+ },
510
+ {
511
+ "type": "number",
512
+ "description": "Literal value (1 byte)"
513
+ },
514
+ {
515
+ "type": "object",
516
+ "required": [
517
+ "field",
518
+ "size"
519
+ ],
520
+ "properties": {
521
+ "field": {
522
+ "type": "string",
523
+ "description": "Field name for extracted data"
524
+ },
525
+ "size": {
526
+ "type": "integer",
527
+ "description": "Size in bytes (0 for variable length)",
528
+ "minimum": 0
529
+ }
530
+ }
531
+ }
532
+ ]
533
+ }
534
+ }
535
+ }
536
+ }
537
+ }
538
+ }
@@ -0,0 +1,40 @@
1
+ import Ajv from 'ajv';
2
+ import addFormats from 'ajv-formats';
3
+ import radioProtocolSchema from '../schemas/radio-protocol-schema.json' with { type: 'json' };
4
+
5
+ export interface ValidationResult {
6
+ valid: boolean;
7
+ errors?: string[];
8
+ }
9
+
10
+ export class SchemaValidator {
11
+ private ajv: any;
12
+
13
+ constructor() {
14
+ this.ajv = new (Ajv as any)({
15
+ allErrors: true,
16
+ verbose: true,
17
+ });
18
+ (addFormats as any)(this.ajv);
19
+ }
20
+
21
+ validateRadioProtocol(config: unknown): ValidationResult {
22
+ const validate = this.ajv.compile(radioProtocolSchema);
23
+ const valid = validate(config);
24
+
25
+ if (valid) {
26
+ return { valid: true };
27
+ }
28
+
29
+ const errors =
30
+ validate.errors?.map((error: { instancePath?: string; message?: string }) => {
31
+ const path = error.instancePath || 'root';
32
+ return `${path}: ${error.message}`;
33
+ }) || [];
34
+
35
+ return {
36
+ errors,
37
+ valid: false,
38
+ };
39
+ }
40
+ }
@@ -0,0 +1,57 @@
1
+ import { BaseTransport } from '@loglayer/transport';
2
+ import { LogLayer } from 'loglayer';
3
+ import { UILogger } from './ui-logger.js';
4
+
5
+ /**
6
+ * Custom LogLayer transport for capturing UI log entries.
7
+ */
8
+ const UILogTransport = class extends BaseTransport<any> {
9
+ #callback: (logEntry: any) => void;
10
+
11
+ constructor(callback: (logEntry: any) => void) {
12
+ // Pass a dummy logger to BaseTransport
13
+ super({ enabled: true, id: 'ui-logger-transport', logger: { log: () => {} } });
14
+ this.#callback = callback;
15
+ }
16
+
17
+ shipToLogger(params: any) {
18
+ const logEntry = {
19
+ data: params.data,
20
+ hasData: params.hasData,
21
+ level: params.logLevel,
22
+ message: params.messages.join(' '),
23
+ timestamp: new Date().toISOString(),
24
+ };
25
+ this.#callback(logEntry);
26
+ return [];
27
+ }
28
+ };
29
+
30
+ export interface UILoggerConfig {
31
+ customTransport?: any;
32
+ enableConsoleDebug?: boolean;
33
+ }
34
+
35
+ export const createUILogger = function createUILogger(config: UILoggerConfig = {}): UILogger {
36
+ // By default, just print to console as JSON
37
+ const defaultCallback = (logEntry: any) => {
38
+ console.log(JSON.stringify(logEntry));
39
+ };
40
+ const transport = config.customTransport || new UILogTransport(defaultCallback);
41
+ const logger = new LogLayer({
42
+ consoleDebug: config.enableConsoleDebug ?? false,
43
+ transport,
44
+ });
45
+ logger.setLevel('debug');
46
+ return new UILogger(logger);
47
+ };
48
+
49
+ export const createUILoggerWithCallback = function createUILoggerWithCallback(callback: (logEntry: any) => void, config: UILoggerConfig = {}): UILogger {
50
+ const transport = new UILogTransport(callback);
51
+ const logger = new LogLayer({
52
+ consoleDebug: config.enableConsoleDebug ?? false,
53
+ transport,
54
+ });
55
+ logger.setLevel('debug');
56
+ return new UILogger(logger);
57
+ };