"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MotionMasterClient = exports.createMotionMasterClient = void 0;
const tslib_1 = require("tslib");
const rxjs_1 = require("rxjs");
const uuid_1 = require("uuid");
const motion_master_pub_sub_client_1 = require("./motion-master-pub-sub-client");
const motion_master_pub_sub_web_socket_1 = require("./motion-master-pub-sub-web-socket");
const motion_master_req_res_client_1 = require("./motion-master-req-res-client");
const motion_master_req_res_web_socket_1 = require("./motion-master-req-res-web-socket");
const operators_1 = require("./operators");
const types_1 = require("./types");
const cia402_1 = require("./cia402");
const util_1 = require("./util");
const data_monitoring_1 = require("./data-monitoring");
const homing_1 = require("./homing");
function createMotionMasterClient(configOrHostname, pingSystemInterval = 250, systemAliveTimeout = 1000) {
    var _a, _b, _c, _d, _e, _f;
    configOrHostname = configOrHostname !== null && configOrHostname !== void 0 ? configOrHostname : 'localhost';
    let clientId = (0, uuid_1.v4)();
    let hostname = typeof configOrHostname === 'string' ? configOrHostname : 'localhost';
    let reqResPort = 63524;
    let pubSubPort = 63525;
    let protocol = 'ws';
    let clientAliveTimeout;
    if (typeof configOrHostname === 'object') {
        hostname = (_a = configOrHostname.hostname) !== null && _a !== void 0 ? _a : hostname;
        reqResPort = (_b = configOrHostname.reqResPort) !== null && _b !== void 0 ? _b : reqResPort;
        pubSubPort = (_c = configOrHostname.pubSubPort) !== null && _c !== void 0 ? _c : pubSubPort;
        clientId = (_d = configOrHostname.clientId) !== null && _d !== void 0 ? _d : clientId;
        pingSystemInterval = (_e = configOrHostname.pingSystemInterval) !== null && _e !== void 0 ? _e : pingSystemInterval;
        systemAliveTimeout = (_f = configOrHostname.systemAliveTimeout) !== null && _f !== void 0 ? _f : systemAliveTimeout;
        protocol = configOrHostname.secure ? 'wss' : protocol;
        clientAliveTimeout = configOrHostname.clientAliveTimeout;
    }
    const reqResSocket = new motion_master_req_res_web_socket_1.MotionMasterReqResWebSocket();
    const pubSubSocket = new motion_master_pub_sub_web_socket_1.MotionMasterPubSubWebSocket();
    const client = new MotionMasterClient(reqResSocket, pubSubSocket);
    reqResSocket.open(`${protocol}://${hostname}:${reqResPort}?clientId=${clientId}`, pingSystemInterval, systemAliveTimeout);
    pubSubSocket.open(`${protocol}://${hostname}:${pubSubPort}?clientId=${clientId}`);
    if (clientAliveTimeout) {
        client
            .whenReady(5000)
            .then(() => {
            client.request.setSystemClientTimeout({ timeoutMs: clientAliveTimeout });
        })
            .catch((err) => {
            console.error('MotionMasterClient failed to set client alive timeout:', err.message);
        });
    }
    return client;
}
exports.createMotionMasterClient = createMotionMasterClient;
class MotionMasterClient {
    constructor(reqResSocket, pubSubSocket) {
        this.reqResSocket = reqResSocket;
        this.pubSubSocket = pubSubSocket;
        this.request = new motion_master_req_res_client_1.MotionMasterReqResClient(this.reqResSocket);
        this.monitor = new motion_master_pub_sub_client_1.MotionMasterPubSubClient(this.pubSubSocket);
        // These observables are assigned here rather than via field initializers because they reference
        // `this.reqResSocket`/`this.pubSubSocket`. Under native class-field semantics (used by Bun, which
        // ignores `useDefineForClassFields`), field initializers run before the constructor body assigns
        // the parameter properties, leaving those sockets undefined. Assigning here guarantees correct order.
        this.socketsOpened$ = (0, rxjs_1.combineLatest)([this.reqResSocket.opened$, this.pubSubSocket.opened$]).pipe((0, rxjs_1.map)(([reqResSocketOpened, pubSubSocketOpened]) => reqResSocketOpened && pubSubSocketOpened));
        this.ready$ = this.socketsOpened$.pipe((0, rxjs_1.distinctUntilChanged)(), (0, rxjs_1.filter)((opened) => opened === true));
        this.onceReady$ = this.ready$.pipe((0, rxjs_1.first)());
        this.subscribeToSystemEvent();
    }
    /**
     * Returns an Observable that emits once when both req/res and pub/sub sockets are opened, then completes.
     *
     * - Emits immediately if both sockets are already open.
     * - Completes after emitting once.
     * - Errors with a TimeoutError if the specified timeout is reached before both sockets are open.
     *
     * @param requestTimeout The maximum time (in milliseconds) to wait for both sockets to open. Default is 5000 ms.
     * @returns Observable<void> that emits once when ready and then completes.
     */
    onceReady(requestTimeout = 5000) {
        return this.socketsOpened$.pipe((0, rxjs_1.distinctUntilChanged)(), (0, rxjs_1.filter)((opened) => opened === true), (0, rxjs_1.take)(1), (0, rxjs_1.timeout)(requestTimeout), (0, rxjs_1.map)(() => void 0));
    }
    /**
     * Waits until both req/res and pub/sub sockets are opened.
     *
     * - Resolves immediately if both sockets are already open.
     * - Otherwise, waits up to `requestTimeout` milliseconds for both sockets to open.
     * - Throws a TimeoutError if the timeout is reached before both sockets are open.
     * - Returns a Promise that resolves when the client is ready.
     *
     * @param requestTimeout Maximum time in milliseconds to wait for both sockets to open. Default is 5000 ms.
     * @returns Promise<void> that resolves when both sockets are open or rejects on timeout.
     */
    whenReady(requestTimeout = 5000) {
        return (0, rxjs_1.firstValueFrom)(this.socketsOpened$.pipe((0, rxjs_1.distinctUntilChanged)(), (0, rxjs_1.filter)((opened) => opened === true), (0, rxjs_1.take)(1), (0, rxjs_1.timeout)(requestTimeout), (0, rxjs_1.map)(() => void 0)));
    }
    /**
     * Creates and returns and instance of {@link DataMonitoring} class.
     */
    createDataMonitoring(ids, monitoringInterval, config) {
        return new data_monitoring_1.DataMonitoring(this, ids, monitoringInterval, config);
    }
    /**
     * Start monitoring.
     *
     * Note that the returned observable will never complete on its own.
     *
     * If you unsubscribe from it, the teardown code will send a request to the master to stop the monitoring.
     * That means you will end up with an observable that is not complete, but it won't emit any values.
     * This is fine when you want to use the monitoring observable directly in the template with async pipe,
     * which will unsubscribe automatically and stop the monitoring when you leave the component.
     *
     * If you need to complete the observable so that, for example, the toArray() operator works as expected, then use the {@link DataMonitoring} class,
     * which has a stop method. It can optionally buffer data for exporting as CSV or performing statistics on it.
     *
     * @param ids Device parameter IDs to monitor.
     * @param monitoringInterval At what interval should the master application send values.
     * @param config Configure the topic, buffer size, distinct.
     * @param requestTimeout How long will the client wait for the master to confirm that monitoring has started.
     * @returns An observable of an array of parameter values.
     */
    startMonitoring(ids, monitoringInterval, config, requestTimeout = 5000) {
        return (0, rxjs_1.defer)(() => {
            var _a, _b;
            const topic = (_a = config === null || config === void 0 ? void 0 : config.topic) !== null && _a !== void 0 ? _a : (0, uuid_1.v4)();
            const messageId = (_b = config === null || config === void 0 ? void 0 : config.messageId) !== null && _b !== void 0 ? _b : (0, uuid_1.v4)();
            return this.request.resolveGetParameterValuesIds(ids).pipe((0, rxjs_1.mergeMap)((refs) => {
                if (new Set(refs.map(([deviceAddress]) => deviceAddress)).size !== 1) {
                    throw new Error('Start monitoring only supports a single device');
                }
                // Skipping write-only (wo) parameters is necessary because Motion Master does not initiate monitoring when the requested list contains such parameters.
                // For now, this serves as a temporary solution until Motion Master is fixed.
                refs = refs.filter(([, index]) => index !== 0x1024);
                const deviceAddress = refs[0][0];
                const props = {
                    getDeviceParameterValues: {
                        deviceAddress,
                        parameters: refs.map(([, index, subindex]) => ({ index, subindex, loadFromCache: false })),
                        sendProgress: false,
                    },
                    interval: monitoringInterval,
                    topic,
                };
                return this.request.startMonitoringDeviceParameterValues(props, requestTimeout, messageId);
            }), (0, rxjs_1.mergeMap)(() => {
                if (!config) {
                    config = { messageId, topic, bufferSize: 1, distinct: false };
                }
                this.monitor.subscribe(config);
                const values$ = this.monitor.socket.data$.pipe((0, operators_1.selectMotionMasterMessageByTopic)(config.topic), (0, operators_1.mapMonitoringParameterValuesStatusMessageToParameterValues)());
                return new rxjs_1.Observable((observer) => {
                    const subscription = values$.subscribe(observer).add(() => {
                        this.stopMonitoring(messageId);
                    });
                    return subscription;
                });
            }));
        });
    }
    /**
     * TODO: return device parameters instead of just parameters
     * get parameters from ESI uprfront, and then when parameters are received
     * from motion master just concatenate (spread operator) props from ESI and props from parameter
     * assign device parameter id like 0x1018:00.12345
     * when assembling device parameter, get parameter from device in ESI based on product code, see ESI service
     * this should probably done for other get parameters functions, get ESI file from device or prepared, get data from it
     */
    startMonitoringDeviceParameters(ids, monitoringInterval, config, requestTimeout = 5000) {
        return (0, rxjs_1.defer)(() => {
            var _a, _b;
            const topic = (_a = config === null || config === void 0 ? void 0 : config.topic) !== null && _a !== void 0 ? _a : (0, uuid_1.v4)();
            const messageId = (_b = config === null || config === void 0 ? void 0 : config.messageId) !== null && _b !== void 0 ? _b : (0, uuid_1.v4)();
            return this.request.resolveGetParameterValuesIds(ids).pipe((0, rxjs_1.mergeMap)((refs) => {
                if (new Set(refs.map(([deviceAddress]) => deviceAddress)).size !== 1) {
                    throw new Error('Start monitoring only supports a single device');
                }
                // Skipping write-only (wo) parameters is necessary because Motion Master does not initiate monitoring when the requested list contains such parameters.
                // For now, this serves as a temporary solution until Motion Master is fixed.
                refs = refs.filter(([, index]) => index !== 0x1024);
                const deviceAddress = refs[0][0];
                const props = {
                    getDeviceParameterValues: {
                        deviceAddress,
                        parameters: refs.map(([, index, subindex]) => ({ index, subindex, loadFromCache: false })),
                        sendProgress: false,
                    },
                    interval: monitoringInterval,
                    topic,
                };
                return this.request.startMonitoringDeviceParameterValues(props, requestTimeout, messageId);
            }), (0, rxjs_1.mergeMap)(() => {
                if (!config) {
                    config = { messageId, topic, bufferSize: 1, distinct: false };
                }
                this.monitor.subscribe(config);
                const values$ = this.monitor.socket.data$.pipe((0, operators_1.selectMotionMasterMessageByTopic)(config.topic), (0, operators_1.mapMonitoringParameterValuesStatusMessageToDeviceParameters)(this.request));
                return new rxjs_1.Observable((observer) => {
                    const subscription = values$.subscribe(observer).add(() => {
                        this.stopMonitoring(messageId);
                    });
                    return subscription;
                });
            }));
        });
    }
    startMonitoringValue(id, monitoringInterval, config, requestTimeout = 5000) {
        const ids = typeof id === 'string' ? [id] : [id];
        return this.startMonitoring(ids, monitoringInterval, config, requestTimeout).pipe((0, rxjs_1.map)((parameters) => { var _a; return ((_a = parameters[0]) !== null && _a !== void 0 ? _a : 0); }));
    }
    trackControlword(deviceRef, monitoringInterval = 10000) {
        return this.startMonitoringValue([deviceRef, 0x6040, 0], monitoringInterval).pipe((0, rxjs_1.distinctUntilChanged)());
    }
    trackStatusword(deviceRef, monitoringInterval = 10000) {
        return this.startMonitoringValue([deviceRef, 0x6041, 0], monitoringInterval).pipe((0, rxjs_1.distinctUntilChanged)());
    }
    trackOperationEnabled(deviceRef, monitoringInterval = 10000) {
        return this.trackStatusword(deviceRef, monitoringInterval).pipe((0, rxjs_1.map)((statusword) => (0, cia402_1.getCia402State)(statusword) === cia402_1.Cia402State.OPERATION_ENABLED));
    }
    trackHomingProcedureStatus(deviceRef, monitoringInterval = 50000) {
        return this.trackStatusword(deviceRef, monitoringInterval).pipe((0, rxjs_1.map)(homing_1.createHomingProcedureStatus));
    }
    trackTargetReached(deviceRef, monitoringInterval = 10000) {
        return this.startMonitoringValue([deviceRef, 0x6041, 0], monitoringInterval).pipe((0, rxjs_1.map)((value) => (value & 0x0400) > 0), // tr = target reached bit
        (0, rxjs_1.distinctUntilChanged)());
    }
    waitUntilTargetReached(deviceRef, monitoringInterval = 10000, requestTimeout = 5000) {
        return this.trackTargetReached(deviceRef, monitoringInterval).pipe((0, rxjs_1.filter)(Boolean), (0, rxjs_1.first)(), (0, rxjs_1.timeout)(requestTimeout));
    }
    whenTargetReached(deviceRef, monitoringInterval = 10000, requestTimeout = 5000) {
        return (0, rxjs_1.firstValueFrom)(this.waitUntilTargetReached(deviceRef, monitoringInterval, requestTimeout));
    }
    waitUntilCia402StateReached(deviceRef, state, monitoringInterval = 10000, requestTimeout = 60000) {
        return this.startMonitoringValue([deviceRef, 0x6041, 0], monitoringInterval).pipe((0, rxjs_1.filter)((value) => (0, cia402_1.getCia402State)(value) === state), (0, rxjs_1.first)(), (0, rxjs_1.timeout)(requestTimeout), (0, rxjs_1.map)(() => undefined));
    }
    whenCia402StateReached(deviceRef, state, monitoringInterval = 10000, requestTimeout = 60000) {
        return (0, rxjs_1.firstValueFrom)(this.waitUntilCia402StateReached(deviceRef, state, monitoringInterval, requestTimeout));
    }
    waitUntilParameterValueEquals(id, value, window = 0, monitoringInterval = 50000, requestTimeout = 5000) {
        return this.startMonitoringValue(id, monitoringInterval).pipe((0, rxjs_1.distinctUntilChanged)(), (0, rxjs_1.map)((v) => {
            return v >= value - window && v <= value + window;
        }), (0, rxjs_1.filter)(Boolean), (0, rxjs_1.timeout)(requestTimeout));
    }
    whenParameterValueEquals(id, value, window = 0, monitoringInterval = 20000, requestTimeout = 5000) {
        return (0, rxjs_1.firstValueFrom)(this.waitUntilParameterValueEquals(id, value, window, monitoringInterval, requestTimeout));
    }
    /**
     * Executes a torque profile on the specified device.
     *
     * Downloads target torque and slope first, then sets Torque Profile Mode and transitions to
     * Operation Enabled. The brake is released automatically by the drive in PT mode. What happens
     * next depends on the combination of skipQuickStop and targetReachTimeout:
     *
     * - skipQuickStop: false (default) + targetReachTimeout set:
     *   Waits for the target reached bit (0x6041) within the given timeout, waits for holdingDuration
     *   if set, then issues a quick stop. The state waited for after the quick stop depends on the
     *   quick stop option code (0x605A): Quick Stop Active if the code is 6, Switch On Disabled otherwise.
     *   Throws if the target is not reached in time.
     *
     * - skipQuickStop: false (default) + no positive targetReachTimeout:
     *   Throws immediately — the function cannot determine when to issue the quick stop.
     *
     * - skipQuickStop: true + targetReachTimeout set:
     *   Waits for the target reached bit within the given timeout but does not issue a quick stop.
     *   The device remains in the Operation Enabled state.
     *
     * - skipQuickStop: true + no targetReachTimeout:
     *   Returns immediately after enabling the drive. The device remains in the Operation Enabled state.
     *
     * When skipQuickStop is true and an error is thrown (e.g. target not reached in time), the drive
     * remains in Operation Enabled. The caller is responsible for issuing a quick stop or transitioning
     * the drive to a safe state.
     *
     * @param deviceRef - Reference to the target device.
     * @param config - Torque profile configuration.
     * @throws Error if skipQuickStop is false and targetReachTimeout is not a positive number.
     * @throws Error if the target is not reached within targetReachTimeout.
     */
    runTorqueProfile(deviceRef, config) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!config.skipQuickStop && (config.targetReachTimeout == null || config.targetReachTimeout <= 0)) {
                throw new Error('targetReachTimeout is required when skipQuickStop is false');
            }
            yield this.request.download(deviceRef, 0x6071, 0, config.target);
            yield this.request.download(deviceRef, 0x6087, 0, config.slope);
            if (config.window !== undefined) {
                yield this.request.download(deviceRef, 0x2014, 1, config.window);
            }
            if (config.windowTime !== undefined) {
                yield this.request.download(deviceRef, 0x2014, 2, config.windowTime);
            }
            yield (0, rxjs_1.lastValueFrom)(this.request.setModesOfOperation(deviceRef, cia402_1.ModesOfOperation.TORQUE_PROFILE_MODE));
            yield this.request.transitionToCia402State(deviceRef, cia402_1.Cia402State.OPERATION_ENABLED);
            if (config.targetReachTimeout && config.targetReachTimeout > 0) {
                try {
                    yield this.whenTargetReached(deviceRef, (_a = config.monitoringInterval) !== null && _a !== void 0 ? _a : 1000, config.targetReachTimeout);
                    if (config.holdingDuration && config.holdingDuration > 0) {
                        yield (0, util_1.resolveAfter)(config.holdingDuration);
                    }
                }
                finally {
                    if (!config.skipQuickStop) {
                        const quickStopOptionCode = yield this.request.upload(deviceRef, 0x605a, 0);
                        yield this.request.quickStop(deviceRef);
                        const targetState = quickStopOptionCode === 6 ? cia402_1.Cia402State.QUICK_STOP_ACTIVE : cia402_1.Cia402State.SWITCH_ON_DISABLED;
                        yield this.whenCia402StateReached(deviceRef, targetState);
                    }
                }
            }
        });
    }
    /**
     * Executes a velocity profile on the specified device.
     *
     * Downloads target velocity, acceleration and deceleration first, then sets Profile Velocity Mode
     * and transitions to Operation Enabled. The brake is released automatically by the drive in PV mode.
     * What happens next depends on the combination of skipQuickStop and targetReachTimeout:
     *
     * - skipQuickStop: false (default) + targetReachTimeout set:
     *   Waits for the target reached bit (0x6041) within the given timeout, waits for holdingDuration
     *   if set, then issues a quick stop. The state waited for after the quick stop depends on the
     *   quick stop option code (0x605A): Quick Stop Active if the code is 6, Switch On Disabled otherwise.
     *   Throws if the target is not reached in time.
     *
     * - skipQuickStop: false (default) + no positive targetReachTimeout:
     *   Throws immediately — the function cannot determine when to issue the quick stop.
     *
     * - skipQuickStop: true + targetReachTimeout set:
     *   Waits for the target reached bit within the given timeout but does not issue a quick stop.
     *   The device remains in the Operation Enabled state.
     *
     * - skipQuickStop: true + no targetReachTimeout:
     *   Returns immediately after enabling the drive. The device remains in the Operation Enabled state.
     *
     * When skipQuickStop is true and an error is thrown (e.g. target not reached in time), the drive
     * remains in Operation Enabled. The caller is responsible for issuing a quick stop or transitioning
     * the drive to a safe state.
     *
     * @param deviceRef - Reference to the target device.
     * @param config - Velocity profile configuration.
     * @throws Error if skipQuickStop is false and targetReachTimeout is not a positive number.
     * @throws Error if the target is not reached within targetReachTimeout.
     */
    runVelocityProfile(deviceRef, config) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!config.skipQuickStop && (config.targetReachTimeout == null || config.targetReachTimeout <= 0)) {
                throw new Error('targetReachTimeout is required when skipQuickStop is false');
            }
            yield this.request.download(deviceRef, 0x60ff, 0, config.target);
            yield this.request.download(deviceRef, 0x6083, 0, config.acceleration);
            yield this.request.download(deviceRef, 0x6084, 0, config.deceleration);
            if (config.window !== undefined) {
                yield this.request.download(deviceRef, 0x606d, 0, config.window);
            }
            if (config.windowTime !== undefined) {
                yield this.request.download(deviceRef, 0x606e, 0, config.windowTime);
            }
            yield (0, rxjs_1.lastValueFrom)(this.request.setModesOfOperation(deviceRef, cia402_1.ModesOfOperation.PROFILE_VELOCITY_MODE));
            yield this.request.transitionToCia402State(deviceRef, cia402_1.Cia402State.OPERATION_ENABLED);
            if (config.targetReachTimeout && config.targetReachTimeout > 0) {
                try {
                    yield this.whenTargetReached(deviceRef, (_a = config.monitoringInterval) !== null && _a !== void 0 ? _a : 1000, config.targetReachTimeout);
                    if (config.holdingDuration && config.holdingDuration > 0) {
                        yield (0, util_1.resolveAfter)(config.holdingDuration);
                    }
                }
                finally {
                    if (!config.skipQuickStop) {
                        const quickStopOptionCode = yield this.request.upload(deviceRef, 0x605a, 0);
                        yield this.request.quickStop(deviceRef);
                        const targetState = quickStopOptionCode === 6 ? cia402_1.Cia402State.QUICK_STOP_ACTIVE : cia402_1.Cia402State.SWITCH_ON_DISABLED;
                        yield this.whenCia402StateReached(deviceRef, targetState);
                    }
                }
            }
        });
    }
    /**
     * Executes a position profile on the specified device.
     *
     * Downloads target position, velocity, acceleration and deceleration first, then sets Profile
     * Position Mode, transitions to Operation Enabled and applies the set point. The brake is released
     * automatically by the drive in PP mode. What happens next depends on the combination of
     * skipQuickStop and targetReachTimeout:
     *
     * - skipQuickStop: false (default) + targetReachTimeout set:
     *   Waits for the target reached bit (0x6041) within the given timeout, waits for holdingDuration
     *   if set, then issues a quick stop. The state waited for after the quick stop depends on the
     *   quick stop option code (0x605A): Quick Stop Active if the code is 6, Switch On Disabled otherwise.
     *   Throws if the target is not reached in time.
     *
     * - skipQuickStop: false (default) + no positive targetReachTimeout:
     *   Throws immediately — the function cannot determine when to issue the quick stop.
     *
     * - skipQuickStop: true + targetReachTimeout set:
     *   Waits for the target reached bit within the given timeout but does not issue a quick stop.
     *   The device remains in the Operation Enabled state.
     *
     * - skipQuickStop: true + no targetReachTimeout:
     *   Returns immediately after applying the set point. The device remains in the Operation Enabled state.
     *
     * When skipQuickStop is true and an error is thrown (e.g. target not reached in time), the drive
     * remains in Operation Enabled. The caller is responsible for issuing a quick stop or transitioning
     * the drive to a safe state.
     *
     * @param deviceRef - Reference to the target device.
     * @param config - Position profile configuration.
     * @throws Error if skipQuickStop is false and targetReachTimeout is not a positive number.
     * @throws Error if the target is not reached within targetReachTimeout.
     */
    runPositionProfile(deviceRef, config) {
        var _a;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (!config.skipQuickStop && (config.targetReachTimeout == null || config.targetReachTimeout <= 0)) {
                throw new Error('targetReachTimeout is required when skipQuickStop is false');
            }
            if (config.relative) {
                const positionActualValue = yield this.request.upload(deviceRef, 0x6064, 0);
                yield this.request.download(deviceRef, 0x607a, 0, config.target + positionActualValue);
            }
            else {
                yield this.request.download(deviceRef, 0x607a, 0, config.target);
            }
            yield this.request.download(deviceRef, 0x6081, 0, config.velocity);
            yield this.request.download(deviceRef, 0x6083, 0, config.acceleration);
            yield this.request.download(deviceRef, 0x6084, 0, config.deceleration);
            if (config.window !== undefined) {
                yield this.request.download(deviceRef, 0x6067, 0, config.window);
            }
            if (config.windowTime !== undefined) {
                yield this.request.download(deviceRef, 0x6068, 0, config.windowTime);
            }
            yield (0, rxjs_1.lastValueFrom)(this.request.setModesOfOperation(deviceRef, cia402_1.ModesOfOperation.PROFILE_POSITION_MODE));
            yield this.request.transitionToCia402State(deviceRef, cia402_1.Cia402State.OPERATION_ENABLED);
            yield this.request.applySetPoint(deviceRef);
            if (config.targetReachTimeout && config.targetReachTimeout > 0) {
                try {
                    yield this.whenTargetReached(deviceRef, (_a = config.monitoringInterval) !== null && _a !== void 0 ? _a : 1000, config.targetReachTimeout);
                    if (config.holdingDuration && config.holdingDuration > 0) {
                        yield (0, util_1.resolveAfter)(config.holdingDuration);
                    }
                }
                finally {
                    if (!config.skipQuickStop) {
                        const quickStopOptionCode = yield this.request.upload(deviceRef, 0x605a, 0);
                        yield this.request.quickStop(deviceRef);
                        const targetState = quickStopOptionCode === 6 ? cia402_1.Cia402State.QUICK_STOP_ACTIVE : cia402_1.Cia402State.SWITCH_ON_DISABLED;
                        yield this.whenCia402StateReached(deviceRef, targetState);
                    }
                }
            }
        });
    }
    /**
     * Executes the homing procedure to locate the home position value (0x2005:01).
     *
     * You can optionally provide a configuration object that specifies acceleration, method, and speeds;
     * if not provided, the parameter values already set on the device will be used.
     *
     * The function will first emit a "running" status, followed by either "succeeded" or "failed".
     * After completion, the device will remain in the Operation Enabled state.
     *
     * No CiA402 state transitions will occur for the "Homing on current position" methods (35 and 37).
     * Additionally, the halt bit will remain unchanged for these methods, in contrast to other methods where the halt bit is cleared.
     *
     * @param deviceRef - Reference to the device being homed.
     * @param config - Optional configuration for the homing procedure.
     * @param monitoringInterval - Interval for monitoring the homing procedure status in microseconds (default: 50,000 μs).
     * @param requestTimeout - Timeout for the homing request in milliseconds (default: 300,000 ms).
     * @returns An Observable that emits the status of the homing procedure.
     */
    runHomingProcedure(deviceRef, config, monitoringInterval = 50000, requestTimeout = 300000) {
        return (0, rxjs_1.concat)((config === null || config === void 0 ? void 0 : config.acceleration) ? this.request.download(deviceRef, 0x609a, 0, config.acceleration) : rxjs_1.EMPTY, (config === null || config === void 0 ? void 0 : config.method) ? this.request.download(deviceRef, 0x6098, 0, config.method) : rxjs_1.EMPTY, (config === null || config === void 0 ? void 0 : config.speedDuringSearchForSwitch)
            ? this.request.download(deviceRef, 0x6099, 1, config.speedDuringSearchForSwitch)
            : rxjs_1.EMPTY, (config === null || config === void 0 ? void 0 : config.speedDuringSearchForZero)
            ? this.request.download(deviceRef, 0x6099, 2, config.speedDuringSearchForZero)
            : rxjs_1.EMPTY, config ? (0, rxjs_1.defer)(() => this.request.forceOnDemandParametersUpdate(deviceRef)) : rxjs_1.EMPTY, this.request.getParameterValue(deviceRef, 0x6098, 0) // retrieve the current value of the homing method from the device
        ).pipe((0, rxjs_1.last)(), // emit only the value of the homing method
        (0, rxjs_1.mergeMap)((method) => {
            if (method === 35 || method === 37) {
                return (0, rxjs_1.concat)(this.request.setModesOfOperation(deviceRef, cia402_1.ModesOfOperation.HOMING_MODE), this.request.setControlwordToStartHoming(deviceRef, false)).pipe((0, rxjs_1.last)());
            }
            else {
                return (0, rxjs_1.concat)(this.request.setModesOfOperation(deviceRef, cia402_1.ModesOfOperation.HOMING_MODE), (0, rxjs_1.defer)(() => this.request.setControlwordToStartHoming(deviceRef, true)), (0, rxjs_1.defer)(() => this.request.transitionToCia402State(deviceRef, cia402_1.Cia402State.OPERATION_ENABLED))).pipe((0, rxjs_1.last)());
            }
        }), (0, rxjs_1.mergeMap)(() => {
            return this.trackHomingProcedureStatus(deviceRef, monitoringInterval).pipe((0, rxjs_1.takeWhile)((status) => status.request === 'running', true), (0, rxjs_1.timeout)(requestTimeout));
        }));
    }
    subscribeToSystemEvent() {
        this.systemEventSubscription = this.monitor.systemEvent$.subscribe((systemEvent) => {
            if (systemEvent.state === types_1.MotionMasterMessage.Status.SystemEvent.State.DEINITIALIZING) {
                this.request.clearDevicesInformation();
            }
        });
    }
    stopMonitoring(messageId) {
        this.request.stopMonitoringDeviceParameterValues({ startMonitoringRequestId: messageId });
        this.monitor.unsubscribe(messageId);
    }
    stopMonitoringAll() {
        this.monitor.messageIds.forEach(this.stopMonitoring);
    }
    closeSockets() {
        var _a;
        (_a = this.systemEventSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe();
        this.request.clearDevicesInformation();
        this.reqResSocket.close();
        this.pubSubSocket.close();
    }
    reopenSockets() {
        this.reqResSocket.reopen();
        this.pubSubSocket.reopen();
        this.subscribeToSystemEvent();
    }
}
exports.MotionMasterClient = MotionMasterClient;
//# sourceMappingURL=motion-master-client.js.map