246 lines
9.6 KiB
JavaScript
246 lines
9.6 KiB
JavaScript
'use strict';
|
|
const { load, DEFAULTS } = require('../robot/RobotConfig');
|
|
|
|
function makeFs(content) {
|
|
return {
|
|
readFileSync: jest.fn(() => content)
|
|
};
|
|
}
|
|
|
|
function makeFailFs() {
|
|
return {
|
|
readFileSync: jest.fn(() => { throw new Error('ENOENT'); })
|
|
};
|
|
}
|
|
|
|
const log = { warn: jest.fn(), log: jest.fn(), error: jest.fn() };
|
|
|
|
const FULL_ROBOT_JSON = {
|
|
kinematics: { type: 'arm3segmentlinearx' },
|
|
motion: { defaultFeedrate: 2300, speedMode: 'legacy', speedModeOptions: ['legacy', 'correct'] },
|
|
controllers: {
|
|
base: { ip: 'fluidNcBase.local', port: 2300, protocol: 'telnet', axes: ['x', 'y', 'z'] },
|
|
elbow: { ip: 'fluidNcEllbow.local', port: 5000, protocol: 'telnet', axes: ['a', null, null] },
|
|
hand: { ip: 'fluidNcHand.local', port: 5000, protocol: 'telnet', axes: ['c', 'e', 'b'] }
|
|
},
|
|
links: {
|
|
Arm1: { skeleton: { from: [0,0,0], to: [0,-250,0] } },
|
|
Arm2: { skeleton: { from: [0,0,0], to: [0,-250,0] } },
|
|
Ellbow: { skeleton: { from: [0,0,0], to: [90,0,0] } }
|
|
}
|
|
};
|
|
|
|
describe('RobotConfig.load — Vollständige robot.json', () => {
|
|
let cfg;
|
|
beforeEach(() => {
|
|
cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), {}, log);
|
|
});
|
|
|
|
test('kinematics.type aus robot.json', () => {
|
|
expect(cfg.kinematics.type).toBe('arm3segmentlinearx');
|
|
});
|
|
|
|
test('l1/l2 aus links.Arm1/Arm2.skeleton.to[1] (Betrag)', () => {
|
|
expect(cfg.kinematics.l1).toBe(250);
|
|
expect(cfg.kinematics.l2).toBe(250);
|
|
});
|
|
|
|
test('l3 aus links.Ellbow.skeleton.to[0]', () => {
|
|
expect(cfg.kinematics.l3).toBe(90);
|
|
});
|
|
|
|
test('motion.defaultFeedrate aus robot.json', () => {
|
|
expect(cfg.motion.defaultFeedrate).toBe(2300);
|
|
});
|
|
|
|
test('motion.speedMode aus robot.json', () => {
|
|
expect(cfg.motion.speedMode).toBe('legacy');
|
|
});
|
|
|
|
test('motion.useSpeedCalc false für legacy', () => {
|
|
expect(cfg.motion.useSpeedCalc).toBe(false);
|
|
});
|
|
|
|
test('controllers enthält alle drei Endpunkte', () => {
|
|
expect(cfg.controllers.base.ip).toBe('fluidNcBase.local');
|
|
expect(cfg.controllers.base.port).toBe(2300);
|
|
expect(cfg.controllers.base.protocol).toBe('telnet');
|
|
expect(cfg.controllers.elbow.port).toBe(5000);
|
|
expect(cfg.controllers.hand.ip).toBe('fluidNcHand.local');
|
|
});
|
|
|
|
test('heartbeatInterval Default wenn nicht in robot.json', () => {
|
|
expect(cfg.controllers.base.heartbeatInterval).toBe(DEFAULTS.controllers.base.heartbeatInterval);
|
|
expect(cfg.controllers.elbow.heartbeatInterval).toBe(DEFAULTS.controllers.elbow.heartbeatInterval);
|
|
expect(cfg.controllers.hand.heartbeatInterval).toBe(DEFAULTS.controllers.hand.heartbeatInterval);
|
|
});
|
|
|
|
test('axesByController gibt korrektes Array zurück', () => {
|
|
expect(cfg.axesByController('base')).toEqual(['x', 'y', 'z']);
|
|
expect(cfg.axesByController('elbow')).toEqual(['a', null, null]);
|
|
expect(cfg.axesByController('hand')).toEqual(['c', 'e', 'b']);
|
|
});
|
|
|
|
test('axesByController gibt [] für unbekannten Key zurück', () => {
|
|
expect(cfg.axesByController('unknown')).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('RobotConfig.load — Fehlerbehandlung', () => {
|
|
test('fehlende robot.json → Defaults', () => {
|
|
const cfg = load(makeFailFs(), {}, log);
|
|
expect(cfg.kinematics.l1).toBe(DEFAULTS.kinematics.l1);
|
|
expect(cfg.motion.defaultFeedrate).toBe(DEFAULTS.motion.defaultFeedrate);
|
|
expect(cfg.controllers.base.ip).toBe(DEFAULTS.controllers.base.ip);
|
|
});
|
|
|
|
test('ungültiges JSON → Defaults', () => {
|
|
const cfg = load(makeFs('{ not valid json }'), {}, log);
|
|
expect(cfg.kinematics.type).toBe(DEFAULTS.kinematics.type);
|
|
});
|
|
|
|
test('fehlende links → kinematics-Defaults', () => {
|
|
const json = { ...FULL_ROBOT_JSON };
|
|
delete json.links;
|
|
const cfg = load(makeFs(JSON.stringify(json)), {}, log);
|
|
expect(cfg.kinematics.l1).toBe(DEFAULTS.kinematics.l1);
|
|
expect(cfg.kinematics.l3).toBe(DEFAULTS.kinematics.l3);
|
|
});
|
|
});
|
|
|
|
describe('RobotConfig.load — Env-Override', () => {
|
|
test('ROBOT_DEFAULT_FEEDRATE überschreibt robot.json', () => {
|
|
const env = { ROBOT_DEFAULT_FEEDRATE: '5000' };
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), env, log);
|
|
expect(cfg.motion.defaultFeedrate).toBe(5000);
|
|
});
|
|
|
|
test('ROBOT_SPEED_MODE=correct setzt useSpeedCalc=true', () => {
|
|
const env = { ROBOT_SPEED_MODE: 'correct' };
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), env, log);
|
|
expect(cfg.motion.speedMode).toBe('correct');
|
|
expect(cfg.motion.useSpeedCalc).toBe(true);
|
|
});
|
|
|
|
test('GRBL_BASE_IP überschreibt controller.base.ip', () => {
|
|
const env = { GRBL_BASE_IP: '192.168.1.10' };
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), env, log);
|
|
expect(cfg.controllers.base.ip).toBe('192.168.1.10');
|
|
});
|
|
|
|
test('GRBL_ELLBOW_IP überschreibt controller.elbow.ip', () => {
|
|
const env = { GRBL_ELLBOW_IP: '192.168.1.11' };
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), env, log);
|
|
expect(cfg.controllers.elbow.ip).toBe('192.168.1.11');
|
|
});
|
|
|
|
test('GRBL_HAND_IP überschreibt controller.hand.ip', () => {
|
|
const env = { GRBL_HAND_IP: '192.168.1.12' };
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), env, log);
|
|
expect(cfg.controllers.hand.ip).toBe('192.168.1.12');
|
|
});
|
|
});
|
|
|
|
describe('RobotConfig.load — speedMode correct', () => {
|
|
test('speedMode correct aus robot.json setzt useSpeedCalc=true', () => {
|
|
const json = { ...FULL_ROBOT_JSON, motion: { ...FULL_ROBOT_JSON.motion, speedMode: 'correct' } };
|
|
const cfg = load(makeFs(JSON.stringify(json)), {}, log);
|
|
expect(cfg.motion.useSpeedCalc).toBe(true);
|
|
});
|
|
|
|
test('ROBOT_USE_SPEED_CALC=true setzt useSpeedCalc', () => {
|
|
const env = { ROBOT_USE_SPEED_CALC: 'true' };
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), env, log);
|
|
expect(cfg.motion.useSpeedCalc).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('RobotConfig.load — heartbeatInterval', () => {
|
|
test('heartbeatInterval aus robot.json überschreibt Default', () => {
|
|
const json = {
|
|
...FULL_ROBOT_JSON,
|
|
controllers: {
|
|
...FULL_ROBOT_JSON.controllers,
|
|
base: { ...FULL_ROBOT_JSON.controllers.base, heartbeatInterval: 5000 },
|
|
elbow: { ...FULL_ROBOT_JSON.controllers.elbow, heartbeatInterval: 30000 },
|
|
}
|
|
};
|
|
const cfg = load(makeFs(JSON.stringify(json)), {}, log);
|
|
expect(cfg.controllers.base.heartbeatInterval).toBe(5000);
|
|
expect(cfg.controllers.elbow.heartbeatInterval).toBe(30000);
|
|
// hand nicht gesetzt → Default
|
|
expect(cfg.controllers.hand.heartbeatInterval).toBe(DEFAULTS.controllers.hand.heartbeatInterval);
|
|
});
|
|
|
|
test('fehlende heartbeatInterval → Default (10 000 ms)', () => {
|
|
const cfg = load(makeFailFs(), {}, log);
|
|
expect(cfg.controllers.base.heartbeatInterval).toBe(10000);
|
|
});
|
|
});
|
|
|
|
describe('RobotConfig.load — emergencyStop (Shelly)', () => {
|
|
test('DEFAULTS.controllers.emergencyStop hat protocol=shelly und url=null', () => {
|
|
expect(DEFAULTS.controllers.emergencyStop.protocol).toBe('shelly');
|
|
expect(DEFAULTS.controllers.emergencyStop.url).toBeNull();
|
|
expect(DEFAULTS.controllers.emergencyStop.urlOn).toBeNull();
|
|
expect(DEFAULTS.controllers.emergencyStop.urlStatus).toBeNull();
|
|
});
|
|
|
|
test('emergencyStop.url aus robot.json wird übernommen', () => {
|
|
const shellyUrl = 'http://shelly.local/rpc/Switch.Set?id=0&on=false';
|
|
const json = {
|
|
...FULL_ROBOT_JSON,
|
|
controllers: {
|
|
...FULL_ROBOT_JSON.controllers,
|
|
emergencyStop: { protocol: 'shelly', url: shellyUrl }
|
|
}
|
|
};
|
|
const cfg = load(makeFs(JSON.stringify(json)), {}, log);
|
|
expect(cfg.controllers.emergencyStop.protocol).toBe('shelly');
|
|
expect(cfg.controllers.emergencyStop.url).toBe(shellyUrl);
|
|
});
|
|
|
|
test('emergencyStop.urlOn + urlStatus aus robot.json werden übernommen (IP statt .local)', () => {
|
|
const base = 'http://192.168.0.99';
|
|
const json = {
|
|
...FULL_ROBOT_JSON,
|
|
controllers: {
|
|
...FULL_ROBOT_JSON.controllers,
|
|
emergencyStop: {
|
|
protocol: 'shelly',
|
|
url: `${base}/rpc/Switch.Set?id=0&on=false`,
|
|
urlOn: `${base}/rpc/Switch.Set?id=0&on=true`,
|
|
urlStatus: `${base}/rpc/Switch.GetStatus?id=0`,
|
|
}
|
|
}
|
|
};
|
|
const cfg = load(makeFs(JSON.stringify(json)), {}, log);
|
|
expect(cfg.controllers.emergencyStop.url).toBe(`${base}/rpc/Switch.Set?id=0&on=false`);
|
|
expect(cfg.controllers.emergencyStop.urlOn).toBe(`${base}/rpc/Switch.Set?id=0&on=true`);
|
|
expect(cfg.controllers.emergencyStop.urlStatus).toBe(`${base}/rpc/Switch.GetStatus?id=0`);
|
|
});
|
|
|
|
test('SHELLY_URL Env-Variable überschreibt url aus robot.json', () => {
|
|
const envUrl = 'http://192.168.0.99/rpc/Switch.Set?id=0&on=false';
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), { SHELLY_URL: envUrl }, log);
|
|
expect(cfg.controllers.emergencyStop.url).toBe(envUrl);
|
|
});
|
|
|
|
test('fehlendes emergencyStop in robot.json → url=null (Default)', () => {
|
|
// FULL_ROBOT_JSON hat kein emergencyStop → fällt auf Default zurück
|
|
const cfg = load(makeFs(JSON.stringify(FULL_ROBOT_JSON)), {}, log);
|
|
expect(cfg.controllers.emergencyStop.protocol).toBe('shelly');
|
|
expect(cfg.controllers.emergencyStop.url).toBeNull();
|
|
expect(cfg.controllers.emergencyStop.urlOn).toBeNull();
|
|
expect(cfg.controllers.emergencyStop.urlStatus).toBeNull();
|
|
});
|
|
|
|
test('emergencyStop hat keine ip/port/axes/heartbeatInterval Felder', () => {
|
|
const cfg = load(makeFailFs(), {}, log);
|
|
expect(cfg.controllers.emergencyStop.ip).toBeUndefined();
|
|
expect(cfg.controllers.emergencyStop.port).toBeUndefined();
|
|
expect(cfg.controllers.emergencyStop.axes).toBeUndefined();
|
|
expect(cfg.controllers.emergencyStop.heartbeatInterval).toBeUndefined();
|
|
});
|
|
});
|