Files
appRobotDriver/robot/RobotConfig.js
2026-06-26 12:28:40 +02:00

132 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const ROBOT_JSON_PATH = 'data/robot/robot.json';
// Backward-compat env-var names für Controller-IPs (werden in Env als GRBL_ELLBOW_IP gespeichert)
const ENV_IP_MAP = {
base: 'GRBL_BASE_IP',
elbow: 'GRBL_ELLBOW_IP',
hand: 'GRBL_HAND_IP'
};
const DEFAULTS = {
kinematics: { type: 'arm3segmentlinearx', l1: 250, l2: 264, l3: 100 },
motion: { defaultFeedrate: 1000, speedMode: 'legacy', useSpeedCalc: false },
controllers: {
base: { ip: 'fluidNcBase.local', port: 2300, protocol: 'telnet', axes: ['x', 'y', 'z'], heartbeatInterval: 10000 },
elbow: { ip: 'fluidNcEllbow.local', port: 5000, protocol: 'telnet', axes: ['a', null, null], heartbeatInterval: 10000 },
hand: { ip: 'fluidNcHand.local', port: 5000, protocol: 'telnet', axes: ['c', 'e', 'b'], heartbeatInterval: 10000 },
// Shelly Smart Plug: schaltet Strom für Emergency Stop.
// url/urlOn/urlStatus: null → deaktiviert, wenn nicht in robot.json konfiguriert.
emergencyStop: { protocol: 'shelly', url: null, urlOn: null, urlStatus: null }
}
};
function deriveKinematicParams(links) {
if (!links) return {};
const result = {};
const l1raw = links.Arm1?.skeleton?.to?.[1];
const l2raw = links.Arm2?.skeleton?.to?.[1];
if (l1raw != null) result.l1 = Math.abs(l1raw);
if (l2raw != null) result.l2 = Math.abs(l2raw);
// l3 = Endeffektor-Länge (Handgelenk → Fingerspitze) = Hand-Segment + Finger.
// FRÜHER fälschlich aus Ellbow.skeleton.to[0] abgeleitet — das ist der seitliche
// Ellbogen-Versatz, nicht die Hand/Finger-Länge (siehe doc/Info_Koordinaten.md, Phase 2).
const handLen = links.Hand?.skeleton?.to?.[1]; // Hand-Segment (z. B. -35)
const fingerLen = links.FingerA?.skeleton?.to?.[1]; // Finger (z. B. -60)
if (handLen != null || fingerLen != null) {
result.l3 = Math.abs(handLen ?? 0) + Math.abs(fingerLen ?? 0);
}
return result;
}
/**
* Liest robot.json synchron und gibt einen typisierten Config-Record zurück.
* Env-Variablen haben Vorrang vor robot.json (nützlich für Tests und Notfall-Overrides).
*
* @param {Object} [fsModule] - Abhängigkeit (Default: require('fs'))
* @param {Object} [processEnv] - Abhängigkeit (Default: process.env)
* @param {Object} [consoleObj] - Abhängigkeit (Default: console)
* @returns {{ kinematics, motion, controllers, axesByController }}
*/
function load(fsModule, processEnv, consoleObj) {
const fs_ = fsModule ?? require('fs');
const env_ = processEnv ?? process.env;
const log_ = consoleObj ?? console;
let json = null;
try {
const raw = fs_.readFileSync(ROBOT_JSON_PATH, 'utf8');
json = JSON.parse(raw);
} catch {
log_.warn('[RobotConfig] data/robot/robot.json nicht lesbar — nutze Defaults');
}
// Kinematik-Typ und Armlängen (aus links abgeleitet)
const linkParams = deriveKinematicParams(json?.links);
// Explizite kinematics.l1/l2/l3 in robot.json haben Vorrang vor der links-Ableitung
// (zum Kalibrieren der realen Längen, z. B. l3 an die gemessene Reichweite anpassen).
const kinematics = {
type: json?.kinematics?.type ?? DEFAULTS.kinematics.type,
l1: json?.kinematics?.l1 ?? linkParams.l1 ?? DEFAULTS.kinematics.l1,
l2: json?.kinematics?.l2 ?? linkParams.l2 ?? DEFAULTS.kinematics.l2,
l3: json?.kinematics?.l3 ?? linkParams.l3 ?? DEFAULTS.kinematics.l3
};
// Bewegungs-Defaults — Env hat Vorrang, dann robot.json, dann Hard-Default
const jsonMotion = json?.motion ?? {};
const rawFeedrate = env_.ROBOT_DEFAULT_FEEDRATE;
const rawMode = env_.ROBOT_SPEED_MODE;
const rawCalc = env_.ROBOT_USE_SPEED_CALC;
const speedMode = (rawMode || jsonMotion.speedMode || DEFAULTS.motion.speedMode).toLowerCase();
const motion = {
defaultFeedrate: rawFeedrate
? Number(rawFeedrate)
: (jsonMotion.defaultFeedrate ?? DEFAULTS.motion.defaultFeedrate),
speedMode,
useSpeedCalc: speedMode === 'correct' || rawCalc === 'true' || rawCalc === '1'
};
// Controller-Endpunkte — robot.json überschreibt Defaults, Env überschreibt IPs
const jsonControllers = json?.controllers ?? {};
const controllers = {};
for (const key of Object.keys(DEFAULTS.controllers)) {
const def = DEFAULTS.controllers[key];
const cfg = jsonControllers[key] ?? {};
if (def.protocol === 'shelly') {
// Shelly Smart Plug: protocol + drei URLs (off / on / status).
// SHELLY_URL überschreibt url aus robot.json (analog zu GRBL_BASE_IP).
// urlOn/urlStatus: explizit konfigurierbar; fehlen sie, leitet ShellyEmergencyStop
// sie aus url ab (url.replace('on=false','on=true') bzw. /rpc/Switch.GetStatus).
const envUrl = env_.SHELLY_URL;
controllers[key] = {
protocol: cfg.protocol ?? def.protocol,
url: envUrl ?? cfg.url ?? def.url,
urlOn: cfg.urlOn ?? def.urlOn,
urlStatus: cfg.urlStatus ?? def.urlStatus,
};
} else {
// Telnet (FluidNC): IP + Port + Achsen + Heartbeat
const envIpKey = ENV_IP_MAP[key];
controllers[key] = {
ip: env_[envIpKey] ?? cfg.ip ?? def.ip,
port: cfg.port ?? def.port,
protocol: cfg.protocol ?? def.protocol,
axes: cfg.axes ?? def.axes,
// Heartbeat-Intervall in ms: wie oft '?' gesendet wird.
// deadTimeout = 2 × heartbeatInterval (zwei verpasste Heartbeats → Verbindung tot).
heartbeatInterval: cfg.heartbeatInterval ?? def.heartbeatInterval,
};
}
}
function axesByController(key) {
return controllers[key]?.axes ?? [];
}
return { kinematics, motion, controllers, axesByController };
}
module.exports = { load, DEFAULTS };