initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
73
RotClient.js
Normal file
73
RotClient.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Implementation of a simple client for simple_rotator_interface_v -- https://remoteqth.com/w/doku.php?id=simple_rotator_interface_v
|
||||||
|
*/
|
||||||
|
export default class RotClient {
|
||||||
|
constructor(label, clientConfig) {
|
||||||
|
this.label = label;
|
||||||
|
this.ip = clientConfig.ip;
|
||||||
|
this.bands = clientConfig.bands;
|
||||||
|
this.startOffset = null;
|
||||||
|
|
||||||
|
setInterval(async () => {
|
||||||
|
console.log(`${this.label}: azi ${await this.readAzi()}, adc ${await this.readAdc()}, status ${await this.readStatus()}`);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasBand(band) {
|
||||||
|
return this.bands.includes(band);
|
||||||
|
}
|
||||||
|
|
||||||
|
turn(az) {
|
||||||
|
fetch(`http://${this.ip}:88/`, {
|
||||||
|
"body": "ROT=" + az,
|
||||||
|
"method": "POST",
|
||||||
|
}).catch(() => {});
|
||||||
|
console.log(`Turning ${this.label} to ${az}°...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readAzi() {
|
||||||
|
try {
|
||||||
|
if (this.startOffset === null) {
|
||||||
|
this.startOffset = await this.readOffset();
|
||||||
|
console.log(`${this.label} Set the initial offset to ${this.startOffset}.`);
|
||||||
|
}
|
||||||
|
const resp = await fetch(`http://${this.ip}:88/readAZ`);
|
||||||
|
return this.startOffset + parseInt(await resp.text(), 10);
|
||||||
|
} catch(ex) {
|
||||||
|
console.error(`${this.label}: Failed to read azimuth:`, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async readAdc() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`http://${this.ip}:88/readADC`);
|
||||||
|
return parseFloat(await resp.text());
|
||||||
|
} catch(ex) {
|
||||||
|
console.error(`${this.label}: Failed to read ADC:`, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async readStatus() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`http://${this.ip}:88/readStat`);
|
||||||
|
return parseInt(await resp.text(), 10);
|
||||||
|
} catch(ex) {
|
||||||
|
console.error(`${this.label}: Failed to read status:`, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async readOffset() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`http://${this.ip}:88/readStart`);
|
||||||
|
return parseInt(await resp.text(), 10);
|
||||||
|
} catch(ex) {
|
||||||
|
console.error(`${this.label}: Failed to read the initial offset:`, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
45
RotRouter.js
Normal file
45
RotRouter.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import RotClient from './RotClient.js';
|
||||||
|
import { XMLParser } from 'fast-xml-parser';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
export default class RotRouter {
|
||||||
|
constructor() {
|
||||||
|
this.clients = [];
|
||||||
|
this.loadConfig();
|
||||||
|
|
||||||
|
this.xmlParser = new XMLParser();
|
||||||
|
}
|
||||||
|
|
||||||
|
processN1mmXml(xml) {
|
||||||
|
try {
|
||||||
|
const msg = this.xmlParser.parse(xml);
|
||||||
|
const band = parseInt(msg?.N1MMRotor?.freqband.split(',')[0] || 0, 10);
|
||||||
|
const az = parseInt(msg?.N1MMRotor?.goazi.split(',')[0] || 0, 10);
|
||||||
|
if (!band || !az) {
|
||||||
|
console.error(`Missing band or azimuth`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const client = this.findClientForBand(band);
|
||||||
|
if (!client) {
|
||||||
|
console.error(`No RotClient found for the ${band} band!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.turn(az);
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(`Failed to parse: ${xml}`, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig() {
|
||||||
|
const raw = fs.readFileSync("config.json", 'utf-8');
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
for (const [label, clientConfig] of Object.entries(parsed)) {
|
||||||
|
this.clients[label] = new RotClient(label, clientConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findClientForBand(band) {
|
||||||
|
return Object.values(this.clients).find(c => c.hasBand(band)) || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
config.json
Normal file
8
config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"rot14": {
|
||||||
|
"ip": "192.168.0.82",
|
||||||
|
"bands": [
|
||||||
|
14
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
26
index.js
Normal file
26
index.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import dgram from 'node:dgram';
|
||||||
|
import RotRouter from './RotRouter.js';
|
||||||
|
|
||||||
|
const server = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
server.on('error', (err) => {
|
||||||
|
console.error(`server error:\n${err.stack}`);
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
server.on('listening', () => {
|
||||||
|
const address = server.address();
|
||||||
|
console.log(`server listening ${address.address}:${address.port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const router = new RotRouter();
|
||||||
|
|
||||||
|
server.on('message', (msg, rinfo) => {
|
||||||
|
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
|
||||||
|
router.processN1mmXml(msg);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
server.bind(12040);
|
||||||
46
package-lock.json
generated
Normal file
46
package-lock.json
generated
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "rotrouter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "rotrouter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-xml-parser": "^5.3.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-xml-parser": {
|
||||||
|
"version": "5.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.5.tgz",
|
||||||
|
"integrity": "sha512-JeaA2Vm9ffQKp9VjvfzObuMCjUYAp5WDYhRYL5LrBPY/jUDlUtOvDfot0vKSkB9tuX885BDHjtw4fZadD95wnA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"strnum": "^2.1.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"fxparser": "src/cli/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strnum": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "rotrouter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Route rotator requests to the proper rotator controller",
|
||||||
|
"license": "ISC",
|
||||||
|
"author": "ericek111",
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"fast-xml-parser": "^5.3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user