132 lines
5.7 KiB
JavaScript
132 lines
5.7 KiB
JavaScript
'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 };
|