# 8. Developer Tools & Debugging

VHDevTools provides a text-based CLI for testing haptic effects over serial (or any text input). You send commands as strings, and the engine executes them. Responses and log messages are stored in an internal message queue.

---

## 8.1 Setup

Include the DevTools header and pass it as an extension module during init:

```cpp
#include <VHDevTools.h>

VHDevTools devTools;

void setup() {
    channels.add(1, {YOUR_OUTPUT_PIN}, {"main"});
    vh.init(&board, {&channels, &devTools});
}
```

`vh.init()` registers DevTools so it can receive text commands. DevTools parses commands and forwards them to VectorHaptics.

---

## 8.2 Sending Commands with executeStringCommand

Use `vh.executeStringCommand(string)` to send text commands. The engine routes them to DevTools (when enabled), which parses and runs them. This is the main entry point for text-based control.

```cpp
vh.executeStringCommand("PULSE 50 0.8 0.5");
vh.executeStringCommand("VIBRATE 300 0.7 160 0.3");
vh.executeStringCommand("CHNL 1; TICK 30 1.0 1.0");
```

Commands can be chained with semicolons. `CHNL 1; TICK 30 1.0 1.0` sets the current channel to 1, then plays a tick. You typically read serial input, pass it to `executeStringCommand`, then drain the message queue to print any responses.

---

## 8.3 The Message Queue

VectorHaptics stores all log output and command responses in an internal **message queue**. You cannot read messages any other way—there is no direct callback or logging API. The only way to access them is via `popMessage()`.

Each call to `popMessage()` removes and returns the next message from the queue. Messages are strings with a type prefix: `[ERROR]`, `[INFO]`, `[WARNING]`, or `[RESPONSE]`. When the queue is empty, `popMessage()` returns an empty string.

Check if there are pending messages with `hasMessage()`:

```cpp
while (vh.hasMessage()) {
    Serial.println(vh.popMessage());
}
```

You must poll and drain the queue yourself. If you never call `popMessage()`, messages accumulate and can fill the queue. Drain it regularly in your loop after handling serial input or other events.

---

## 8.4 Verbosity

`setVerbosity()` controls which message types are eligible to be enqueued. It does not affect what gets printed—only what the engine *puts* into the queue. You still read everything with `popMessage()`.

```cpp
vh.setVerbosity(SILENT);         // No messages enqueued
vh.setVerbosity(ERROR_ONLY);     // Errors only
vh.setVerbosity(INFO_ONLY);      // Info only
vh.setVerbosity(WARNING_ONLY);   // Warnings only
vh.setVerbosity(VERBOSE);        // All (ERROR | INFO | WARNING)
```

For debugging, use `VERBOSE` so you see effect completion messages, device info responses, and errors. In production, use `ERROR_ONLY` or `SILENT`.

---

## 8.5 Typical Serial Loop

Read a line from serial, pass it to `executeStringCommand`, then drain the message queue to print any responses:

```cpp
void loop() {
    if (Serial.available()) {
        String input = Serial.readStringUntil('\n');
        input.trim();
        if (input.length() > 0) {
            vh.executeStringCommand(std::string(input.c_str()));

            while (vh.hasMessage()) {
                Serial.println(vh.popMessage());
            }
        }
    }
}
```

When the user types `PULSE 50 0.8 0.5` and presses Enter, `executeStringCommand` runs the command. DevTools may enqueue an `[INFO]` message when the effect completes. The `while (vh.hasMessage())` loop drains all pending messages and prints them to serial.

---

## 8.6 Supported Commands

| Command | Aliases | Parameters | Example |
|---------|---------|------------|---------|
| `PULSE` | `P` | `[duration] [intensity] [sharpness]` | `PULSE 50 0.8 0.5` |
| `TICK` | `T` | `[duration] [intensity] [sharpness]` | `TICK 30 0.9 1.0` |
| `VIBRATE` | `V` | `[duration] [intensity] [frequency] [sharpness]` | `VIBRATE 300 0.7 160 0.3` |
| `PAUSE` | `U` | `[duration]` | `PAUSE 100` |
| `ERM` | `E` | `[duration] [intensity]` | `ERM 500 0.6` |
| `SWEEP` | `S` | `[dur] [sI] [sF] [sS] [eI] [eF] [eS] [tI] [tF] [tS]` | See MODULES.md |
| `CHNL` | — | `<channel_spec>` | `CHNL 1` or `CHNL 'left'` |
| `HDI` | — | `D` / `C` / `<channel_no>` | Device/channel info |
| `SETDEVNAME` | — | `<name>` | Set device name |
| `RESTART` | — | — | Restart device |

**Semicolon chaining:** `PULSE 50; PAUSE 100; VIBRATE 200`

**Channel targeting:** `CHNL 1; PULSE 50` or `CHNL 'left & motor'; VIBRATE 200`

For full command syntax, see [MODULES.md § VHDevTools](../MODULES.md#4-vhdevtools).

---

## 8.7 Complete Example

```cpp
#include <VectorHaptics.h>
#include <VHDevTools.h>
#include "YourBoardProfile.h"

VectorHaptics vh;
YourBoardProfile board;
VHChannels channels;
VHDevTools devTools;

void setup() {
    Serial.begin(115200);
    channels.add(1, {PIN_LEFT}, {"left", "motor"});
    channels.add(2, {PIN_RIGHT}, {"right", "motor"});
    vh.init(&board, {&channels, &devTools});
    vh.setVerbosity(VERBOSE);

    Serial.println("VectorHaptics DevTools ready. Type commands:");
    Serial.println("  PULSE 50 0.8 0.5");
    Serial.println("  VIBRATE 300 0.7 160 0.3");
    Serial.println("  CHNL 1; TICK 30 1.0 1.0");
    Serial.println("  CHNL 'left'; PULSE 50");
    Serial.println("  HDI D   (device info)");
    Serial.println("  HDI C   (all channel info)");
}

void loop() {
    if (Serial.available()) {
        String input = Serial.readStringUntil('\n');
        input.trim();
        if (input.length() > 0) {
            vh.executeStringCommand(std::string(input.c_str()));
            while (vh.hasMessage()) {
                Serial.println(vh.popMessage());
            }
        }
    }
}
```

See [CODE_EXAMPLES.md § DevTools Serial CLI](../CODE_EXAMPLES.md) for more examples.
