Skip to content

Commit ee01855

Browse files
committed
Added custom_command to e.g. send "set/skimmmer" after login
1 parent 7abb96c commit ee01855

4 files changed

Lines changed: 36 additions & 18 deletions

File tree

‎.env.sample‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,20 @@ SPOT_MAX_AGE=120
8181
CLUSTER_ENABLED=true
8282

8383
# Multiple DX Cluster connections (JSON array format)
84-
# Each cluster should have: host, port, loginPrompt, call, password, cluster (name)
84+
# Each cluster should have: host, port, loginPrompt, call, password, cluster (name), customCommand (optional)
8585
# Recommended clusters:
8686
# - DXSPOTS: Reliable, fast, worldwide coverage
8787
# - NC7J: Popular, good availability
8888
# Replace YOUR_CALLSIGN with your amateur radio callsign
89+
# customCommand: Optional command sent after login (e.g., "set/skimmer" for skimmer mode)
8990
CLUSTERS=[{"host":"dxspots.com","port":7300,"loginPrompt":"login:","call":"YOUR_CALLSIGN","password":"","cluster":"DXSPOTS"},{"host":"dxc.nc7j.com","port":7300,"loginPrompt":"login:","call":"YOUR_CALLSIGN","password":"","cluster":"NC7J"}]
9091

9192
# Single cluster example (simpler format):
9293
# CLUSTERS=[{"host":"dxspots.com","port":7300,"loginPrompt":"login:","call":"YOUR_CALLSIGN","password":"","cluster":"DXSPOTS"}]
9394

95+
# Cluster with custom command example:
96+
# CLUSTERS=[{"host":"dxspots.com","port":7300,"loginPrompt":"login:","call":"YOUR_CALLSIGN","password":"","cluster":"DXSPOTS","customCommand":"set/skimmer"}]
97+
9498
# ================================================================
9599
# SPOT SOURCES - External Data Integration
96100
# ================================================================

‎lib/dxcluster.js‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = class DXCluster extends events.EventEmitter {
2121
awaiting_login: false,
2222
awaiting_password: false,
2323
}
24+
this.customCommand = null
2425
this.regex = {
2526
deline: /^(DX de) +([A-Z0-9/\-#]{3,}):? *(\d*.\d{1,2}) *([A-Z0-9/\-#]{3,}) +(.*\S)? +(\d{4}){1}Z *(\w{2}\d{2})?/g,
2627
callsign: /(((\d|[A-Z])+\/){0,2}((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?){1,1}/g,
@@ -40,6 +41,9 @@ module.exports = class DXCluster extends events.EventEmitter {
4041
return;
4142
}
4243

44+
// Store custom command from connection options
45+
this.customCommand = opts.customCommand || null
46+
4347
this.host = opts.host || '127.0.0.1'
4448
this.port = opts.port || 23
4549

@@ -94,21 +98,29 @@ module.exports = class DXCluster extends events.EventEmitter {
9498
})
9599

96100
this.socket.on('data', (data) => {
101+
const dataStr = data.toString('utf8')
102+
97103
if(this.status.awaiting_login) {
98-
if(data.toString('utf8').indexOf(loginPrompt) != -1) {
104+
if(dataStr.indexOf(loginPrompt) != -1) {
99105
if(this.write(call)) {
100106
this.status.awaiting_login = false
107+
// Execute custom command after login if no password required
108+
if (!opts.password) {
109+
this._executeCustomCommand()
110+
}
101111
}
102112
}
103113
}
104114
if(this.status.awaiting_password) {
105-
if(data.toString('utf8').indexOf(passPrompt) != -1) {
115+
if(dataStr.indexOf(passPrompt) != -1) {
106116
if(this.write(opts.password)) {
107117
this.status.awaiting_password = false
118+
// Execute custom command after password if configured
119+
this._executeCustomCommand()
108120
}
109121
}
110122
}
111-
this._parseDX(data.toString('utf8'))
123+
this._parseDX(dataStr)
112124
})
113125

114126
this.socket.on('close', (err) => {
@@ -146,6 +158,17 @@ module.exports = class DXCluster extends events.EventEmitter {
146158
return this.socket.write(str + this.ct)
147159
}
148160

161+
/**
162+
* Execute custom command after authentication is complete
163+
* @private
164+
*/
165+
_executeCustomCommand() {
166+
if (this.customCommand) {
167+
this.write(this.customCommand)
168+
this.emit('custom_command_sent', this.customCommand)
169+
}
170+
}
171+
149172
/**
150173
* Internal method to cleanup all resources and prevent memory leaks
151174
* Called during connection failures, timeouts, and shutdown

‎lib/utils.js‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,6 @@ function toKHz(freqMHz) {
164164
return NaN;
165165
}
166166

167-
/**
168-
* Converts frequency from kHz to MHz.
169-
* @param {number} freqKHz - Frequency in kHz.
170-
* @returns {number} - Frequency in MHz, or NaN if invalid.
171-
*/
172-
function toMHz(freqKHz) {
173-
const khz = Number(freqKHz);
174-
if (Number.isFinite(khz)) {
175-
return khz / 1000; // Convert to MHz
176-
}
177-
return NaN;
178-
}
179-
180167
/**
181168
* Normalizes frequency to consistent kHz format with 1 decimal place.
182169
* This ensures frequency consistency across all sources (DXCluster, POTA, SOTA, RBN).
@@ -228,6 +215,5 @@ module.exports = {
228215
sleepNow,
229216
getAllowedDeviation,
230217
toKHz,
231-
toMHz,
232218
normalizeFrequency
233219
};

‎modules/clusters/index.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ class Clusters extends EventEmitter {
154154
this.emit('spot', spot, cluster.cluster || 'cluster');
155155
});
156156

157+
// Monitor custom command execution (cleanup handled by _cleanupConnection)
158+
conn.on('custom_command_sent', (command) => {
159+
logConnectionState('info', cluster.host, `Custom command sent: "${command}"`);
160+
});
161+
157162
this.connections.push({ cluster, conn, clusterKey });
158163
} catch (e) {
159164
logConnectionState('error', cluster.host, 'DXCluster not reachable', e);

0 commit comments

Comments
 (0)