initialize generic rms-software repository

Add the reusable RMS core application (server, web UI, plugins, tests, tools) with generic defaults, GPL licensing, and maintainer context documentation so deployments can consume this repo as software source independent of station-specific overlays.
This commit is contained in:
2026-03-16 03:31:08 +01:00
commit e1a4ce0b8b
58 changed files with 20611 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
async function createPlugin(ctx) {
let lastAzimuth = Number(ctx.getSetting("defaultAzimuth", ctx.env.ROTOR_DEFAULT_AZIMUTH || 0));
const simulate = typeof ctx.simulateHardware === "boolean"
? ctx.simulateHardware
: (process.platform !== "linux" && String(ctx.env.ALLOW_NON_LINUX_CMDS || "false") !== "true");
return {
async execute(action, input) {
if (action === "getAzimuth") {
return getRotorStatus(ctx, simulate, () => lastAzimuth, (value) => {
lastAzimuth = value;
});
}
if (action !== "setAzimuth") {
throw new Error(`Unknown action: ${action}`);
}
const target = Number(input && input.target);
if (!Number.isFinite(target) || target < 0 || target > 360) {
throw new Error("target muss zwischen 0 und 360 sein");
}
const template = String(ctx.getSetting("setTemplate", ctx.env.ROTOR_SET_CMD_TEMPLATE || "")).trim();
if (template) {
if (simulate) {
lastAzimuth = target;
return {
ok: true,
skipped: true,
message: `Rotor auf ${target} Grad simuliert (${ctx.execMode || "dev"})`
};
}
const command = template.replace(/\{az\}/g, String(target));
const result = await ctx.commandRunner(command, {
timeoutMs: Number(ctx.env.ROTOR_SET_TIMEOUT_MS || 20000)
});
if (!result.ok) {
throw new Error(result.stderr || result.error || "Rotor set failed");
}
}
lastAzimuth = target;
ctx.emit("ui.control.state.changed", {
controlId: "rotor-main",
data: { azimuth: lastAzimuth, moving: false }
});
return {
ok: true,
message: `Rotor auf ${target} Grad gesetzt`
};
},
async getStatus() {
return getRotorStatus(ctx, simulate, () => lastAzimuth, (value) => {
lastAzimuth = value;
});
},
async health() {
return { ok: true };
}
};
}
async function getRotorStatus(ctx, simulate, getLastAzimuth, setLastAzimuth) {
const getCmd = String(ctx.getSetting("getCommand", ctx.env.ROTOR_GET_CMD || "")).trim();
if (getCmd) {
if (simulate) {
return {
azimuth: getLastAzimuth(),
moving: false,
min: 0,
max: 360
};
}
const result = await ctx.commandRunner(getCmd, {
timeoutMs: Number(ctx.env.ROTOR_GET_TIMEOUT_MS || 10000)
});
if (result.ok) {
const parsed = Number(String(result.stdout).split(/\s+/)[0]);
if (Number.isFinite(parsed)) {
setLastAzimuth(parsed < 0 ? parsed + 360 : parsed);
}
}
}
return {
azimuth: getLastAzimuth(),
moving: false,
min: 0,
max: 360
};
}
module.exports = {
createPlugin
};

View File

@@ -0,0 +1,51 @@
{
"id": "rms.rotor.hamlib",
"name": "Rotor Hamlib",
"version": "1.0.0",
"apiVersion": "1.0",
"capabilities": [
"rotor.read",
"rotor.set"
],
"settingsSchema": {
"type": "object",
"properties": {
"setTemplate": { "type": "string" },
"getCommand": { "type": "string" },
"defaultAzimuth": { "type": "number", "minimum": 0, "maximum": 360 }
},
"additionalProperties": false
},
"uiControls": [
{
"controlId": "rotor-main",
"controlType": "rotor.azimuth",
"title": "Rotorsteuerung",
"capability": "rotor.set",
"actions": [
{
"name": "setAzimuth",
"inputSchema": {
"type": "object",
"properties": {
"target": {
"type": "number",
"minimum": 0,
"maximum": 360
}
},
"required": [
"target"
],
"additionalProperties": false
}
}
],
"viewSchema": {
"showCompass": true,
"presets": [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330],
"stepButtons": [1, 5, 10]
}
}
]
}