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