# MOZA SDK Flight Base Force Feedback Command Guide

> Audience: developers integrating AS flight base force feedback through MOZA SDK
> Scope: force feedback effect registration, parameter updates, start, stop, and release
> Public headers: `moza/moza.h`, `moza/moza.hpp`, `moza/device_as.hpp`

This document explains how to control AS flight base force feedback through the public MOZA SDK command interface. It describes SDK-level commands and parameters only; it does not depend on any specific game, telemetry module, or application implementation.

## 1. Basic Model

Flight Base force feedback is controlled through the `MainCtrl_Ffb*` command family. A typical workflow is:

1. Register one or more effect blocks and obtain or assign an `effectBlockIndex`.
2. Periodically update parameters for each effect block, such as constant, periodic, or condition parameters.
3. Start, stop, or free a single effect through effect operation.
4. Start/stop FFB calculation, reset effects, or stop all effects through global control.

SDK users should send the command repository name or command id, for example `MainCtrl_FfbConstant` / `604`. Applications do not need to build the low-level HID payload manually.

The C++ typed accessors in this document use the current generator output, such as `cmdMainCtrlFfbControl()`. If your SDK package exposes slightly different accessor names, check the bundled `moza/generated/device_as.generated.hpp`, or use the raw command lookup approach shown below.

## 2. Commands and Parameters

| Purpose | Repository name | Command id | Current typed accessor | Request |
| --- | --- | ---: | --- | --- |
| Register effect | `MainCtrl_FfbNewEffect` | `603` | `cmdMainCtrlFfbNewEffect()` | `type, index` |
| Constant force | `MainCtrl_FfbConstant` | `604` | `cmdMainCtrlFfbConstant()` | constant request |
| Ramp force | `MainCtrl_FfbRamp` | `605` | `cmdMainCtrlFfbRamp()` | ramp request |
| Periodic force | `MainCtrl_FfbPeriodic` | `606` | `cmdMainCtrlFfbPeriodic()` | periodic request |
| Condition force | `MainCtrl_FfbCondition` | `607` | `cmdMainCtrlFfbCondition()` | condition request |
| Envelope | `MainCtrl_FfbEnvelope` | `608` | `cmdMainCtrlFfbEnvelope()` | envelope request |
| Single effect operation | `MainCtrl_FfbEffectOperation` | `609` | `cmdMainCtrlFfbEffectOperation()` | `effectBlockIndex, operation` |
| Global control | `MainCtrl_FfbControl` | `610` | `cmdMainCtrlFfbControl()` | `control` |

Related parameters:

| Purpose | Repository name | Parameter id | Accessor | Notes |
| --- | --- | ---: | --- | --- |
| FFB enable | `Main_CtrlFfbEnable` | `312` | `ctrlFfbEnable()` | If this value is `0`, the application should stop real-time FFB updates |
| Aileron dynamic condition-force binding | `Main_DynamicConditionForceBind_Aileron` | `360` | `dynamicConditionForceBindAileron()` | Optional, needed only by specific dynamic condition-force effects |
| Elevator dynamic condition-force binding | `Main_DynamicConditionForceBind_Elevator` | `363` | `dynamicConditionForceBindElevator()` | Optional, needed only by specific dynamic condition-force effects |

## 3. Effect Types

`MainCtrl_FfbNewEffect` uses the following `type` values:

| Value | Type |
| ---: | --- |
| `0` | None |
| `1` | Constant Force |
| `2` | Ramp |
| `3` | Square |
| `4` | Sine |
| `5` | Triangle |
| `6` | Sawtooth Up |
| `7` | Sawtooth Down |
| `8` | Spring |
| `9` | Damper |
| `10` | Inertia |
| `11` | Friction |

`index` is the effect block index. The valid range is `1~80`. Applications may explicitly assign indexes so later updates, starts, and stops can target the same effect. The command repository also allows automatic allocation when the index is omitted; if automatic allocation is used, the application must store the index returned by `MainCtrl_FfbNewEffect`.

## 4. Registration Flow

Recommended registration sequence:

1. Initialize the SDK, enumerate devices, and open the AS flight base.
2. Optionally read `Main_CtrlFfbEnable`. If it is `0`, do not enter real-time FFB updates.
3. Send `MainCtrl_FfbControl(2)` to reset and free existing effects.
4. Send `MainCtrl_FfbControl(1)` to start FFB calculation.
5. For each effect required by the application, send `MainCtrl_FfbNewEffect(type, index)`.
6. If a specific dynamic condition-force effect is used, write the corresponding binding parameter, such as `dynamicConditionForceBindAileron().set(index)`.
7. After registration completes, start periodic effect-parameter updates.

It is recommended to send `MainCtrl_FfbNewEffect` synchronously and check the response. A response value of `0` means failure; values from `1` to `80` indicate the registered effect index.

## 5. Real-Time Updates

Real-time updates target the registered `effectBlockIndex`. The application can calculate force-feedback parameters from game or simulation telemetry and send the matching `MainCtrl_Ffb*` command.

### 5.1 Constant

Command: `MainCtrl_FfbConstant` / `604`

Field order:

| Field | Type | Notes |
| --- | --- | --- |
| `effectBlockIndex` | `uint8` | Registered effect index |
| `magnitude` | `int16` | Force magnitude |
| `duration` | `uint16` | Duration in ms |
| `enableAxis` | bit/struct | X, Y, DirectionEnable |
| `directionX` | `uint16` | Angle * 100 |
| `directionY` | `uint16` | Angle * 100 |

### 5.2 Ramp

Command: `MainCtrl_FfbRamp` / `605`

Field order: `effectBlockIndex, startLevel, endLevel, duration, enableAxis, directionX, directionY`.

### 5.3 Periodic

Command: `MainCtrl_FfbPeriodic` / `606`

Field order: `effectBlockIndex, magnitude, offset, phase, period, duration, enableAxis, directionX, directionY`.

### 5.4 Condition

Command: `MainCtrl_FfbCondition` / `607`

Field order:

`effectBlockIndex, parameterBlockOffset, centerPointOffset, positiveCoefficient, negativeCoefficient, positiveSaturation, negativeSaturation, deadBand, duration, enableAxis, directionX, directionY`.

Condition-type effects such as spring, damper, inertia, and friction usually update these fields periodically. Some dynamic condition-force effects also require `Main_DynamicConditionForceBind_Aileron` or `Main_DynamicConditionForceBind_Elevator` to be written after registration.

### 5.5 Envelope

Command: `MainCtrl_FfbEnvelope` / `608`

Field order: `effectBlockIndex, attackLevel, fadeLevel, attackTim, fadeTime`.

`attackTim` is the field name preserved by the current generated header.

## 6. Start, Stop, and Release

### 6.1 Single Effect

Command: `MainCtrl_FfbEffectOperation` / `609`

Fields:

- `effectBlockIndex`: registered effect index.
- `operation`:
  - `0`: Stop
  - `1`: Start
  - `2`: Free

### 6.2 Global FFB Control

Command: `MainCtrl_FfbControl` / `610`

Control values:

| Value | Meaning |
| ---: | --- |
| `0` | Stop FFB calculation without changing effect state |
| `1` | Start FFB calculation without changing effect state |
| `2` | Reset and free all effects |
| `3` | Stop all effects |

Recommended usage:

- Before initialization or re-registration: send `MainCtrl_FfbControl(2)`, then `MainCtrl_FfbControl(1)`.
- When the game exits, the device changes, or `Main_CtrlFfbEnable` changes from enabled to disabled: send `MainCtrl_FfbControl(3)`.
- To pause calculation while preserving effect state: send `MainCtrl_FfbControl(0)`.
- To resume calculation: send `MainCtrl_FfbControl(1)`.

## 7. Example Code / Pseudocode

The following example shows a minimal flow: register one constant-force effect, send one parameter update, start it, stop it, and stop all effects before leaving. The example focuses on command order and parameter meaning; production code can wrap error handling as needed.

```cpp
#include "moza/moza.h"
#include "moza/moza.hpp"
#include "moza/device_as.hpp"

MOZA_DEVICE_ID findAsFlight Base()
{
    auto [err, devices] = moza::getDeviceList();
    if (err != MOZA_ERR_NO_ERROR) {
        return -1;
    }

    for (const auto& info : devices) {
        if (info.category == MOZA_DC_AS && info.type == MOZA_DT_AS_WHEELBASE) {
            return info.id;
        }
    }
    return -1;
}

bool runForceFeedbackSequence(moza::as::Flight Base& base)
{
    using namespace moza::as::flight base_type;

    auto [enableErr, enabled] = base.ctrlFfbEnable().getSync(100);
    if (enableErr == MOZA_ERR_NO_ERROR && enabled == 0) {
        return false;
    }

    base.cmdMainCtrlFfbControl().sendSync(2, 100); // Reset/free all effects.
    base.cmdMainCtrlFfbControl().sendSync(1, 100); // Start FFB calculation.

    MainCtrlFfbNewEffectRequest effect{};
    effect.type = MainCtrlFfbNewEffectRequestType::ConstantForce;
    effect.index = 1;

    auto [regErr, registeredIndex] = base.cmdMainCtrlFfbNewEffect().sendSync(effect, 100);
    if (regErr != MOZA_ERR_NO_ERROR || registeredIndex == 0) {
        base.cmdMainCtrlFfbControl().send(3);
        return false;
    }

    MainCtrlFfbConstantRequest constant{};
    constant.effectBlockIndex = registeredIndex;
    constant.magnitude = 1200;
    constant.duration = 50;
    constant.enableAxis.field0 = decltype(constant.enableAxis.field0){1}; // X
    constant.enableAxis.field1 = decltype(constant.enableAxis.field1){0}; // Y
    constant.enableAxis.field2 = decltype(constant.enableAxis.field2){1}; // DirectionEnable
    constant.directionX = 0;
    constant.directionY = 0;
    base.cmdMainCtrlFfbConstant().send(constant);

    MainCtrlFfbEffectOperationRequest op{};
    op.effectBlockIndex = registeredIndex;
    op.operation = MainCtrlFfbEffectOperationRequestOperation::Start;
    base.cmdMainCtrlFfbEffectOperation().send(op);

    // ... Periodically update MainCtrl_FfbConstant / Periodic / Condition here ...

    op.operation = MainCtrlFfbEffectOperationRequestOperation::Stop;
    base.cmdMainCtrlFfbEffectOperation().send(op);

    base.cmdMainCtrlFfbControl().send(3); // Stop all effects before leaving.
    return true;
}

void runForceFeedbackExample()
{
    if (moza::initialize() != MOZA_ERR_NO_ERROR) {
        return;
    }

    MOZA_DEVICE_ID deviceId = findAsFlight Base();
    if (deviceId < 0) {
        moza::shutdown();
        return;
    }

    MOZA_DEVICE_HANDLE handle = MOZA_INVALID_DEVICE_HANDLE;
    if (Moza_DeviceOpen(deviceId, &handle) != MOZA_ERR_NO_ERROR) {
        moza::shutdown();
        return;
    }

    {
        // The generated wrapper owns the opened handle and closes it on destruction.
        moza::as::Flight Base base(handle);
        runForceFeedbackSequence(base);
    }

    moza::shutdown();
}
```

If typed generated headers are not used, commands can also be found by name and sent as value lists:

```cpp
auto [infoErr, info] = device.getCommandInfoByName("MainCtrl_FfbControl");
if (infoErr == MOZA_ERR_NO_ERROR) {
    device.command(info.id).send(std::vector<moza::Value>{moza::Value(2)});
    device.command(info.id).send(std::vector<moza::Value>{moza::Value(1)});
}
```

When using raw commands, the argument order must exactly match the field order in this document. For bit fields or structured fields, prefer typed request objects.

## 8. Notes

- `effectBlockIndex` connects registration, parameter updates, start, and stop. Applications should maintain their own mapping from logical effect to index.
- If `Main_CtrlFfbEnable == 0`, stop periodic FFB updates; send `MainCtrl_FfbControl(3)` if the currently running effects need to be cleared.
- Typed request objects are encoded according to the generated header field order, so applications usually do not need to handle byte order manually.
- Global `Reset(2)` frees all effects. After reset, register effects again before reusing the previous indexes.
- Real-time updates should be sent only when telemetry changes or at a controlled update interval to avoid unnecessary high-frequency duplicate commands.
