@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.
- package/CHANGELOG.md +16 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/radio-protocol-schema.json +538 -0
- package/dist/utils/schema-validator.d.ts +10 -0
- package/dist/utils/schema-validator.d.ts.map +1 -0
- package/dist/utils/schema-validator.js +29 -0
- package/dist/utils/schema-validator.js.map +1 -0
- package/dist/utils/ui-logger-factory.d.ts +8 -0
- package/dist/utils/ui-logger-factory.d.ts.map +1 -0
- package/dist/utils/ui-logger-factory.js +48 -0
- package/dist/utils/ui-logger-factory.js.map +1 -0
- package/dist/utils/ui-logger.d.ts +43 -0
- package/dist/utils/ui-logger.d.ts.map +1 -0
- package/dist/utils/ui-logger.js +155 -0
- package/dist/utils/ui-logger.js.map +1 -0
- package/package.json +9 -4
- package/src/index.ts +6 -0
- package/src/schemas/radio-protocol-schema.json +538 -0
- package/src/utils/schema-validator.ts +40 -0
- package/src/utils/ui-logger-factory.ts +57 -0
- package/src/utils/ui-logger.ts +203 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { ILogLayer } from 'loglayer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UI Logger: Command-Level Logging for UI Display
|
|
5
|
+
*
|
|
6
|
+
* This module provides a specialized logger for capturing command-level information
|
|
7
|
+
* that can be easily parsed and displayed in a UI. It captures details about
|
|
8
|
+
* commands, data sent, data expected, and data received in a structured format.
|
|
9
|
+
*
|
|
10
|
+
* Purpose:
|
|
11
|
+
* - Captures command-level information for UI display
|
|
12
|
+
* - Provides structured JSON logging for easy parsing
|
|
13
|
+
* - Tracks command execution details including timing
|
|
14
|
+
* - Supports protocol debugging in UI environments
|
|
15
|
+
* - Maintains separation from debug logging
|
|
16
|
+
*
|
|
17
|
+
* Design Rationale:
|
|
18
|
+
* - Single log entry per command provides clean UI display
|
|
19
|
+
* - JSON structure enables easy parsing and filtering
|
|
20
|
+
* - Command-level granularity is appropriate for UI debugging
|
|
21
|
+
* - Structured data supports rich UI representations
|
|
22
|
+
* - Separate from debug logging avoids UI noise
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generic protocol step interface for UI logging
|
|
27
|
+
*/
|
|
28
|
+
export interface UIProtocolStep {
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* UI Logger for capturing command-level information for UI display
|
|
34
|
+
*/
|
|
35
|
+
export class UILogger {
|
|
36
|
+
private logger: ILogLayer;
|
|
37
|
+
private commandStartTimes = new Map<string, number>();
|
|
38
|
+
|
|
39
|
+
constructor(logger: ILogLayer) {
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// eslint-disable-next-line max-params
|
|
44
|
+
startCommand(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep): void {
|
|
45
|
+
const commandId = `${operation}-${stepIndex}`;
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
this.commandStartTimes.set(commandId, startTime);
|
|
48
|
+
|
|
49
|
+
const commandType = this.getCommandType(step);
|
|
50
|
+
const description = this.getStepDescription(step);
|
|
51
|
+
|
|
52
|
+
this.logger
|
|
53
|
+
.withMetadata({
|
|
54
|
+
commandId,
|
|
55
|
+
commandType,
|
|
56
|
+
description,
|
|
57
|
+
operation,
|
|
58
|
+
startTime,
|
|
59
|
+
stepIndex,
|
|
60
|
+
totalSteps,
|
|
61
|
+
})
|
|
62
|
+
.info('Command started');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// eslint-disable-next-line max-params
|
|
66
|
+
logCommandSuccess(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, context: any): void {
|
|
67
|
+
const commandId = `${operation}-${stepIndex}`;
|
|
68
|
+
const startTime = this.commandStartTimes.get(commandId) || Date.now();
|
|
69
|
+
const endTime = Date.now();
|
|
70
|
+
const duration = endTime - startTime;
|
|
71
|
+
|
|
72
|
+
const commandType = this.getCommandType(step);
|
|
73
|
+
const description = this.getStepDescription(step);
|
|
74
|
+
|
|
75
|
+
// Extract sent and received data from context
|
|
76
|
+
const dataSent = context.variables?.get('lastSentData');
|
|
77
|
+
const dataReceived = context.variables?.get('lastReceivedData');
|
|
78
|
+
const dataExpected = this.getExpectedData(step);
|
|
79
|
+
|
|
80
|
+
// For readSegment, also extract chunk logs if present
|
|
81
|
+
let dataChunks = undefined;
|
|
82
|
+
if ('readSegment' === commandType) {
|
|
83
|
+
dataChunks = context.variables?.get('lastReadSegmentChunks');
|
|
84
|
+
// For readSegment, we don't need the flattened dataSent/dataReceived since we have dataChunks
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Convert dataSent and dataReceived to byte array format if they exist
|
|
88
|
+
const dataSentArray = dataSent ? [...dataSent] : undefined;
|
|
89
|
+
const dataReceivedArray = dataReceived ? [...dataReceived] : undefined;
|
|
90
|
+
|
|
91
|
+
this.logger
|
|
92
|
+
.withMetadata({
|
|
93
|
+
commandType,
|
|
94
|
+
dataChunks,
|
|
95
|
+
dataExpected,
|
|
96
|
+
dataReceived: dataReceivedArray,
|
|
97
|
+
dataSent: dataSentArray,
|
|
98
|
+
description,
|
|
99
|
+
duration,
|
|
100
|
+
endTime,
|
|
101
|
+
operation,
|
|
102
|
+
startTime,
|
|
103
|
+
stepIndex,
|
|
104
|
+
success: true,
|
|
105
|
+
totalSteps,
|
|
106
|
+
})
|
|
107
|
+
.info('Command completed successfully');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// eslint-disable-next-line max-params
|
|
111
|
+
logCommandFailure(stepIndex: number, totalSteps: number, operation: string, step: UIProtocolStep, error: Error, context: any): void {
|
|
112
|
+
const commandId = `${operation}-${stepIndex}`;
|
|
113
|
+
const startTime = this.commandStartTimes.get(commandId) || Date.now();
|
|
114
|
+
const endTime = Date.now();
|
|
115
|
+
const duration = endTime - startTime;
|
|
116
|
+
|
|
117
|
+
const commandType = this.getCommandType(step);
|
|
118
|
+
const description = this.getStepDescription(step);
|
|
119
|
+
|
|
120
|
+
// Extract sent data from context
|
|
121
|
+
const dataSent = context.variables?.get('lastSentData');
|
|
122
|
+
const dataExpected = this.getExpectedData(step);
|
|
123
|
+
|
|
124
|
+
// Convert dataSent to byte array format if it exists
|
|
125
|
+
const dataSentArray = dataSent ? [...dataSent] : undefined;
|
|
126
|
+
|
|
127
|
+
this.logger
|
|
128
|
+
.withMetadata({
|
|
129
|
+
commandType,
|
|
130
|
+
dataExpected,
|
|
131
|
+
dataSent: dataSentArray,
|
|
132
|
+
description,
|
|
133
|
+
duration,
|
|
134
|
+
endTime,
|
|
135
|
+
error: error.message,
|
|
136
|
+
operation,
|
|
137
|
+
startTime,
|
|
138
|
+
stepIndex,
|
|
139
|
+
success: false,
|
|
140
|
+
totalSteps,
|
|
141
|
+
})
|
|
142
|
+
.info('Command failed');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private getCommandType(step: UIProtocolStep): string {
|
|
146
|
+
if ('sendReceive' in step) {
|
|
147
|
+
return 'sendReceive';
|
|
148
|
+
}
|
|
149
|
+
if ('send' in step) {
|
|
150
|
+
return 'send';
|
|
151
|
+
}
|
|
152
|
+
if ('receive' in step) {
|
|
153
|
+
return 'receive';
|
|
154
|
+
}
|
|
155
|
+
if ('readSegment' in step) {
|
|
156
|
+
return 'readSegment';
|
|
157
|
+
}
|
|
158
|
+
if ('writeSegment' in step) {
|
|
159
|
+
return 'writeSegment';
|
|
160
|
+
}
|
|
161
|
+
if ('setVariable' in step) {
|
|
162
|
+
return 'setVariable';
|
|
163
|
+
}
|
|
164
|
+
return 'unknown';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private getStepDescription(step: UIProtocolStep): string {
|
|
168
|
+
if ('sendReceive' in step && step.sendReceive.description) {
|
|
169
|
+
return step.sendReceive.description;
|
|
170
|
+
}
|
|
171
|
+
if ('send' in step && step.send.description) {
|
|
172
|
+
return step.send.description;
|
|
173
|
+
}
|
|
174
|
+
if ('receive' in step && step.receive.description) {
|
|
175
|
+
return step.receive.description;
|
|
176
|
+
}
|
|
177
|
+
if ('readSegment' in step && step.readSegment.description) {
|
|
178
|
+
return step.readSegment.description;
|
|
179
|
+
}
|
|
180
|
+
if ('writeSegment' in step && step.writeSegment.description) {
|
|
181
|
+
return step.writeSegment.description;
|
|
182
|
+
}
|
|
183
|
+
return 'No description';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private getExpectedData(step: UIProtocolStep): any {
|
|
187
|
+
if ('sendReceive' in step && step.sendReceive.receive) {
|
|
188
|
+
return step.sendReceive.receive;
|
|
189
|
+
}
|
|
190
|
+
if ('receive' in step) {
|
|
191
|
+
return step.receive;
|
|
192
|
+
}
|
|
193
|
+
if ('readSegment' in step) {
|
|
194
|
+
// For readSegment, return both start and end chunk receive patterns
|
|
195
|
+
return {
|
|
196
|
+
endChunk: step.readSegment.endChunk.receive,
|
|
197
|
+
startChunk: step.readSegment.startChunk.receive,
|
|
198
|
+
type: 'readSegment',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|