Joystick APIs

Joystick

The Joystick class provides a wrapper around a 2-axis analog joystick, with an associated reader function for grabbing the raw values. When the joystick update() is called, the raw values are mapped into the range [-1,1] for each axis according to the configuration provided.

Code examples for the task API are provided in the joystick example folder.

API Reference

Header File

Classes

class Joystick : public espp::BaseComponent

2-axis Joystick with axis mapping / calibration.

Basic Circular and Rectangular Joystick Example

      float min = 0;
      float max = 255;
      float center = 127;
      float deadband_percent = 0.1;
      float deadband = deadband_percent * (max - min);

      // circular joystick
      espp::Joystick js1({
          .x_calibration = {.center = center, .minimum = min, .maximum = max},
          .y_calibration = {.center = center, .minimum = min, .maximum = max},
          .type = espp::Joystick::Type::CIRCULAR,
          .center_deadzone_radius = deadband_percent,
          .range_deadzone = deadband_percent,
      });
      // square joystick (for comparison)
      espp::Joystick js2({
          .x_calibration = {.center = center,
                            .center_deadband = deadband,
                            .minimum = min,
                            .maximum = max,
                            .range_deadband = deadband},
          .y_calibration = {.center = center,
                            .center_deadband = deadband,
                            .minimum = min,
                            .maximum = max,
                            .range_deadband = deadband},
      });
      // now make a loop where we update the raw valuse and print out the joystick values
      fmt::print("raw x, raw y, js1 x, js1 y, js2 x, js2 y\n");
      for (float x = min - 10.0f; x <= max + 10.0f; x += 10.0f) {
        for (float y = min - 10.0f; y <= max + 10.0f; y += 10.0f) {
          js1.update(x, y);
          js2.update(x, y);
          fmt::print("{}, {}, {}, {}, {}, {}\n", x, y, js1.x(), js1.y(), js2.x(), js2.y());
        }
      }

ADC Joystick Example

      static constexpr adc_unit_t ADC_UNIT = CONFIG_EXAMPLE_ADC_UNIT == 1 ? ADC_UNIT_1 : ADC_UNIT_2;
      static constexpr adc_channel_t ADC_CHANNEL_X = (adc_channel_t)CONFIG_EXAMPLE_ADC_CHANNEL_X;
      static constexpr adc_channel_t ADC_CHANNEL_Y = (adc_channel_t)CONFIG_EXAMPLE_ADC_CHANNEL_Y;

      std::vector<espp::AdcConfig> channels{
          {.unit = ADC_UNIT, .channel = ADC_CHANNEL_X, .attenuation = ADC_ATTEN_DB_12},
          {.unit = ADC_UNIT, .channel = ADC_CHANNEL_Y, .attenuation = ADC_ATTEN_DB_12}};
      espp::OneshotAdc adc({
          .unit = ADC_UNIT_2,
          .channels = channels,
      });
      auto read_joystick = [&adc, &channels](float *x, float *y) -> bool {
        // this will be in mv
        auto maybe_x_mv = adc.read_mv(channels[0]);
        auto maybe_y_mv = adc.read_mv(channels[1]);
        if (maybe_x_mv.has_value() && maybe_y_mv.has_value()) {
          auto x_mv = maybe_x_mv.value();
          auto y_mv = maybe_y_mv.value();
          *x = (float)(x_mv);
          *y = (float)(y_mv);
          return true;
        }
        return false;
      };
      espp::Joystick js1({
          // convert [0, 3300]mV to approximately [-1.0f, 1.0f]
          .x_calibration =
              {.center = 1700.0f, .center_deadband = 100.0f, .minimum = 0.0f, .maximum = 3300.0f},
          .y_calibration =
              {.center = 1700.0f, .center_deadband = 100.0f, .minimum = 0.0f, .maximum = 3300.0f},
          .get_values = read_joystick,
      });
      espp::Joystick js2({
          // convert [0, 3300]mV to approximately [-1.0f, 1.0f]
          .x_calibration =
              {.center = 1700.0f, .center_deadband = 0.0f, .minimum = 0.0f, .maximum = 3300.0f},
          .y_calibration =
              {.center = 1700.0f, .center_deadband = 0.0f, .minimum = 0.0f, .maximum = 3300.0f},
          .type = espp::Joystick::Type::CIRCULAR,
          .center_deadzone_radius = 0.1f,
          .get_values = read_joystick,
      });
      auto task_fn = [&js1, &js2](std::mutex &m, std::condition_variable &cv) {
        js1.update();
        js2.update();
        fmt::print("{}, {}\n", js1, js2);
        // NOTE: sleeping in this way allows the sleep to exit early when the
        // task is being stopped / destroyed
        {
          std::unique_lock<std::mutex> lk(m);
          cv.wait_for(lk, 500ms);
        }
        // don't want to stop the task
        return false;
      };
      auto task = espp::Task({.callback = task_fn,
                              .task_config = {.name = "Joystick"},
                              .log_level = espp::Logger::Verbosity::INFO});
      fmt::print("js1 x, js1 y, js2 x, js2 y\n");
      task.start();

Public Types

enum class Type

Type of the joystick.

Note

When using a Type::CIRCULAR joystick, it’s recommended to set the individual x/y calibration deadzones to be 0 and to only use the deadzone_radius field to set the deadzone around the center.

Values:

enumerator RECTANGULAR

The default type of joystick. Uses the rangemappers for each axis (to convert raw values from input range to be [-1,1]) independently which results in x/y deadzones and output that are rectangular.

enumerator CIRCULAR

The joystick is configured to have a circular output. This means that the x/y < deadzones are circular around the input and range and the output is clamped to be on or within the unit circle.

typedef std::function<bool(float *x, float *y)> get_values_fn

function for gettin x/y values for the joystick.

Param x

Pointer to the x value. Function should fill this variable with the latest reading.

Param y

Pointer to the y value. Function should fill this variable with the latest reading.

Return

True if the values were able to be retrieved, false otherwise.

Public Functions

explicit Joystick(const espp::Joystick::Config &config)

Initalize the joystick using the provided configuration.

Parameters

configConfig structure with initialization information.

void set_type(espp::Joystick::Type type, float radius = 0, float range_deadzone = 0)

Set the type of the joystick.

See also

set_calibration

Note

If the Joystick is Type::CIRCULAR, the actual calibrations that are saved into the joystick will have 0 deadzone around the center value and range values, so that center and range deadzones are actually applied on the vector value instead of on the individual axes independently.

Parameters
  • type – The Type of the joystick.

  • radius – Optional radius parameter used when type is Type::CIRCULAR. When the magnitude of the joystick’s mapped position vector is less than this value, the vector is set to (0,0).

  • range_deadzone – Optional deadzone around the edge of the unit circle when type is Type::CIRCULAR. This scales the output so that the output appears to have magnitude 1 (meaning it appears to be on the edge of the unit circle) if the magnitude of the mapped position vector is greater than 1-range_deadzone. Example: if the range deadzone is 0.1, then the output will be scaled so that the magnitude of the output is 1 if the magnitude of the mapped position vector is greater than 0.9.

espp::Joystick::Type type() const

Get the type of the joystick.

Returns

The Type of the joystick.

void set_center_deadzone_radius(float radius)

Sets the center deadzone radius.

Note

Radius is only applied when deadzone is Deadzone::CIRCULAR.

Parameters

radius – Optional radius parameter used when deadzone is Deadzone::CIRCULAR. When the magnitude of the joystick’s mapped position vector is less than this value, the vector is set to (0,0).

float center_deadzone_radius() const

Get the center deadzone radius.

Returns

The center deadzone radius.

void set_range_deadzone(float range_deadzone)

Sets the range deadzone.

Note

Range deadzone is only applied when deadzone is Deadzone::CIRCULAR.

Parameters

range_deadzone – Optional deadzone around the edge of the unit circle when deadzone is Deadzone::CIRCULAR. This scales the output so that the output appears to have magnitude 1 (meaning it appears to be on the edge of the unit circle) if the magnitude of the mapped position vector is greater than 1-range_deadzone. Example: if the range deadzone is 0.1, then the output will be scaled so that the magnitude of the output is 1 if the magnitude of the mapped position vector is greater than 0.9.

float range_deadzone() const

Get the range deadzone.

Returns

The range deadzone.

void set_calibration(const espp::FloatRangeMapper::Config &x_calibration, const espp::FloatRangeMapper::Config &y_calibration, float center_deadzone_radius = 0, float range_deadzone = 0)

Update the x and y axis mapping.

Note

If the Joystick is Type::CIRCULAR, the actual calibrations that are saved into the joystick will have 0 deadzone around the center and range values, so that center and range deadzones are actually applied on the vector value.

Parameters
  • x_calibration – New x-axis range mapping configuration to use.

  • y_calibration – New y-axis range mapping configuration to use.

  • center_deadzone_radius – The radius of the unit circle’s deadzone [0, 1.0f] around the center, only used when the joystick is configured as Type::CIRCULAR.

  • range_deadzone – Optional deadzone around the edge of the unit circle when type is Type::CIRCULAR. This scales the output so that the output appears to have magnitude 1 (meaning it appears to be on the edge of the unit circle) if the magnitude of the mapped position vector is greater than 1-range_deadzone. Example: if the range deadzone is 0.1, then the output will be scaled so that the magnitude of the output is 1 if the magnitude of the mapped position vector is greater than 0.9.

void update()

Read the raw values and use the calibration data to update the position.

Note

Requires that the get_values_ function is set.

void update(float raw_x, float raw_y)

Update the joystick’s position using the provided raw x and y values.

Note

This function is useful when you have the raw values and don’t want to use the get_values_ function.

Parameters
  • raw_x – The raw x-axis value.

  • raw_y – The raw y-axis value.

float x() const

Get the most recently updated x axis calibrated position.

Returns

The most recent x-axis position (from when update() was last called).

float y() const

Get the most recently updated y axis calibrated position.

Returns

The most recent y-axis position (from when update() was last called).

espp::Vector2f position() const

Get the most recently updated calibrated position.

Returns

The most recent position (from when update() was last called).

espp::Vector2f raw() const

Get the most recently updated raw / uncalibrated readings. This function is useful for externally performing a calibration routine and creating updated calibration / mapper configuration structures.

Returns

The most recent raw measurements (from when update() was last called).

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters

rate_limit – The rate limit to use for the logger

struct Config

Configuration structure for the joystick.

Public Members

espp::FloatRangeMapper::Config x_calibration

Configuration for the x axis.

espp::FloatRangeMapper::Config y_calibration

Configuration for the y axis.

espp::Joystick::Type type = {espp::Joystick::Type::RECTANGULAR}

The type of the joystick. See Type enum for more information.

float center_deadzone_radius{0}

The radius of the unit circle’s deadzone [0, 1.0f] around the center, only used when the joystick is configured as Type::CIRCULAR.

float range_deadzone = {0}

The deadzone around the edge of the unit circle, only used when the joystick is configured as Type::CIRCULAR. This scales the output so that the output appears to have magnitude 1 (meaning it appears to be on the edge of the unit circle) when the joystick value magnitude is within the range [1-range_deadzone, 1].

espp::Joystick::get_values_fn get_values = {nullptr}

Function to retrieve the latest unmapped joystick values. Required if you want to use update(), unused if you call update(float raw_x, float raw_y).

espp::Logger::Verbosity log_level = {espp::Logger::Verbosity::WARN}

Verbosity for the Joystick logger_.