ngx-kel-agent 0.4.10 → 0.5.3
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/esm2020/lib/agent-message.service.mjs +18 -0
- package/esm2020/lib/agent.service.mjs +89 -196
- package/esm2020/lib/hamlib-messages.mjs +2 -0
- package/esm2020/lib/hamlib.service.mjs +48 -0
- package/esm2020/lib/wsjtx-messages.mjs +2 -0
- package/esm2020/lib/wsjtx.service.mjs +251 -0
- package/esm2020/public-api.mjs +6 -2
- package/fesm2015/ngx-kel-agent.mjs +360 -174
- package/fesm2015/ngx-kel-agent.mjs.map +1 -1
- package/fesm2020/ngx-kel-agent.mjs +359 -166
- package/fesm2020/ngx-kel-agent.mjs.map +1 -1
- package/lib/agent-message.service.d.ts +9 -0
- package/lib/agent.service.d.ts +112 -29
- package/lib/hamlib-messages.d.ts +10 -0
- package/lib/hamlib.service.d.ts +16 -0
- package/lib/{messages.d.ts → wsjtx-messages.d.ts} +0 -10
- package/lib/wsjtx.service.d.ts +64 -0
- package/package.json +4 -4
- package/public-api.d.ts +5 -1
- package/esm2020/lib/messages.mjs +0 -2
|
@@ -1,237 +1,162 @@
|
|
|
1
|
+
import { Subject, BehaviorSubject, ReplaySubject } from 'rxjs';
|
|
1
2
|
import * as i0 from '@angular/core';
|
|
2
3
|
import { Injectable } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, ReplaySubject, Subject } from 'rxjs';
|
|
4
4
|
import { debounceTime, retryWhen, tap, delay } from 'rxjs/operators';
|
|
5
5
|
import { webSocket } from 'rxjs/webSocket';
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class AgentMessageService {
|
|
8
8
|
constructor() {
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
|
|
9
|
+
this.rxMessage$ = new Subject();
|
|
10
|
+
this.txMessage$ = new Subject();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
AgentMessageService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: AgentMessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
14
|
+
AgentMessageService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: AgentMessageService, providedIn: 'root' });
|
|
15
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: AgentMessageService, decorators: [{
|
|
16
|
+
type: Injectable,
|
|
17
|
+
args: [{
|
|
18
|
+
providedIn: 'root',
|
|
19
|
+
}]
|
|
20
|
+
}], ctorParameters: function () { return []; } });
|
|
21
|
+
|
|
22
|
+
class WsjtxService {
|
|
23
|
+
constructor(messages) {
|
|
24
|
+
this.messages = messages;
|
|
12
25
|
/** Whether we're getting any messages from WSJT-X. */
|
|
13
|
-
this.
|
|
26
|
+
this.connected$ = new BehaviorSubject(false);
|
|
14
27
|
/** Subject for listening to WSJT-X "Heartbeat" messages. */
|
|
15
|
-
this.
|
|
28
|
+
this.heartbeat$ = new ReplaySubject(1);
|
|
16
29
|
/** Subject for listening to WSJT-X "Status" messages. */
|
|
17
|
-
this.
|
|
30
|
+
this.status$ = new ReplaySubject(1);
|
|
18
31
|
/** Subject for listening to WSJT-X "Decode" messages. */
|
|
19
|
-
this.
|
|
32
|
+
this.decode$ = new Subject();
|
|
20
33
|
/** Subject for listening to WSJT-X "Clear" messages. */
|
|
21
|
-
this.
|
|
34
|
+
this.clear$ = new Subject();
|
|
22
35
|
/** Subject for listening to WSJT-X "QsoLogged" messages. */
|
|
23
|
-
this.
|
|
36
|
+
this.qsoLogged$ = new Subject();
|
|
24
37
|
/** Subject for listening to WSJT-X "Close" messages. */
|
|
25
|
-
this.
|
|
38
|
+
this.close$ = new Subject();
|
|
26
39
|
/** Subject for listening to WSJT-X "WsprDecode" messages. */
|
|
27
|
-
this.
|
|
40
|
+
this.wsprDecode$ = new Subject();
|
|
28
41
|
/** Subject for listening to WSJT-X "LoggedAdif" messages. */
|
|
29
|
-
this.
|
|
30
|
-
/* Hamlib */
|
|
31
|
-
/** Whether we're getting any messages from Hamlib. */
|
|
32
|
-
this.hamlibState$ = new BehaviorSubject(false);
|
|
33
|
-
/** Subject for listening to Hamlib "RigState" messages. */
|
|
34
|
-
this.hamlibRigState$ = new BehaviorSubject(null);
|
|
35
|
-
this.defaultAgentHost = 'localhost';
|
|
36
|
-
this.defaultAgentPort = 8081;
|
|
37
|
-
this.localStorageHostKey = 'agent-host';
|
|
38
|
-
this.localStoragePortKey = 'agent-port';
|
|
39
|
-
this.agentHost = this.defaultAgentHost;
|
|
40
|
-
this.agentPort = this.defaultAgentPort;
|
|
41
|
-
this.agentWebSocketSubject = null;
|
|
42
|
-
this.agentWebsocketSubscription = null;
|
|
42
|
+
this.loggedAdif$ = new Subject();
|
|
43
43
|
this.wsjtxId = 'WSJT-X';
|
|
44
|
+
this.setupBehaviors();
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
this.
|
|
47
|
-
this.agentPort = this.getPort();
|
|
48
|
-
this.setupWsjtxBehaviors();
|
|
49
|
-
this.setupHamlibBehaviors();
|
|
50
|
-
this.connect();
|
|
51
|
-
}
|
|
52
|
-
setupWsjtxBehaviors() {
|
|
46
|
+
setupBehaviors() {
|
|
47
|
+
this.messages.rxMessage$.subscribe((msg) => this.handleMessage(msg));
|
|
53
48
|
// if we haven't heard from WSJT-X in 15 seconds, consider it "down"
|
|
54
|
-
this.
|
|
49
|
+
this.connected$
|
|
55
50
|
.pipe(debounceTime(15000))
|
|
56
|
-
.subscribe(() => this.
|
|
51
|
+
.subscribe(() => this.connected$.next(false));
|
|
57
52
|
// When WSJT-X announces it's closing, set it to "down" immediately
|
|
58
|
-
this.
|
|
59
|
-
this.
|
|
53
|
+
this.close$.subscribe(() => {
|
|
54
|
+
this.connected$.next(false);
|
|
60
55
|
});
|
|
61
56
|
// When WSJT-X goes down, clear its persistent message subjects
|
|
62
|
-
this.
|
|
57
|
+
this.connected$.subscribe((isUp) => {
|
|
63
58
|
if (!isUp) {
|
|
64
|
-
this.
|
|
65
|
-
this.
|
|
59
|
+
this.heartbeat$.next(null);
|
|
60
|
+
this.status$.next(null);
|
|
66
61
|
}
|
|
67
62
|
});
|
|
68
63
|
}
|
|
69
|
-
setupHamlibBehaviors() {
|
|
70
|
-
// if we haven't heard from Hamlib in 15 seconds, consider it down
|
|
71
|
-
this.hamlibState$.pipe(debounceTime(15000)).subscribe(() => {
|
|
72
|
-
this.hamlibState$.next(false);
|
|
73
|
-
});
|
|
74
|
-
// When Hamlib goes down, clear its persistent message subjects
|
|
75
|
-
this.hamlibState$.subscribe((isUp) => {
|
|
76
|
-
if (!isUp) {
|
|
77
|
-
this.hamlibRigState$.next(null);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
/** Connect (or reconnect) the websocket to the kel-agent server. */
|
|
82
|
-
connect() {
|
|
83
|
-
if (this.agentWebsocketSubscription) {
|
|
84
|
-
this.agentWebsocketSubscription.unsubscribe();
|
|
85
|
-
}
|
|
86
|
-
this.agentHost = this.getHost();
|
|
87
|
-
this.agentPort = this.getPort();
|
|
88
|
-
const protocol = this.agentHost === 'localhost' ? 'ws://' : 'wss://';
|
|
89
|
-
this.agentWebSocketSubject = webSocket({
|
|
90
|
-
url: protocol + this.agentHost + ':' + this.agentPort + '/websocket',
|
|
91
|
-
});
|
|
92
|
-
this.connectedState$.next(true);
|
|
93
|
-
this.agentWebsocketSubscription = this.agentWebSocketSubject
|
|
94
|
-
.pipe(retryWhen((errors) =>
|
|
95
|
-
// retry the websocket connection after 10 seconds
|
|
96
|
-
errors.pipe(tap(() => this.connectedState$.next(false)), delay(10000))))
|
|
97
|
-
.subscribe({
|
|
98
|
-
next: (msg) => {
|
|
99
|
-
this.connectedState$.next(true);
|
|
100
|
-
this.handleMessage(msg);
|
|
101
|
-
},
|
|
102
|
-
error: () => this.connectedState$.next(false),
|
|
103
|
-
complete: () => this.connectedState$.next(false),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
64
|
handleMessage(msg) {
|
|
107
|
-
if (msg.wsjtx
|
|
108
|
-
|
|
109
|
-
this.wsjtxId = msg.wsjtx.payload.id;
|
|
110
|
-
switch (msg.wsjtx.type) {
|
|
111
|
-
case 'HeartbeatMessage':
|
|
112
|
-
this.wsjtxHeartbeat$.next(msg.wsjtx.payload);
|
|
113
|
-
return;
|
|
114
|
-
case 'StatusMessage':
|
|
115
|
-
this.wsjtxStatus$.next(msg.wsjtx.payload);
|
|
116
|
-
return;
|
|
117
|
-
case 'DecodeMessage':
|
|
118
|
-
this.wsjtxDecode$.next(msg.wsjtx.payload);
|
|
119
|
-
return;
|
|
120
|
-
case 'ClearMessage':
|
|
121
|
-
this.wsjtxClear$.next(msg.wsjtx.payload);
|
|
122
|
-
return;
|
|
123
|
-
case 'QsoLoggedMessage':
|
|
124
|
-
this.wsjtxQsoLogged$.next(msg.wsjtx.payload);
|
|
125
|
-
return;
|
|
126
|
-
case 'CloseMessage':
|
|
127
|
-
this.wsjtxClose$.next(msg.wsjtx.payload);
|
|
128
|
-
return;
|
|
129
|
-
case 'WSPRDecodeMessage':
|
|
130
|
-
this.wsjtxWsprDecode$.next(msg.wsjtx.payload);
|
|
131
|
-
return;
|
|
132
|
-
case 'LoggedAdifMessage':
|
|
133
|
-
this.wsjtxLoggedAdif$.next(msg.wsjtx.payload);
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
65
|
+
if (!msg.wsjtx) {
|
|
66
|
+
return;
|
|
136
67
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
68
|
+
this.connected$.next(true);
|
|
69
|
+
this.wsjtxId = msg.wsjtx.payload.id;
|
|
70
|
+
switch (msg.wsjtx.type) {
|
|
71
|
+
case 'HeartbeatMessage':
|
|
72
|
+
this.heartbeat$.next(msg.wsjtx.payload);
|
|
73
|
+
return;
|
|
74
|
+
case 'StatusMessage':
|
|
75
|
+
this.status$.next(msg.wsjtx.payload);
|
|
76
|
+
return;
|
|
77
|
+
case 'DecodeMessage':
|
|
78
|
+
this.decode$.next(msg.wsjtx.payload);
|
|
79
|
+
return;
|
|
80
|
+
case 'ClearMessage':
|
|
81
|
+
this.clear$.next(msg.wsjtx.payload);
|
|
82
|
+
return;
|
|
83
|
+
case 'QsoLoggedMessage':
|
|
84
|
+
this.qsoLogged$.next(msg.wsjtx.payload);
|
|
85
|
+
return;
|
|
86
|
+
case 'CloseMessage':
|
|
87
|
+
this.close$.next(msg.wsjtx.payload);
|
|
88
|
+
return;
|
|
89
|
+
case 'WSPRDecodeMessage':
|
|
90
|
+
this.wsprDecode$.next(msg.wsjtx.payload);
|
|
91
|
+
return;
|
|
92
|
+
case 'LoggedAdifMessage':
|
|
93
|
+
this.loggedAdif$.next(msg.wsjtx.payload);
|
|
94
|
+
return;
|
|
144
95
|
}
|
|
145
96
|
}
|
|
146
|
-
/** Get the currently configured kel-agent host. */
|
|
147
|
-
getHost() {
|
|
148
|
-
return localStorage.getItem(this.localStorageHostKey) || this.defaultAgentHost;
|
|
149
|
-
}
|
|
150
|
-
/** Get the currently configured kel-agent port. */
|
|
151
|
-
getPort() {
|
|
152
|
-
let portStr = localStorage.getItem(this.localStoragePortKey);
|
|
153
|
-
if (portStr == null) {
|
|
154
|
-
return this.defaultAgentPort;
|
|
155
|
-
}
|
|
156
|
-
let portNum = parseInt(portStr, 10);
|
|
157
|
-
if (isNaN(portNum)) {
|
|
158
|
-
return this.defaultAgentPort;
|
|
159
|
-
}
|
|
160
|
-
return portNum;
|
|
161
|
-
}
|
|
162
|
-
/** Set the kel-agent host. */
|
|
163
|
-
setHost(host) {
|
|
164
|
-
localStorage.setItem(this.localStorageHostKey, host);
|
|
165
|
-
this.connect();
|
|
166
|
-
}
|
|
167
|
-
/** Set the kel-agent port. */
|
|
168
|
-
setPort(port) {
|
|
169
|
-
localStorage.setItem(this.localStoragePortKey, String(port));
|
|
170
|
-
this.connect();
|
|
171
|
-
}
|
|
172
97
|
/** Send a command to WSJT-X to clear the Band Activity window. */
|
|
173
|
-
|
|
98
|
+
clearBandActivity() {
|
|
174
99
|
const wsMsg = {
|
|
175
100
|
wsjtx: {
|
|
176
101
|
type: 'ClearMessage',
|
|
177
102
|
payload: { id: this.wsjtxId, window: 0 },
|
|
178
103
|
},
|
|
179
104
|
};
|
|
180
|
-
this.
|
|
105
|
+
this.messages.txMessage$.next(wsMsg);
|
|
181
106
|
}
|
|
182
107
|
/** Send a command to WSJT-X to clear the Rx Frequency window. */
|
|
183
|
-
|
|
108
|
+
clearRxFreqWindow() {
|
|
184
109
|
const wsMsg = {
|
|
185
110
|
wsjtx: {
|
|
186
111
|
type: 'ClearMessage',
|
|
187
112
|
payload: { id: this.wsjtxId, window: 1 },
|
|
188
113
|
},
|
|
189
114
|
};
|
|
190
|
-
this.
|
|
115
|
+
this.messages.txMessage$.next(wsMsg);
|
|
191
116
|
}
|
|
192
117
|
/** Send a command to WSJT-X to clear the Band Activity and Rx Frequency windows. */
|
|
193
|
-
|
|
118
|
+
clearAll() {
|
|
194
119
|
const wsMsg = {
|
|
195
120
|
wsjtx: {
|
|
196
121
|
type: 'ClearMessage',
|
|
197
122
|
payload: { id: this.wsjtxId, window: 2 },
|
|
198
123
|
},
|
|
199
124
|
};
|
|
200
|
-
this.
|
|
125
|
+
this.messages.txMessage$.next(wsMsg);
|
|
201
126
|
}
|
|
202
127
|
/** Send a command to WSJT-X to replay messages. Useful for a fresh client that wants to hear
|
|
203
128
|
* previous WSJT-X decodes. */
|
|
204
|
-
|
|
129
|
+
replay() {
|
|
205
130
|
const wsMsg = {
|
|
206
131
|
wsjtx: {
|
|
207
132
|
type: 'ReplayMessage',
|
|
208
133
|
payload: { id: this.wsjtxId },
|
|
209
134
|
},
|
|
210
135
|
};
|
|
211
|
-
this.
|
|
136
|
+
this.messages.txMessage$.next(wsMsg);
|
|
212
137
|
}
|
|
213
138
|
/** Send a command to WSJT-X to halt any transmissions immediately. */
|
|
214
|
-
|
|
139
|
+
haltTxNow() {
|
|
215
140
|
const wsMsg = {
|
|
216
141
|
wsjtx: {
|
|
217
142
|
type: 'HaltTxMessage',
|
|
218
143
|
payload: { id: this.wsjtxId, autoTxOnly: false },
|
|
219
144
|
},
|
|
220
145
|
};
|
|
221
|
-
this.
|
|
146
|
+
this.messages.txMessage$.next(wsMsg);
|
|
222
147
|
}
|
|
223
148
|
/** Send a command to WSJT-X to stop auto-transmitting after finishing the current round. */
|
|
224
|
-
|
|
149
|
+
haltTxAfterCurrent() {
|
|
225
150
|
const wsMsg = {
|
|
226
151
|
wsjtx: {
|
|
227
152
|
type: 'HaltTxMessage',
|
|
228
153
|
payload: { id: this.wsjtxId, autoTxOnly: true },
|
|
229
154
|
},
|
|
230
155
|
};
|
|
231
|
-
this.
|
|
156
|
+
this.messages.txMessage$.next(wsMsg);
|
|
232
157
|
}
|
|
233
158
|
/** Send a command to WSJT-X to reply to the given decode. The message must include CQ or QRZ. */
|
|
234
|
-
|
|
159
|
+
reply(decode) {
|
|
235
160
|
const wsMsg = {
|
|
236
161
|
wsjtx: {
|
|
237
162
|
type: 'ReplyMessage',
|
|
@@ -247,10 +172,10 @@ class AgentService {
|
|
|
247
172
|
},
|
|
248
173
|
},
|
|
249
174
|
};
|
|
250
|
-
this.
|
|
175
|
+
this.messages.txMessage$.next(wsMsg);
|
|
251
176
|
}
|
|
252
177
|
/** Send a command to WSJT-X to reply to the given decode. The message must include CQ or QRZ. */
|
|
253
|
-
|
|
178
|
+
highlightCallsign(highlightMsg) {
|
|
254
179
|
highlightMsg.id = this.wsjtxId;
|
|
255
180
|
const wsMsg = {
|
|
256
181
|
wsjtx: {
|
|
@@ -258,32 +183,300 @@ class AgentService {
|
|
|
258
183
|
payload: highlightMsg,
|
|
259
184
|
},
|
|
260
185
|
};
|
|
261
|
-
this.
|
|
186
|
+
this.messages.txMessage$.next(wsMsg);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Send a command to WSJT-X to transmit the given free text. If the text is too long to be
|
|
190
|
+
* encoded in a single message, it may be silently truncated. */
|
|
191
|
+
sendFreeText(freeText) {
|
|
192
|
+
freeText.id = this.wsjtxId;
|
|
193
|
+
const wsMsg = {
|
|
194
|
+
wsjtx: {
|
|
195
|
+
type: 'FreeTextMessage',
|
|
196
|
+
payload: freeText,
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
this.messages.txMessage$.next(wsMsg);
|
|
200
|
+
}
|
|
201
|
+
/** Send a command to WSJT-X to set the local station's Maidenhead grid. This is temporary,
|
|
202
|
+
* lasting only as long as WSJT-X is running. */
|
|
203
|
+
setLocation(grid) {
|
|
204
|
+
const wsMsg = {
|
|
205
|
+
wsjtx: {
|
|
206
|
+
type: 'LocationMessage',
|
|
207
|
+
payload: {
|
|
208
|
+
id: this.wsjtxId,
|
|
209
|
+
location: grid,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
this.messages.txMessage$.next(wsMsg);
|
|
214
|
+
}
|
|
215
|
+
/** Send a command to WSJT-X to switch to the named configuration. */
|
|
216
|
+
switchConfiguration(configName) {
|
|
217
|
+
const wsMsg = {
|
|
218
|
+
wsjtx: {
|
|
219
|
+
type: 'SwitchConfigurationMessage',
|
|
220
|
+
payload: {
|
|
221
|
+
id: this.wsjtxId,
|
|
222
|
+
configurationName: configName,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
this.messages.txMessage$.next(wsMsg);
|
|
227
|
+
}
|
|
228
|
+
/** Send a command to WSJT-X to set the given configuration parameters. */
|
|
229
|
+
configure(config) {
|
|
230
|
+
config.id = this.wsjtxId;
|
|
231
|
+
const wsMsg = {
|
|
232
|
+
wsjtx: {
|
|
233
|
+
type: 'ConfigureMessage',
|
|
234
|
+
payload: config,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
this.messages.txMessage$.next(wsMsg);
|
|
262
238
|
}
|
|
263
239
|
/** Given a decode message, format a string the same way as displayed in the WSJT-X Band
|
|
264
240
|
* Activity/Rx Frequency windows. */
|
|
265
241
|
static formatDecode(msg) {
|
|
266
|
-
const
|
|
242
|
+
const timeStr = this.formatTime(msg.time);
|
|
243
|
+
return `${timeStr} ${msg.snr.toString().padStart(3)} ${msg.deltaTime
|
|
244
|
+
.toFixed(1)
|
|
245
|
+
.padStart(4)} ${msg.deltaFrequency.toString().padStart(4)} ~ ${msg.message}`;
|
|
246
|
+
}
|
|
247
|
+
/** Given a time in milliseconds since midnight UTC, format as HHMMSS. */
|
|
248
|
+
static formatTime(time) {
|
|
249
|
+
const secondsSinceMidnight = Math.floor(time / 1000);
|
|
267
250
|
const hours = Math.floor(secondsSinceMidnight / 3600);
|
|
268
251
|
const secondsSinceHour = secondsSinceMidnight - hours * 3600;
|
|
269
252
|
const minutes = Math.floor(secondsSinceHour / 60);
|
|
270
253
|
const seconds = secondsSinceHour - minutes * 60;
|
|
271
|
-
|
|
254
|
+
return `${hours.toString().padStart(2, '0')}${minutes
|
|
272
255
|
.toString()
|
|
273
256
|
.padStart(2, '0')}${seconds.toString().padStart(2, '0')}`;
|
|
274
|
-
return `${timeStr} ${msg.snr.toString().padStart(3)} ${msg.deltaTime
|
|
275
|
-
.toFixed(1)
|
|
276
|
-
.padStart(4)} ${msg.deltaFrequency.toString().padStart(4)} ~ ${msg.message}`;
|
|
277
257
|
}
|
|
278
258
|
}
|
|
279
|
-
|
|
259
|
+
WsjtxService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: WsjtxService, deps: [{ token: AgentMessageService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
260
|
+
WsjtxService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: WsjtxService, providedIn: 'root' });
|
|
261
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: WsjtxService, decorators: [{
|
|
262
|
+
type: Injectable,
|
|
263
|
+
args: [{
|
|
264
|
+
providedIn: 'root',
|
|
265
|
+
}]
|
|
266
|
+
}], ctorParameters: function () { return [{ type: AgentMessageService }]; } });
|
|
267
|
+
|
|
268
|
+
class HamlibService {
|
|
269
|
+
constructor(messages) {
|
|
270
|
+
this.messages = messages;
|
|
271
|
+
/** Whether we're getting any messages from Hamlib. */
|
|
272
|
+
this.connected$ = new BehaviorSubject(false);
|
|
273
|
+
/** Subject for listening to Hamlib "RigState" messages. */
|
|
274
|
+
this.rigState$ = new BehaviorSubject(null);
|
|
275
|
+
this.setupBehaviors();
|
|
276
|
+
}
|
|
277
|
+
setupBehaviors() {
|
|
278
|
+
this.messages.rxMessage$.subscribe((msg) => this.handleMessage(msg));
|
|
279
|
+
// if we haven't heard from Hamlib in 15 seconds, consider it down
|
|
280
|
+
this.connected$.pipe(debounceTime(15000)).subscribe(() => {
|
|
281
|
+
this.connected$.next(false);
|
|
282
|
+
});
|
|
283
|
+
// When Hamlib goes down, clear its persistent message subjects
|
|
284
|
+
this.connected$.subscribe((isUp) => {
|
|
285
|
+
if (!isUp) {
|
|
286
|
+
this.rigState$.next(null);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
handleMessage(msg) {
|
|
291
|
+
if (!msg.hamlib) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
this.connected$.next(true);
|
|
295
|
+
switch (msg.hamlib.type) {
|
|
296
|
+
case 'RigState':
|
|
297
|
+
this.rigState$.next(msg.hamlib.payload);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
HamlibService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: HamlibService, deps: [{ token: AgentMessageService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
303
|
+
HamlibService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: HamlibService, providedIn: 'root' });
|
|
304
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: HamlibService, decorators: [{
|
|
305
|
+
type: Injectable,
|
|
306
|
+
args: [{
|
|
307
|
+
providedIn: 'root',
|
|
308
|
+
}]
|
|
309
|
+
}], ctorParameters: function () { return [{ type: AgentMessageService }]; } });
|
|
310
|
+
|
|
311
|
+
class AgentService {
|
|
312
|
+
constructor(messages, hamlibService, wsjtxService) {
|
|
313
|
+
this.messages = messages;
|
|
314
|
+
this.hamlibService = hamlibService;
|
|
315
|
+
this.wsjtxService = wsjtxService;
|
|
316
|
+
/** Whether we're connected to the agent. */
|
|
317
|
+
this.connectedState$ = new BehaviorSubject(false);
|
|
318
|
+
this.defaultAgentHost = 'localhost';
|
|
319
|
+
this.defaultAgentPort = 8081;
|
|
320
|
+
this.localStorageHostKey = 'agent-host';
|
|
321
|
+
this.localStoragePortKey = 'agent-port';
|
|
322
|
+
this.agentHost = this.defaultAgentHost;
|
|
323
|
+
this.agentPort = this.defaultAgentPort;
|
|
324
|
+
this.agentWebSocketSubject = null;
|
|
325
|
+
this.agentWebsocketSubscription = null;
|
|
326
|
+
this.hamlibState$ = this.hamlibService.connected$;
|
|
327
|
+
this.hamlibRigState$ = this.hamlibService.rigState$;
|
|
328
|
+
this.wsjtxState$ = this.wsjtxService.connected$;
|
|
329
|
+
this.wsjtxHeartbeat$ = this.wsjtxService.heartbeat$;
|
|
330
|
+
this.wsjtxStatus$ = this.wsjtxService.status$;
|
|
331
|
+
this.wsjtxDecode$ = this.wsjtxService.decode$;
|
|
332
|
+
this.wsjtxClear$ = this.wsjtxService.clear$;
|
|
333
|
+
this.wsjtxQsoLogged$ = this.wsjtxService.qsoLogged$;
|
|
334
|
+
this.wsjtxClose$ = this.wsjtxService.close$;
|
|
335
|
+
this.wsjtxWsprDecode$ = this.wsjtxService.wsprDecode$;
|
|
336
|
+
this.wsjtxLoggedAdif$ = this.wsjtxService.loggedAdif$;
|
|
337
|
+
}
|
|
338
|
+
init() {
|
|
339
|
+
this.messages.txMessage$.subscribe((msg) => this.send(msg));
|
|
340
|
+
this.agentHost = this.getHost();
|
|
341
|
+
this.agentPort = this.getPort();
|
|
342
|
+
this.connect();
|
|
343
|
+
}
|
|
344
|
+
/** Connect (or reconnect) the websocket to the kel-agent server. */
|
|
345
|
+
connect() {
|
|
346
|
+
if (this.agentWebsocketSubscription) {
|
|
347
|
+
this.agentWebsocketSubscription.unsubscribe();
|
|
348
|
+
}
|
|
349
|
+
this.agentHost = this.getHost();
|
|
350
|
+
this.agentPort = this.getPort();
|
|
351
|
+
const protocol = this.agentHost === 'localhost' ? 'ws://' : 'wss://';
|
|
352
|
+
this.agentWebSocketSubject = webSocket({
|
|
353
|
+
url: protocol + this.agentHost + ':' + this.agentPort + '/websocket',
|
|
354
|
+
});
|
|
355
|
+
this.connectedState$.next(true);
|
|
356
|
+
this.agentWebsocketSubscription = this.agentWebSocketSubject
|
|
357
|
+
.pipe(retryWhen((errors) =>
|
|
358
|
+
// retry the websocket connection after 10 seconds
|
|
359
|
+
errors.pipe(tap(() => this.connectedState$.next(false)), delay(10000))))
|
|
360
|
+
.subscribe({
|
|
361
|
+
next: (msg) => {
|
|
362
|
+
this.connectedState$.next(true);
|
|
363
|
+
this.messages.rxMessage$.next(msg);
|
|
364
|
+
},
|
|
365
|
+
error: () => this.connectedState$.next(false),
|
|
366
|
+
complete: () => this.connectedState$.next(false),
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
/** Get the currently configured kel-agent host. */
|
|
370
|
+
getHost() {
|
|
371
|
+
return (localStorage.getItem(this.localStorageHostKey) || this.defaultAgentHost);
|
|
372
|
+
}
|
|
373
|
+
/** Get the currently configured kel-agent port. */
|
|
374
|
+
getPort() {
|
|
375
|
+
let portStr = localStorage.getItem(this.localStoragePortKey);
|
|
376
|
+
if (portStr == null) {
|
|
377
|
+
return this.defaultAgentPort;
|
|
378
|
+
}
|
|
379
|
+
let portNum = parseInt(portStr, 10);
|
|
380
|
+
if (isNaN(portNum)) {
|
|
381
|
+
return this.defaultAgentPort;
|
|
382
|
+
}
|
|
383
|
+
return portNum;
|
|
384
|
+
}
|
|
385
|
+
/** Set the kel-agent host. */
|
|
386
|
+
setHost(host) {
|
|
387
|
+
localStorage.setItem(this.localStorageHostKey, host);
|
|
388
|
+
this.connect();
|
|
389
|
+
}
|
|
390
|
+
/** Set the kel-agent port. */
|
|
391
|
+
setPort(port) {
|
|
392
|
+
localStorage.setItem(this.localStoragePortKey, String(port));
|
|
393
|
+
this.connect();
|
|
394
|
+
}
|
|
395
|
+
send(wsMsg) {
|
|
396
|
+
this.agentWebSocketSubject?.next(wsMsg);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Send a command to WSJT-X to clear the Band Activity window.
|
|
400
|
+
*
|
|
401
|
+
* @deprecated Use {@link WsjtxService.clearBandActivity} instead.
|
|
402
|
+
*/
|
|
403
|
+
sendWsjtxClearBandActivity() {
|
|
404
|
+
this.wsjtxService.clearBandActivity();
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Send a command to WSJT-X to clear the Rx Frequency window.
|
|
408
|
+
*
|
|
409
|
+
* @deprecated Use {@link WsjtxService.clearRxFreqWindow} instead.
|
|
410
|
+
*/
|
|
411
|
+
sendWsjtxClearRxFreqWindow() {
|
|
412
|
+
this.wsjtxService.clearRxFreqWindow();
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Send a command to WSJT-X to clear the Band Activity and Rx Frequency windows.
|
|
416
|
+
*
|
|
417
|
+
* @deprecated Use {@link WsjtxService.clearAll} instead.
|
|
418
|
+
*/
|
|
419
|
+
sendWsjtxClearAll() {
|
|
420
|
+
this.wsjtxService.clearAll();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Send a command to WSJT-X to replay messages. Useful for a fresh client that wants to hear
|
|
424
|
+
* previous WSJT-X decodes.
|
|
425
|
+
*
|
|
426
|
+
* @deprecated Use {@link WsjtxService.replay} instead.
|
|
427
|
+
*/
|
|
428
|
+
sendWsjtxReplay() {
|
|
429
|
+
this.wsjtxService.replay();
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Send a command to WSJT-X to halt any transmissions immediately.
|
|
433
|
+
*
|
|
434
|
+
* @deprecated Use {@link WsjtxService.haltTxNow} instead.
|
|
435
|
+
*/
|
|
436
|
+
sendWsjtxHaltTxNow() {
|
|
437
|
+
this.wsjtxService.haltTxNow();
|
|
438
|
+
}
|
|
439
|
+
/** Send a command to WSJT-X to stop auto-transmitting after finishing the current round.
|
|
440
|
+
*
|
|
441
|
+
* @deprecated Use {@link WsjtxService.haltTxAfterCurrent} instead.
|
|
442
|
+
*/
|
|
443
|
+
sendWsjtxHaltTxAfterCurrent() {
|
|
444
|
+
this.wsjtxService.haltTxAfterCurrent();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Send a command to WSJT-X to reply to the given decode. The message must include CQ or QRZ.
|
|
448
|
+
*
|
|
449
|
+
* @deprecated Use {@link WsjtxService.reply} instead.
|
|
450
|
+
*/
|
|
451
|
+
sendWsjtxReply(decode) {
|
|
452
|
+
this.wsjtxService.reply(decode);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Send a command to WSJT-X to reply to the given decode. The message must include CQ or QRZ.
|
|
456
|
+
*
|
|
457
|
+
* @deprecated Use {@link WsjtxService.highlightCallsign} instead.
|
|
458
|
+
*/
|
|
459
|
+
sendWsjtxHighlightCallsign(highlightMsg) {
|
|
460
|
+
this.wsjtxService.highlightCallsign(highlightMsg);
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Given a decode message, format a string the same way as displayed in the WSJT-X Band
|
|
464
|
+
* Activity/Rx Frequency windows.
|
|
465
|
+
*
|
|
466
|
+
* @deprecated Use {@link WsjtxService.formatDecode} instead.
|
|
467
|
+
*/
|
|
468
|
+
static formatDecode(msg) {
|
|
469
|
+
return WsjtxService.formatDecode(msg);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
AgentService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: AgentService, deps: [{ token: AgentMessageService }, { token: HamlibService }, { token: WsjtxService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
280
473
|
AgentService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: AgentService, providedIn: 'root' });
|
|
281
474
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImport: i0, type: AgentService, decorators: [{
|
|
282
475
|
type: Injectable,
|
|
283
476
|
args: [{
|
|
284
|
-
providedIn: 'root'
|
|
477
|
+
providedIn: 'root',
|
|
285
478
|
}]
|
|
286
|
-
}], ctorParameters: function () { return []; } });
|
|
479
|
+
}], ctorParameters: function () { return [{ type: AgentMessageService }, { type: HamlibService }, { type: WsjtxService }]; } });
|
|
287
480
|
|
|
288
481
|
/*
|
|
289
482
|
* Public API Surface of ngx-kel-agent
|
|
@@ -295,5 +488,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.2", ngImpor
|
|
|
295
488
|
* Generated bundle index. Do not edit.
|
|
296
489
|
*/
|
|
297
490
|
|
|
298
|
-
export { AgentService };
|
|
491
|
+
export { AgentMessageService, AgentService, HamlibService, WsjtxService };
|
|
299
492
|
//# sourceMappingURL=ngx-kel-agent.mjs.map
|