-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathcontroller.hpp
329 lines (309 loc) · 12.4 KB
/
controller.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
#pragma once
#include <mutex>
#include "driver/dedic_gpio.h"
#include "driver/gpio.h"
#include "joystick.hpp"
#include "logger.hpp"
namespace espp {
/**
* @brief Class for managing controller input.
*
* The controller can be configured to either use a digital d-pad or an analog
* 2-axis joystick with select button.
*
* Digital configuration can support ABXY, start, select, and 4 digital
* directional inputs.
*
* Anaolg Joystick Configuration can support ABXY, start, select, two axis
* (analog) joystick, and joystick select button. It will also convert the
* joystick analog values into digital d-pad buttons.
*
* \section controller_ex1 Digital Controller Example
* \snippet controller_example.cpp digital controller example
* \section controller_ex2 Analog Controller Example
* \snippet controller_example.cpp analog controller example
* \section controller_ex3 I2C Analog Controller Example
* \snippet controller_example.cpp i2c analog controller example
*/
class Controller {
public:
/**
* @brief The buttons that the controller supports.
*/
enum class Button : int {
A = 0,
B,
X,
Y,
SELECT,
START,
UP,
DOWN,
LEFT,
RIGHT,
JOYSTICK_SELECT,
LAST_UNUSED
};
/**
* @brief Packed bit structure containing the state (pressed=1) of each
* button.
*/
struct State {
uint32_t a : 1; ///< State of the A button
uint32_t b : 1; ///< State of the B button
uint32_t x : 1; ///< State of the X button
uint32_t y : 1; ///< State of the Y button
uint32_t select : 1; ///< State of the SELECT button
uint32_t start : 1; ///< State of the START button
uint32_t up : 1; ///< State of the UP button
uint32_t down : 1; ///< State of the DOWN button
uint32_t left : 1; ///< State of the LEFT button
uint32_t right : 1; ///< State of the RIGHT button
uint32_t joystick_select : 1; ///< State of the Joystick Select button
};
/**
* @brief Configuration for the controller to use d-pad only, no joystick.
*/
struct DigitalConfig {
bool active_low{true}; ///< Whether the buttons are active-low (default) or not.
int gpio_a{-1}; ///< GPIO for the A button
int gpio_b{-1}; ///< GPIO for the B button
int gpio_x{-1}; ///< GPIO for the X button
int gpio_y{-1}; ///< GPIO for the Y button
int gpio_start{-1}; ///< GPIO for the START button
int gpio_select{-1}; ///< GPIO for the SELECT button
int gpio_up{-1}; ///< GPIO for the UP button
int gpio_down{-1}; ///< GPIO for the DOWN button
int gpio_left{-1}; ///< GPIO for the LEFT button
int gpio_right{-1}; ///< GPIO for the RIGHT button
espp::Logger::Verbosity log_level{espp::Logger::Verbosity::WARN}; ///< Verbosity for the logger.
};
/**
* @brief Configuration for the controller to be used with a joystick (which
* has a center-press / select button), no d-pad.
* @note In this configuration, the controller will create and manage a
* joystick component to read the analog axes of the joystick and
* convert them into digital up/down/left/right signals as a virtual
* d-pad.
*/
struct AnalogJoystickConfig {
bool active_low{true}; ///< Whether the buttons are active-low (default) or not.
int gpio_a{-1}; ///< GPIO for the A button
int gpio_b{-1}; ///< GPIO for the B button
int gpio_x{-1}; ///< GPIO for the X button
int gpio_y{-1}; ///< GPIO for the Y button
int gpio_start{-1}; ///< GPIO for the START button
int gpio_select{-1}; ///< GPIO for the SELECT button
int gpio_joystick_select{-1}; ///< GPIO for the JOYSTICK SELECT button
espp::Joystick::Config joystick_config; ///< Configuration for the analog joystick which will be
///< used to read digital direction values.
espp::Logger::Verbosity log_level{espp::Logger::Verbosity::WARN}; ///< Verbosity for the logger.
};
/**
* @brief Configuration for the controller to be used with both a joystick (which
* has a center-press / select button), and a d-pad.
* @note In this configuration, the controller will NOT create / manage a
* joystick component. The configured d-pad provides the digital
* directions so the analog joystick values should be retrieved from a
* separately managed joystick component.
*/
struct DualConfig {
bool active_low{true}; ///< Whether the buttons are active-low (default) or not.
int gpio_a{-1}; ///< GPIO for the A button
int gpio_b{-1}; ///< GPIO for the B button
int gpio_x{-1}; ///< GPIO for the X button
int gpio_y{-1}; ///< GPIO for the Y button
int gpio_start{-1}; ///< GPIO for the START button
int gpio_select{-1}; ///< GPIO for the SELECT button
int gpio_up{-1}; ///< GPIO for the UP button
int gpio_down{-1}; ///< GPIO for the DOWN button
int gpio_left{-1}; ///< GPIO for the LEFT button
int gpio_right{-1}; ///< GPIO for the RIGHT button
int gpio_joystick_select{-1}; ///< GPIO for the JOYSTICK SELECT button
espp::Logger::Verbosity log_level{espp::Logger::Verbosity::WARN}; ///< Verbosity for the logger.
};
/**
* @brief Create a Digital controller.
*/
Controller(const DigitalConfig &config)
: logger_({.tag = "Digital Controller", .level = config.log_level}) {
gpio_.assign((int)Button::LAST_UNUSED, -1);
input_state_.assign((int)Button::LAST_UNUSED, false);
gpio_[(int)Button::A] = config.gpio_a;
gpio_[(int)Button::B] = config.gpio_b;
gpio_[(int)Button::X] = config.gpio_x;
gpio_[(int)Button::Y] = config.gpio_y;
gpio_[(int)Button::START] = config.gpio_start;
gpio_[(int)Button::SELECT] = config.gpio_select;
gpio_[(int)Button::UP] = config.gpio_up;
gpio_[(int)Button::DOWN] = config.gpio_down;
gpio_[(int)Button::LEFT] = config.gpio_left;
gpio_[(int)Button::RIGHT] = config.gpio_right;
init_gpio(config.active_low);
}
/**
* @brief Create an analog joystick controller.
*/
Controller(const AnalogJoystickConfig &config)
: joystick_(std::make_unique<espp::Joystick>(config.joystick_config)),
logger_({.tag = "Analog Joystick Controller", .level = config.log_level}) {
gpio_.assign((int)Button::LAST_UNUSED, -1);
input_state_.assign((int)Button::LAST_UNUSED, false);
gpio_[(int)Button::A] = config.gpio_a;
gpio_[(int)Button::B] = config.gpio_b;
gpio_[(int)Button::X] = config.gpio_x;
gpio_[(int)Button::Y] = config.gpio_y;
gpio_[(int)Button::START] = config.gpio_start;
gpio_[(int)Button::SELECT] = config.gpio_select;
gpio_[(int)Button::JOYSTICK_SELECT] = config.gpio_joystick_select;
init_gpio(config.active_low);
}
/**
* @brief Create a dual d-pad + analog joystick controller.
*/
Controller(const DualConfig &config)
: logger_({.tag = "Dual Digital Controller", .level = config.log_level}) {
gpio_.assign((int)Button::LAST_UNUSED, -1);
input_state_.assign((int)Button::LAST_UNUSED, false);
gpio_[(int)Button::A] = config.gpio_a;
gpio_[(int)Button::B] = config.gpio_b;
gpio_[(int)Button::X] = config.gpio_x;
gpio_[(int)Button::Y] = config.gpio_y;
gpio_[(int)Button::START] = config.gpio_start;
gpio_[(int)Button::SELECT] = config.gpio_select;
gpio_[(int)Button::UP] = config.gpio_up;
gpio_[(int)Button::DOWN] = config.gpio_down;
gpio_[(int)Button::LEFT] = config.gpio_left;
gpio_[(int)Button::RIGHT] = config.gpio_right;
gpio_[(int)Button::JOYSTICK_SELECT] = config.gpio_joystick_select;
init_gpio(config.active_low);
}
/**
* @brief Destroys the controller and deletes the associated dedicated GPIO
* bundle.
*/
~Controller() { dedic_gpio_del_bundle(gpio_bundle_); }
/**
* @brief Get the most recent state structure for the controller.
* @return State structure for the inputs - updated when update() was last
* called.
*/
State get_state() {
logger_.debug("Returning state structure");
std::scoped_lock<std::mutex> lk(state_mutex_);
return State{
.a = input_state_[(int)Button::A],
.b = input_state_[(int)Button::B],
.x = input_state_[(int)Button::X],
.y = input_state_[(int)Button::Y],
.select = input_state_[(int)Button::SELECT],
.start = input_state_[(int)Button::START],
.up = input_state_[(int)Button::UP],
.down = input_state_[(int)Button::DOWN],
.left = input_state_[(int)Button::LEFT],
.right = input_state_[(int)Button::RIGHT],
.joystick_select = input_state_[(int)Button::JOYSTICK_SELECT],
};
}
/**
* @brief Return true if the \p input was pressed, false otherwise.
* @param input The Button of interest.
* @return True if \p input was pressed last time update() was called.
*/
bool is_pressed(const Button input) {
std::scoped_lock<std::mutex> lk(state_mutex_);
return input_state_[(int)input];
}
/**
* @brief Read the current button values and update the internal input state
* accordingly.
*/
void update() {
logger_.debug("Reading gpio bundle");
// read the updated state for configured gpios (all at once)
uint32_t pin_state = dedic_gpio_bundle_read_in(gpio_bundle_);
// when setting up the dedic gpio, we removed the gpio that were not
// configured (-1) in our vector, but the returned bitmask simply orders
// them (low bit is low member in originally provided vector) so we need to
// track the actual bit corresponding to the pin in the pin_state.
int bit = 0;
// and pull out the state into the vector accordingly
logger_.debug("Parsing bundle state from pin state 0x{:04X}", pin_state);
{
std::scoped_lock<std::mutex> lk(state_mutex_);
for (int i = 0; i < gpio_.size(); i++) {
auto gpio = gpio_[i];
if (gpio != -1) {
input_state_[i] = is_bit_set(pin_state, bit);
// this pin is used, increment the bit index
bit++;
} else {
input_state_[i] = false;
}
}
}
// now update the joystick if we have it
if (joystick_) {
logger_.debug("Updating joystick");
joystick_->update();
// now update the d-pad state if the joystick values are high enough
float x = joystick_->x();
float y = joystick_->y();
logger_.debug("Got joystick x,y: ({},{})", x, y);
std::scoped_lock<std::mutex> lk(state_mutex_);
if (x > 0.5f) {
input_state_[(int)Button::RIGHT] = true;
} else if (x < -0.5f) {
input_state_[(int)Button::LEFT] = true;
}
if (y > 0.5f) {
input_state_[(int)Button::UP] = true;
} else if (y < -0.5f) {
input_state_[(int)Button::DOWN] = true;
}
}
}
protected:
bool is_bit_set(uint32_t data, int bit) { return (data & (1 << bit)) != 0; }
void init_gpio(bool active_low) {
// select only the gpios that are used (not -1)
std::vector<int> actual_gpios;
for (auto gpio : gpio_) {
if (gpio != -1) {
actual_gpios.push_back(gpio);
}
}
uint64_t pin_mask = 0;
for (auto gpio : actual_gpios) {
pin_mask |= (1ULL << gpio);
}
gpio_config_t io_config = {
.pin_bit_mask = pin_mask,
.mode = GPIO_MODE_INPUT,
.pull_up_en = active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
.pull_down_en = active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_config));
// Create gpio_bundle_, input only
dedic_gpio_bundle_config_t gpio_bundle_config = {
.gpio_array = actual_gpios.data(),
.array_size = actual_gpios.size(),
.flags =
{
.in_en = 1,
.in_invert = (unsigned int)(active_low ? 1 : 0),
.out_en = 0, // we _could_ enable input & output but we don't want to
.out_invert = 0,
},
};
ESP_ERROR_CHECK(dedic_gpio_new_bundle(&gpio_bundle_config, &gpio_bundle_));
}
std::mutex state_mutex_;
std::vector<int> gpio_;
std::vector<bool> input_state_;
dedic_gpio_bundle_handle_t gpio_bundle_{NULL};
std::unique_ptr<espp::Joystick> joystick_;
espp::Logger logger_;
};
} // namespace espp