TeslasuitDocumentation
APIs

Play a haptic touch

C11 · Teslasuit v. 4.5+ · SDK 2.5.1+

A complete, runnable walkthrough: initialize the API, connect to a device, access the Haptic subsystem, and play a haptic touch on the right upper arm.

Code for haptic touch#

⚠️ Safety. Make sure the device is connected and calibrated before running. Start with conservative touch settings (low amplitude, short duration), check electrode placement, and stop if anything feels wrong.

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <ts_api/ts_types.h>

#ifdef _WIN32
#include <Windows.h>
#elif defined(__APPLE__) || defined(__linux__)
#include <dlfcn.h>
#include <unistd.h>
#endif

// Change these to play with the touch: which bone gets it, and how it feels.
static const TsBoneIndex TARGET_BONE_INDEX = TsBoneIndex_RightUpperArm;
static const uint64_t TOUCH_PERIOD_US = 21600;
static const uint64_t TOUCH_AMPLITUDE_PERCENT = 40;
static const uint64_t TOUCH_PULSE_WIDTH_US = 120;
static const uint64_t TOUCH_DURATION_MS = 3000;

#ifdef __cplusplus
extern "C" {
#endif

#define API_FUNCTION_NAME(name) name##_func_t

#define DECLARE_API_FUNCTION(ret, name, args)                                  \
  typedef ret(*API_FUNCTION_NAME(name)) args;                                  \
  static API_FUNCTION_NAME(name) name

#define LOAD_API_FUNCTION(lib, name)                                           \
  do {                                                                         \
    name = (API_FUNCTION_NAME(name))api_library_get_function(lib, #name);      \
    if (name == NULL)                                                          \
      return false;                                                            \
  } while (0)

typedef void *SharedLibrary;

static void *api_library_get_function(SharedLibrary lib,
                                      const char *function_name) {
#ifdef _WIN32
  return (void *)GetProcAddress((HMODULE)lib, function_name);
#elif defined(__APPLE__) || defined(__linux__)
  return dlsym(lib, function_name);
#endif
}

static void sleep_ms(int ms) {
#ifdef _WIN32
  Sleep(ms);
#elif defined(__APPLE__) || defined(__linux__)
  usleep(ms * 1000);
#endif
}

// device/session
DECLARE_API_FUNCTION(TsStatusCode, ts_initialize, ());
DECLARE_API_FUNCTION(void, ts_uninitialize, ());
DECLARE_API_FUNCTION(TsStatusCode, ts_get_device_list,
                     (TsDevice *, uint32_t *));
DECLARE_API_FUNCTION(TsDeviceHandle *, ts_device_open, (TsDevice *));
DECLARE_API_FUNCTION(const char *, ts_device_get_name, (TsDeviceHandle *));
DECLARE_API_FUNCTION(void, ts_device_close, (TsDeviceHandle *));
DECLARE_API_FUNCTION(const char *, ts_get_status_code_message, (TsStatusCode));

// mapping/layout
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_get_by_device,
                     (TsDeviceHandle *, TsMapping2d *));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_get_number_of_layouts,
                     (const TsMapping2d, uint64_t *));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_get_layouts,
                     (const TsMapping2d, TsLayout2d *, uint64_t));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_layout_get_type,
                     (const TsLayout2d layout, TsLayout2dType *layout_type));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_layout_get_element_type,
                     (const TsLayout2d layout,
                      TsLayout2dElementType *layout_element_type));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_layout_get_number_of_bones,
                     (const TsLayout2d, uint64_t *));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_layout_get_bones,
                     (const TsLayout2d, TsMapping2dBone *, uint64_t));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_bone_get_index,
                     (const TsMapping2dBone, TsBoneIndex *));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_bone_get_number_of_contents,
                     (const TsMapping2dBone, uint64_t *));
DECLARE_API_FUNCTION(TsStatusCode, ts_mapping2d_bone_get_contents,
                     (const TsMapping2dBone, TsMapping2dBoneContent *,
                      uint64_t));

// haptic
DECLARE_API_FUNCTION(TsStatusCode, ts_haptic_create_touch,
                     (TsDeviceHandle *, TsHapticParam *, uint64_t,
                      const TsMapping2dBoneContent *, uint64_t, uint64_t,
                      uint64_t *));
DECLARE_API_FUNCTION(TsStatusCode, ts_haptic_play_playable,
                     (TsDeviceHandle *, uint64_t));

static bool load_api_functions(SharedLibrary lib) {
  // device/session
  LOAD_API_FUNCTION(lib, ts_initialize);
  LOAD_API_FUNCTION(lib, ts_uninitialize);
  LOAD_API_FUNCTION(lib, ts_get_device_list);
  LOAD_API_FUNCTION(lib, ts_device_open);
  LOAD_API_FUNCTION(lib, ts_device_get_name);
  LOAD_API_FUNCTION(lib, ts_device_close);
  LOAD_API_FUNCTION(lib, ts_get_status_code_message);

  // mapping/layout
  LOAD_API_FUNCTION(lib, ts_mapping2d_get_by_device);
  LOAD_API_FUNCTION(lib, ts_mapping2d_get_number_of_layouts);
  LOAD_API_FUNCTION(lib, ts_mapping2d_get_layouts);
  LOAD_API_FUNCTION(lib, ts_mapping2d_layout_get_type);
  LOAD_API_FUNCTION(lib, ts_mapping2d_layout_get_element_type);
  LOAD_API_FUNCTION(lib, ts_mapping2d_layout_get_number_of_bones);
  LOAD_API_FUNCTION(lib, ts_mapping2d_layout_get_bones);
  LOAD_API_FUNCTION(lib, ts_mapping2d_bone_get_index);
  LOAD_API_FUNCTION(lib, ts_mapping2d_bone_get_number_of_contents);
  LOAD_API_FUNCTION(lib, ts_mapping2d_bone_get_contents);

  // haptic
  LOAD_API_FUNCTION(lib, ts_haptic_create_touch);
  LOAD_API_FUNCTION(lib, ts_haptic_play_playable);
  return true;
}

static bool check_code(TsStatusCode code) {
  if (code != 0) {
    printf("Error: %s\n", ts_get_status_code_message(code));
    return false;
  }
  return true;
}

// Finds the Electric+Channel layout, for a given device.
static bool find_electric_channel_layout(TsDeviceHandle *dev,
                                         TsLayout2d *out_layout) {
  TsMapping2d mapping2d = NULL;
  check_code(ts_mapping2d_get_by_device(dev, &mapping2d));

  uint64_t number_of_layouts = 0;
  check_code(ts_mapping2d_get_number_of_layouts(mapping2d, &number_of_layouts));
  TsLayout2d *layouts = malloc(number_of_layouts * sizeof(TsLayout2d));
  if (layouts == NULL) {
    printf("Error: allocation failed (layouts)\n");
    return false;
  }
  check_code(ts_mapping2d_get_layouts(mapping2d, layouts, number_of_layouts));

  // Electric=1, Channel=2 values from ts_types.h doc comments
  const TsLayout2dType electric_type = 1;
  const TsLayout2dElementType channel_element_type = 2;
  bool found = false;
  for (uint64_t i = 0; i < number_of_layouts && !found; i++) {
    TsLayout2dType layout_type = 0;
    TsLayout2dElementType element_type = 0;
    check_code(ts_mapping2d_layout_get_type(layouts[i], &layout_type));
    check_code(ts_mapping2d_layout_get_element_type(layouts[i], &element_type));

    if (layout_type == electric_type && element_type == channel_element_type) {
      *out_layout = layouts[i];
      found = true;
    }
  }

  free(layouts);

  if (!found) {
    printf("Error: electric/channel layout not found\n");
  }
  return found;
}

static bool find_target_bone(TsLayout2d layout, TsMapping2dBone *out_bone) {
  uint64_t number_of_bones = 0;
  check_code(ts_mapping2d_layout_get_number_of_bones(layout, &number_of_bones));

  TsMapping2dBone *bones = malloc(number_of_bones * sizeof(TsMapping2dBone));
  if (bones == NULL) {
    printf("Error: allocation failed (bones)\n");
    return false;
  }
  check_code(ts_mapping2d_layout_get_bones(layout, bones, number_of_bones));

  bool found = false;

  // Some bones are listed once per TsBone2dSide (Front/Back); we select only
  // the side with nonzero contents, since that's the one with electrodes wired.
  for (uint64_t i = 0; i < number_of_bones && !found; i++) {
    TsBoneIndex bone_index = TsBoneIndex_BonesCount;
    uint64_t content_count = 0;
    check_code(ts_mapping2d_bone_get_index(bones[i], &bone_index));
    check_code(
        ts_mapping2d_bone_get_number_of_contents(bones[i], &content_count));

    if (bone_index != TARGET_BONE_INDEX || content_count == 0) {
      continue;
    }

    *out_bone = bones[i];
    found = true;
  }

  free(bones);

  if (!found) {
    printf("Error: target bone channel not found\n");
  }
  return found;
}

// Caller must free() the returned array.
static bool get_bone_channels(TsMapping2dBone bone,
                              TsMapping2dBoneContent **out_contents,
                              uint64_t *out_count) {
  uint64_t content_count = 0;
  check_code(ts_mapping2d_bone_get_number_of_contents(bone, &content_count));

  TsMapping2dBoneContent *contents =
      malloc(content_count * sizeof(TsMapping2dBoneContent));
  if (contents == NULL) {
    printf("Error: allocation failed (bone_contents)\n");
    return false;
  }
  check_code(ts_mapping2d_bone_get_contents(bone, contents, content_count));

  *out_contents = contents;
  *out_count = content_count;
  return true;
}

// Finds the target bone's channel on the connected device and plays a touch on
// it.
static void run(TsDeviceHandle *dev) {
  TsLayout2d layout;
  if (!find_electric_channel_layout(dev, &layout)) {
    return;
  }

  TsMapping2dBone bone;
  if (!find_target_bone(layout, &bone)) {
    return;
  }

  TsMapping2dBoneContent *bone_contents = NULL;
  uint64_t number_of_bone_contents = 0;
  if (!get_bone_channels(bone, &bone_contents, &number_of_bone_contents)) {
    return;
  }

  TsHapticParam haptic_params[] = {{1, TOUCH_PERIOD_US},
                                   {2, TOUCH_AMPLITUDE_PERCENT},
                                   {3, TOUCH_PULSE_WIDTH_US}};

  uint64_t playable_id = 0;
  if (check_code(ts_haptic_create_touch(
          dev, haptic_params, sizeof(haptic_params) / sizeof(TsHapticParam),
          bone_contents, number_of_bone_contents, TOUCH_DURATION_MS,
          &playable_id))) {
    check_code(ts_haptic_play_playable(dev, playable_id));
    sleep_ms((int)TOUCH_DURATION_MS);
  }

  free(bone_contents);
}

#ifdef __cplusplus
}
#endif

int main() {
  const char *api_path = getenv("TESLASUIT_API_LIB_PATH");
  if (api_path == NULL) {
    printf("Error: environment variable TESLASUIT_API_LIB_PATH not set\n");
    return 0;
  }

#ifdef _WIN32
  SharedLibrary lib = (SharedLibrary)LoadLibraryEx(
      api_path, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
#elif defined(__APPLE__) || defined(__linux__)
  SharedLibrary lib = (SharedLibrary)dlopen(api_path, RTLD_NOW | RTLD_LOCAL);
#endif
  if (lib == NULL) {
    printf("Error: library was not found\n");
    return 0;
  }

  if (!load_api_functions(lib)) {
    printf("Error: failed to load library functions\n");
    goto unload;
  }

  if (!check_code(ts_initialize())) {
    printf("Error: failed to initialize API\n");
    goto unload;
  }

  TsDevice device;
  uint32_t device_count = 0;
  while (device_count == 0) {
    printf("Waiting for a device...\n");
    device_count = 1;
    ts_get_device_list(&device, &device_count);
    sleep_ms(100);
  }

  TsDeviceHandle *dev = ts_device_open(&device);
  if (dev == NULL) {
    printf("Error: failed to get device\n");
    goto deinitialize;
  }
  printf("Using device: %s\n", ts_device_get_name(dev));

  run(dev);

  ts_device_close(dev);

deinitialize:
  ts_uninitialize();

unload:
#ifdef _WIN32
  FreeLibrary((HMODULE)lib);
#elif defined(__APPLE__) || defined(__linux__)
  dlclose(lib);
#endif
  return 0;
}

Expected output#

Waiting for a device...
Using device: Teslasuit XR-5

Explore these references and guides to build on this example and get the most out of Teslasuit haptic and EMS features:

Essential API references#

Practical guides & examples#

Deeper dives & broader concepts#

  • Haptics & EMS Main Concept: The conceptual background behind haptic feedback and EMS.
  • Body & Channel Map: A Python-first tool to visualize which suit channels map to which areas of the body — conceptually useful here too.
  • Teslasuit Main Concepts: Fundamental articles covering Teslasuit's core technology and how its subsystems interact.