/**
 * MAVROS 通信模块 - 基于 roslibjs
 * 直接通过 MAVROS 话题和服务控制无人机
 *
 * 架构: 浏览器 ──roslibjs──► rosbridge_server ──ROS话题──► MAVROS ──MAVLink──► PX4
 *
 * 使用的话题:
 *   订阅: /uav1/mavros/state              (mavros_msgs/State)
 *   订阅: /uav1/mavros/local_position/pose (geometry_msgs/PoseStamped)
 *   订阅: /uav1/mavros/global_position/raw/fix (sensor_msgs/NavSatFix)
 *   订阅: /uav1/mavros/battery             (sensor_msgs/BatteryState)
 *   发布: /uav1/mavros/setpoint_position/local  (geometry_msgs/PoseStamped)
 *   服务: /uav1/mavros/cmd/arming          (mavros_msgs/CommandBool)
 *   服务: /uav1/mavros/set_mode            (mavros_msgs/SetMode)
 */

const WsModule = (() => {
    let ros = null;
    let rosConnected = false;
    let droneConnected = false;
    let fcuReady = false;  // true when MAVROS reports valid mode (FCU connection established)

    // ========== 配置: UAV 命名空间 ==========
    // 仿真环境默认: '/uav1'   实机可能: '' 或 '/uav1'
    // 如果话题不对，改这里
    let uavNS = '/uav1';

    function topicName(name) { return uavNS + name; }
    function serviceName(name) { return uavNS + name; }

    // ROS handles
    let stateSub = null;
    let localPosSub = null;
    let gpsSub = null;
    let batterySub = null;
    let velocitySub = null;
    let setpointPub = null;

    // Telemetry state
    let telemetry = {
        lat: 0, lng: 0, alt: 0,
        speed: 0, battery: 100, heading: 0,
        flightMode: 'UNKNOWN', gpsStatus: 'No Fix',
        armed: false,
        localPos: { x: 0, y: 0, z: 0 }  // ENU: x=East, y=North, z=Up
    };

    // Origin for lat/lng <-> ENU conversion
    let origin = { lat: null, lng: null };
    let originSet = false;
    let originFromGps = false;  // true if origin was set from real GPS

    // Navigation state
    let rawWaypoints = [];     // Original lat/lng waypoints
    let navWaypoints = [];     // Converted ENU waypoints
    let currentWPIndex = 0;
    let navActive = false;
    let navPaused = false;
    let publishTimer = null;
    let wpCheckTimer = null;
    let telemetryTimer = null;  // Throttled UI update timer

    const WP_REACH_DIST = 2.0;     // meters
    const PUBLISH_INTERVAL = 100;   // ms (10 Hz, OFFBOARD needs >= 2Hz)

    // ---- Coordinate conversion ----

    function latLngToENU(lat, lng, alt) {
        if (!originSet) return { x: 0, y: 0, z: alt };
        const latRad = origin.lat * Math.PI / 180;
        const x = (lng - origin.lng) * Math.cos(latRad) * 111319.488;  // East
        const y = (lat - origin.lat) * 111319.488;                      // North
        return { x, y, z: alt };                                         // Up
    }

    function enuToLatLng(x, y) {
        if (!originSet) return { lat: 0, lng: 0 };
        const latRad = origin.lat * Math.PI / 180;
        const lat = origin.lat + y / 111319.488;
        const lng = origin.lng + x / (111319.488 * Math.cos(latRad));
        return { lat, lng };
    }

    // Quaternion to yaw (degrees, ENU: 0=East, CCW+)
    function quatToYawDeg(qx, qy, qz, qw) {
        const siny = 2 * (qw * qz + qx * qy);
        const cosy = 1 - 2 * (qy * qy + qz * qz);
        let yaw = Math.atan2(siny, cosy) * 180 / Math.PI;
        // Convert to compass heading: 0=North, CW+
        return (90 - yaw + 360) % 360;
    }

    // ---- Init & Connection ----

    function init() {}

    function isConnected() { return rosConnected; }

    function connectDrone(url) {
        try {
            if (typeof ROSLIB === 'undefined') {
                UIModule.onStatus({ type: 'error', message: 'roslibjs 库未加载' });
                return;
            }

            if (ros) {
                try { ros.close(); } catch (e) { /* ignore */ }
            }

            UIModule.onStatus({ type: 'info', message: `正在连接 rosbridge: ${url} ...` });

            ros = new ROSLIB.Ros({ url: url });

            ros.on('connection', () => {
                rosConnected = true;
                UIModule.onStatus({ type: 'info', message: 'rosbridge 已连接，正在检测 MAVROS 话题...' });
                setupSubscriptions();
            });

            ros.on('error', (error) => {
                UIModule.onStatus({ type: 'error', message: `rosbridge 连接错误: ${error || '未知'}` });
            });

            ros.on('close', () => {
                const wasConnected = rosConnected;
                rosConnected = false;
                droneConnected = false;
                stopAll();
                if (wasConnected) {
                    UIModule.onDroneDisconnected();
                }
            });
        } catch (e) {
            UIModule.onStatus({ type: 'error', message: `连接失败: ${e.message || e}` });
        }
    }

    // ---- Topic subscriptions ----

    function setupSubscriptions() {
        // Try to detect which namespace has MAVROS
        trySubscribesWithFallback();
    }

    // Set a default origin for coordinate conversion (fallback before GPS)
    function setDefaultOrigin() {
        if (originSet) return;
        origin.lat = MapModule.DEFAULT_LAT || 30.0;
        origin.lng = MapModule.DEFAULT_LNG || 120.0;
        originSet = true;
        originFromGps = false;
        UIModule.onStatus({ type: 'info', message: '使用默认坐标原点 (' + origin.lat + ', ' + origin.lng + ')，等待 GPS 定位...' });
    }

    function trySubscribesWithFallback() {
        let gotState = false;
        const tryNS = uavNS;

        // Subscribe to MAVROS state
        stateSub = new ROSLIB.Topic({
            ros: ros,
            name: topicName('/mavros/state'),
            messageType: 'mavros_msgs/State'
        });

        const stateTimeout = setTimeout(() => {
            if (!gotState) {
                // Try without namespace
                if (tryNS !== '') {
                    uavNS = '';
                    UIModule.onStatus({ type: 'warning', message: `未收到 ${tryNS}/mavros/state，尝试无命名空间...` });
                    trySubscribesWithFallback();
                } else {
                    UIModule.onStatus({ type: 'error', message: '未检测到 MAVROS 话题，请确认仿真/实机已启动' });
                }
            }
        }, 5000);

        stateSub.subscribe((msg) => {
            clearTimeout(stateTimeout);
            gotState = true;

            telemetry.armed = msg.armed || false;
            telemetry.flightMode = msg.mode || 'UNKNOWN';

            // Check if FCU is truly connected (has valid mode)
            const modeValid = msg.connected && msg.mode && msg.mode.length > 0;

            if (!droneConnected) {
                droneConnected = true;
                if (modeValid) {
                    fcuReady = true;
                    UIModule.onStatus({ type: 'success', message: `无人机已连接 (模式: ${msg.mode}, ${msg.armed ? '已解锁' : '未解锁'})` });
                } else {
                    UIModule.onStatus({ type: 'warning', message: `MAVROS 已连接，但飞控未就绪 (mode=空)。等待 PX4 初始化...` });
                }
                setDefaultOrigin();
            }

            // Detect when FCU becomes ready
            if (!fcuReady && modeValid) {
                fcuReady = true;
                UIModule.onStatus({ type: 'success', message: `飞控已就绪! 当前模式: ${msg.mode}, 状态: ${msg.armed ? '已解锁' : '未解锁'}` });
            }
        });

        // Local position (ENU: x=East, y=North, z=Up)
        localPosSub = new ROSLIB.Topic({
            ros: ros,
            name: topicName('/mavros/local_position/pose'),
            messageType: 'geometry_msgs/PoseStamped'
        });

        localPosSub.subscribe((msg) => {
            if (msg.pose && msg.pose.position) {
                telemetry.localPos = {
                    x: msg.pose.position.x,  // East
                    y: msg.pose.position.y,  // North
                    z: msg.pose.position.z   // Up
                };
            }
            if (msg.pose && msg.pose.orientation) {
                const o = msg.pose.orientation;
                telemetry.heading = quatToYawDeg(o.x, o.y, o.z, o.w);
            }
        });

        // GPS (for lat/lng display and origin)
        gpsSub = new ROSLIB.Topic({
            ros: ros,
            name: topicName('/mavros/global_position/raw/fix'),
            messageType: 'sensor_msgs/NavSatFix'
        });

        gpsSub.subscribe((msg) => {
            telemetry.lat = msg.latitude || 0;
            telemetry.lng = msg.longitude || 0;

            if (msg.status) {
                telemetry.gpsStatus = msg.status.status >= 0 ? '3D Fix' : 'No Fix';
            }

            // Update origin from real GPS (overrides default origin)
            if (msg.latitude !== 0 && Math.abs(msg.latitude) > 1) {
                if (!originFromGps) {
                    origin.lat = msg.latitude;
                    origin.lng = msg.longitude;
                    originSet = true;
                    originFromGps = true;
                    UIModule.onStatus({ type: 'success', message: 'GPS 已定位! 坐标原点: ' + origin.lat.toFixed(6) + ', ' + origin.lng.toFixed(6) });
                    MapModule.centerOnDrone(msg.latitude, msg.longitude);
                }
            }
        });

        // Battery
        batterySub = new ROSLIB.Topic({
            ros: ros,
            name: topicName('/mavros/battery'),
            messageType: 'sensor_msgs/BatteryState'
        });

        batterySub.subscribe((msg) => {
            telemetry.battery = (msg.percentage != null) ? msg.percentage * 100 : 100;
        });

        // Velocity (for speed calculation)
        velocitySub = new ROSLIB.Topic({
            ros: ros,
            name: topicName('/mavros/local_position/velocity'),
            messageType: 'geometry_msgs/TwistStamped'
        });

        velocitySub.subscribe((msg) => {
            if (msg.twist && msg.twist.linear) {
                const v = msg.twist.linear;
                telemetry.speed = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
            }
        });

        // Setpoint publisher — PoseStamped is the standard MAVROS position control interface
        setpointPub = new ROSLIB.Topic({
            ros: ros,
            name: topicName('/mavros/setpoint_position/local'),
            messageType: 'geometry_msgs/PoseStamped'
        });

        // Start throttled telemetry loop (5Hz, decoupled from subscription rate)
        startTelemetryLoop();
    }

    function pushTelemetry() {
        let displayLat = telemetry.lat;
        let displayLng = telemetry.lng;
        if ((!displayLat || displayLat === 0) && originSet) {
            const ll = enuToLatLng(telemetry.localPos.x, telemetry.localPos.y);
            displayLat = ll.lat;
            displayLng = ll.lng;
        }

        UIModule.onTelemetry({
            type: 'telemetry',
            lat: displayLat,
            lng: displayLng,
            alt: telemetry.localPos.z,
            speed: telemetry.speed,
            battery: telemetry.battery,
            heading: telemetry.heading,
            flight_mode: telemetry.flightMode,
            gps_status: telemetry.gpsStatus,
            current_wp: (currentWPIndex + 1) + '/' + navWaypoints.length
        });
    }

    function startTelemetryLoop() {
        if (telemetryTimer) clearInterval(telemetryTimer);
        pushTelemetry();
        telemetryTimer = setInterval(pushTelemetry, 200); // 5Hz UI update
    }

    function stopTelemetryLoop() {
        if (telemetryTimer) { clearInterval(telemetryTimer); telemetryTimer = null; }
    }

    // ---- Setpoint publishing ----

    let spSeq = 0;

    function publishSetpoint(east, north, up) {
        if (!setpointPub || !rosConnected) return;

        const msg = new ROSLIB.Message({
            header: {
                seq: spSeq++,
                stamp: { secs: Math.floor(Date.now() / 1000), nsecs: 0 },
                frame_id: 'map'
            },
            pose: {
                position: { x: east, y: north, z: up },
                orientation: { x: 0, y: 0, z: 0, w: 1 }
            }
        });

        setpointPub.publish(msg);
    }

    function startPublishLoop(east, north, up) {
        stopPublishing();
        publishSetpoint(east, north, up);
        publishTimer = setInterval(() => {
            publishSetpoint(east, north, up);
        }, PUBLISH_INTERVAL);
    }

    function stopPublishing() {
        if (publishTimer) { clearInterval(publishTimer); publishTimer = null; }
        if (wpCheckTimer) { clearInterval(wpCheckTimer); wpCheckTimer = null; }
    }

    function stopAll() {
        stopPublishing();
        stopTelemetryLoop();
    }

    // ---- ROS Service calls ----

    // Simple one-shot versions for arm/disarm/land buttons

    // ---- Mission operations ----

    function uploadMission(waypoints) {
        if (waypoints.length === 0) {
            UIModule.onStatus({ type: 'error', message: '航点列表为空' });
            return;
        }

        // Store raw waypoints
        rawWaypoints = waypoints;
        currentWPIndex = 0;

        // Convert to ENU (origin should always be set by now)
        if (originSet) {
            convertWaypoints();
            UIModule.onStatus({ type: 'success', message: `任务上传成功，共 ${navWaypoints.length} 个航点` });
        } else {
            // Fallback (shouldn't happen, but just in case)
            UIModule.onStatus({ type: 'success', message: `已暂存 ${waypoints.length} 个航点` });
        }
    }

    function convertWaypoints() {
        if (!originSet || !rawWaypoints || rawWaypoints.length === 0) return false;
        navWaypoints = rawWaypoints.map(wp => {
            const enu = latLngToENU(wp.lat, wp.lng, wp.alt);
            return { ...enu, hold_time: wp.hold_time || 0 };
        });
        return true;
    }

    function startMission() {
        if (!rosConnected || !droneConnected) {
            UIModule.onStatus({ type: 'error', message: '未连接无人机' });
            return;
        }
        if (!fcuReady) {
            UIModule.onStatus({ type: 'error', message: '飞控未就绪 (MAVROS 模式为空)。请等待 PX4 完全启动后重试。也可尝试在终端执行: rosservice call ' + serviceName('/mavros/set_mode') + ' "{base_mode: 0, custom_mode: \'OFFBOARD\'}"  验证模式切换是否正常' });
            return;
        }
        if (rawWaypoints.length === 0) {
            UIModule.onStatus({ type: 'error', message: '请先上传任务' });
            return;
        }

        // Convert waypoints
        if (navWaypoints.length === 0 && rawWaypoints.length > 0) {
            if (!originSet) setDefaultOrigin();
            convertWaypoints();
        }
        if (navWaypoints.length === 0) {
            if (!originSet) {
                UIModule.onStatus({ type: 'error', message: '无法开始任务: 实机模式需要等待 GPS 定位后才能转换航点坐标' });
            } else {
                UIModule.onStatus({ type: 'error', message: '航点转换失败，请重新上传任务' });
            }
            return;
        }

        // Step 1: Start sending takeoff setpoints at target altitude
        // PX4 requires setpoints BEFORE switching OFFBOARD, and altitude must be positive
        const firstWP = navWaypoints[0];
        const startX = telemetry.localPos.x || 0;
        const startY = telemetry.localPos.y || 0;
        const takeoffAlt = firstWP.z;
        startPublishLoop(startX, startY, takeoffAlt);
        UIModule.onStatus({ type: 'info', message: '正在发送位置设定值 (10Hz)...' });

        // Step 2: Wait for setpoints to flow, then switch OFFBOARD
        setTimeout(() => {
            callSetModeWithRetry('OFFBOARD', 3, (ok) => {
                if (!ok) {
                    stopPublishing();
                    UIModule.onStatus({ type: 'error', message: 'OFFBOARD 模式切换失败，任务中止。请在终端验证: rosservice call ' + serviceName('/mavros/set_mode') + ' "{base_mode: 0, custom_mode: \'OFFBOARD\'}"' });
                    return;
                }

                // Step 3: Arm
                setTimeout(() => {
                    callArmingWithRetry(true, 3, (ok) => {
                        if (!ok) {
                            stopPublishing();
                            UIModule.onStatus({ type: 'error', message: '电机解锁失败，任务中止。请检查: 1) 传感器是否校准 2) GPS是否定位 3) 安全检查是否通过' });
                            return;
                        }

                        // Step 4: Wait for takeoff, then navigate
                        setTimeout(() => {
                            UIModule.onStatus({ type: 'info', message: '正在起飞...' });

                            // Check if drone reached takeoff altitude
                            let takeoffDone = false;
                            const takeoffCheck = setInterval(() => {
                                const curZ = telemetry.localPos.z || 0;
                                if (curZ > takeoffAlt - 1.0 && curZ > 0.5) {
                                    clearInterval(takeoffCheck);
                                    takeoffDone = true;
                                    navActive = true;
                                    navPaused = false;
                                    UIModule.onStatus({ type: 'success', message: '起飞完成，开始巡航' });
                                    navigateToWaypoint(0);
                                }
                            }, 500);

                            // Fallback: start navigation after 15s regardless
                            setTimeout(() => {
                                clearInterval(takeoffCheck);
                                if (!takeoffDone) {
                                    navActive = true;
                                    navPaused = false;
                                    UIModule.onStatus({ type: 'info', message: '任务已开始执行' });
                                    navigateToWaypoint(0);
                                }
                            }, 15000);
                        }, 1000);
                    });
                }, 500);
            });
        }, 3000);
    }

    // Retry wrappers for service calls
    function callSetModeWithRetry(mode, retries, callback) {
        UIModule.onStatus({ type: 'info', message: `正在切换 ${mode} 模式...` });
        const service = new ROSLIB.Service({
            ros: ros,
            name: serviceName('/mavros/set_mode'),
            serviceType: 'mavros_msgs/SetMode'
        });
        service.callService(
            new ROSLIB.ServiceRequest({ base_mode: 0, custom_mode: mode }),
            (result) => {
                if (result && result.mode_sent) {
                    UIModule.onStatus({ type: 'success', message: `模式已切换: ${mode}` });
                    callback(true);
                } else if (retries > 0) {
                    UIModule.onStatus({ type: 'warning', message: `模式切换未成功，剩余重试 ${retries} 次...` });
                    setTimeout(() => callSetModeWithRetry(mode, retries - 1, callback), 2000);
                } else {
                    UIModule.onStatus({ type: 'error', message: `模式切换失败: ${JSON.stringify(result)}` });
                    callback(false);
                }
            },
            (error) => {
                if (retries > 0) {
                    UIModule.onStatus({ type: 'warning', message: `模式切换服务错误，重试中...` });
                    setTimeout(() => callSetModeWithRetry(mode, retries - 1, callback), 2000);
                } else {
                    UIModule.onStatus({ type: 'error', message: `模式切换服务失败: ${error || '不可用'}` });
                    callback(false);
                }
            }
        );
    }

    function callArmingWithRetry(arm, retries, callback) {
        UIModule.onStatus({ type: 'info', message: arm ? '正在解锁电机...' : '正在上锁电机...' });
        const service = new ROSLIB.Service({
            ros: ros,
            name: serviceName('/mavros/cmd/arming'),
            serviceType: 'mavros_msgs/CommandBool'
        });
        service.callService(
            new ROSLIB.ServiceRequest({ value: arm }),
            (result) => {
                if (result && result.success) {
                    UIModule.onStatus({ type: 'success', message: arm ? '电机已解锁' : '电机已上锁' });
                    callback(true);
                } else if (retries > 0) {
                    UIModule.onStatus({ type: 'warning', message: `${arm ? '解锁' : '上锁'}未成功 (result=${result.result})，剩余重试 ${retries} 次...` });
                    setTimeout(() => callArmingWithRetry(arm, retries - 1, callback), 2000);
                } else {
                    UIModule.onStatus({ type: 'error', message: `${arm ? '解锁' : '上锁'}失败: result=${result ? result.result : 'N/A'}` });
                    callback(false);
                }
            },
            (error) => {
                if (retries > 0) {
                    UIModule.onStatus({ type: 'warning', message: `${arm ? '解锁' : '上锁'}服务错误，重试中...` });
                    setTimeout(() => callArmingWithRetry(arm, retries - 1, callback), 2000);
                } else {
                    UIModule.onStatus({ type: 'error', message: `${arm ? '解锁' : '上锁'}服务失败` });
                    callback(false);
                }
            }
        );
    }

    // Simple one-shot mode switch (no retry, used by returnHome/land)
    function callSetMode(mode) {
        callSetModeWithRetry(mode, 1, () => {});
    }

    function navigateToWaypoint(index) {
        if (!navActive || navPaused) return;

        if (index >= navWaypoints.length) {
            UIModule.onStatus({ type: 'success', message: '所有航点已到达，任务完成' });
            const p = telemetry.localPos;
            startPublishLoop(p.x, p.y, p.z);
            navActive = false;
            return;
        }

        currentWPIndex = index;
        const wp = navWaypoints[index];

        UIModule.onStatus({ type: 'info', message: `飞往航点 ${index + 1}/${navWaypoints.length} (${wp.x.toFixed(1)}, ${wp.y.toFixed(1)}, ${wp.z.toFixed(1)}m)` });
        startPublishLoop(wp.x, wp.y, wp.z);
        checkWaypointReached(index);
    }

    function checkWaypointReached(index) {
        if (wpCheckTimer) clearInterval(wpCheckTimer);

        const wpTimeout = setTimeout(() => {
            // Fallback: if waypoint not reached in 30s, advance anyway
            if (navActive && !navPaused) {
                UIModule.onStatus({ type: 'warning', message: `航点 ${index + 1} 到达超时，跳至下一航点` });
                navigateToWaypoint(index + 1);
            }
        }, 30000);

        wpCheckTimer = setInterval(() => {
            if (!navActive || navPaused) {
                clearInterval(wpCheckTimer);
                clearTimeout(wpTimeout);
                return;
            }

            const wp = navWaypoints[index];
            if (!wp) { clearTimeout(wpTimeout); return; }

            const p = telemetry.localPos;
            const dx = p.x - wp.x;
            const dy = p.y - wp.y;
            const dz = p.z - wp.z;
            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);

            if (dist < WP_REACH_DIST) {
                clearInterval(wpCheckTimer);
                clearTimeout(wpTimeout);
                wpCheckTimer = null;

                UIModule.onTelemetry({
                    type: 'mission_progress',
                    current: index + 1,
                    total: navWaypoints.length
                });

                const holdTime = wp.hold_time || 0;
                if (holdTime > 0) {
                    UIModule.onStatus({ type: 'info', message: `航点 ${index + 1} 已到达，悬停 ${holdTime} 秒` });
                    setTimeout(() => {
                        if (navActive && !navPaused) navigateToWaypoint(index + 1);
                    }, holdTime * 1000);
                } else {
                    navigateToWaypoint(index + 1);
                }
            }
        }, 500);
    }

    function pauseMission() {
        if (!navActive || navPaused) return;
        navPaused = true;
        if (wpCheckTimer) { clearInterval(wpCheckTimer); wpCheckTimer = null; }
        const p = telemetry.localPos;
        startPublishLoop(p.x, p.y, p.z);
        UIModule.onStatus({ type: 'info', message: '任务已暂停（悬停中）' });
    }

    function resumeMission() {
        if (!navActive || !navPaused) return;
        navPaused = false;
        navigateToWaypoint(currentWPIndex);
        UIModule.onStatus({ type: 'info', message: '任务已继续' });
    }

    function returnHome() {
        stopPublishing();
        navActive = false;
        navPaused = false;

        UIModule.onStatus({ type: 'warning', message: '正在返航...' });

        // Fly to origin at current altitude
        startPublishLoop(0, 0, telemetry.localPos.z);

        const homeCheck = setInterval(() => {
            const p = telemetry.localPos;
            const dist = Math.sqrt(p.x * p.x + p.y * p.y);
            if (dist < WP_REACH_DIST) {
                clearInterval(homeCheck);
                // Switch to AUTO.LAND for safe landing
                callSetMode('AUTO.LAND');
                setTimeout(() => stopPublishing(), 3000);
                UIModule.onStatus({ type: 'info', message: '已到达起飞点上方，正在降落...' });
            }
        }, 500);
    }

    function land() {
        stopPublishing();
        navActive = false;
        navPaused = false;
        // Use PX4 AUTO.LAND mode (safest)
        callSetMode('AUTO.LAND');
        UIModule.onStatus({ type: 'warning', message: '正在降落 (AUTO.LAND)...' });
    }

    function arm() {
        callArmingWithRetry(true, 2, () => {});
    }

    function disarm() {
        callArmingWithRetry(false, 2, () => {});
    }

    // 获取 ROS 连接实例 (供闪光检测模块使用)
    function getRos() {
        return ros;
    }

    // 获取本地位置 (供闪光检测模块使用)
    function getLocalPosition() {
        return telemetry.localPos;
    }

    return {
        init,
        isConnected,
        connectDrone,
        uploadMission,
        startMission,
        pauseMission,
        resumeMission,
        returnHome,
        land,
        arm,
        disarm,
        getRos,
        getLocalPosition,
        publishSetpoint
    };
})();
