CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
GpioSerialCommands.cpp
Go to the documentation of this file.
1
19
20#include "cdc_log.h"
23#include "HexUtil.h"
24
25#include "driver/gpio.h"
26#include "driver/i2c.h"
27#include "esp_adc/adc_oneshot.h"
28
29#include <cstdio>
30#include <cstdlib>
31#include <cstring>
32
33namespace cdc::plugin_manager {
34
35static const char* TAG = "GPIO_CMD";
36static const char* CMD_MODULE = "gpio";
37
38namespace {
39
40inline bool is_allowed(uint8_t pin) { return gpio_policy::isAllowed(pin); }
41
42void send(const char* line) { std::printf("%s\n", line); }
43void sendf(const char* fmt, ...)
44{
45 va_list ap;
46 va_start(ap, fmt);
47 std::vprintf(fmt, ap);
48 va_end(ap);
49 std::printf("\n");
50}
51
52void cmd_list()
53{
54 send("pin status");
55 for (size_t i = 0; i < gpio_policy::ALLOWED_COUNT; ++i) {
56 sendf(" %2u free", static_cast<unsigned>(gpio_policy::ALLOWED[i]));
57 }
58}
59
60void cmd_mode(const char* args)
61{
62 char dir_buf[16] = {0};
63 char pull_buf[16] = "none";
64 unsigned long pin_l = 0;
65 int n = std::sscanf(args, "%lu %15s %15s", &pin_l, dir_buf, pull_buf);
66 if (n < 2) { send("ERR usage_pin_mode_pull"); return; }
67 uint8_t pin = static_cast<uint8_t>(pin_l);
68 if (!is_allowed(pin)) { send("ERR pin_not_allowed"); return; }
69
70 gpio_mode_t mode;
71 if (std::strcmp(dir_buf, "in") == 0) mode = GPIO_MODE_INPUT;
72 else if (std::strcmp(dir_buf, "out") == 0) mode = GPIO_MODE_OUTPUT;
73 else if (std::strcmp(dir_buf, "out_od") == 0) mode = GPIO_MODE_OUTPUT_OD;
74 else { send("ERR mode_in_out_out_od"); return; }
75
76 gpio_pull_mode_t pull = GPIO_FLOATING;
77 if (std::strcmp(pull_buf, "pull_up") == 0) pull = GPIO_PULLUP_ONLY;
78 else if (std::strcmp(pull_buf, "pull_down") == 0) pull = GPIO_PULLDOWN_ONLY;
79
80 gpio_config_t cfg{};
81 cfg.pin_bit_mask = 1ULL << pin;
82 cfg.intr_type = GPIO_INTR_DISABLE;
83 cfg.mode = mode;
84 cfg.pull_up_en = pull == GPIO_PULLUP_ONLY ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
85 cfg.pull_down_en = pull == GPIO_PULLDOWN_ONLY ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
86 if (gpio_config(&cfg) != ESP_OK) { send("ERR gpio_config"); return; }
87 send("OK");
88}
89
90void cmd_write(const char* args)
91{
92 unsigned long pin_l = 0;
93 int level = 0;
94 if (std::sscanf(args, "%lu %d", &pin_l, &level) != 2) { send("ERR usage_pin_level"); return; }
95 uint8_t pin = static_cast<uint8_t>(pin_l);
96 if (!is_allowed(pin)) { send("ERR pin_not_allowed"); return; }
97 if (gpio_set_level(static_cast<gpio_num_t>(pin), level ? 1 : 0) != ESP_OK) {
98 send("ERR set_level"); return;
99 }
100 send("OK");
101}
102
103void cmd_read(const char* args)
104{
105 unsigned long pin_l = 0;
106 if (std::sscanf(args, "%lu", &pin_l) != 1) { send("ERR usage_pin"); return; }
107 uint8_t pin = static_cast<uint8_t>(pin_l);
108 if (!is_allowed(pin)) { send("ERR pin_not_allowed"); return; }
109 sendf("%d", gpio_get_level(static_cast<gpio_num_t>(pin)));
110}
111
112void cmd_release(const char* args)
113{
114 unsigned long pin_l = 0;
115 if (std::sscanf(args, "%lu", &pin_l) != 1) { send("ERR usage_pin"); return; }
116 uint8_t pin = static_cast<uint8_t>(pin_l);
117 if (!is_allowed(pin)) { send("ERR pin_not_allowed"); return; }
118 gpio_reset_pin(static_cast<gpio_num_t>(pin));
119 send("OK");
120}
121
122void gpioDispatch(const char* args)
123{
124 if (!args || !*args) { send("ERR missing_subcmd"); return; }
125 while (*args == ' ') args++;
126 const char* space = std::strchr(args, ' ');
127 size_t sub_len = space ? static_cast<size_t>(space - args) : std::strlen(args);
128 const char* rest = space ? space + 1 : "";
129
130 auto eq = [&](const char* w) {
131 return std::strlen(w) == sub_len && strncasecmp(args, w, sub_len) == 0;
132 };
133
134 if (eq("LIST")) cmd_list();
135 else if (eq("MODE")) cmd_mode(rest);
136 else if (eq("WRITE")) cmd_write(rest);
137 else if (eq("READ")) cmd_read(rest);
138 else if (eq("RELEASE")) cmd_release(rest);
139 else send("ERR unknown_subcmd");
140}
141
142// ESP32-S3 ADC1 channel mapping for the user-accessible pins.
143// (ADC2 is unreliable when WiFi is active, so we expose only ADC1 here.)
144struct AdcMap { uint8_t pin; adc_channel_t ch; };
145constexpr AdcMap ADC1_PINS[] = {
146 { 2, ADC_CHANNEL_1 }, { 3, ADC_CHANNEL_2 },
147 { 4, ADC_CHANNEL_3 }, { 5, ADC_CHANNEL_4 },
148 { 6, ADC_CHANNEL_5 }, { 7, ADC_CHANNEL_6 },
149 { 9, ADC_CHANNEL_8 },
150};
151
152bool pin_to_adc(uint8_t pin, adc_channel_t& ch_out) {
153 for (const auto& m : ADC1_PINS) {
154 if (m.pin == pin) { ch_out = m.ch; return true; }
155 }
156 return false;
157}
158
159void adcDispatch(const char* args)
160{
161 if (!args || strncasecmp(args, "READ", 4) != 0) { send("ERR usage_ADC_READ_pin"); return; }
162 while (*args && *args != ' ') args++;
163 while (*args == ' ') args++;
164 unsigned long pin_l = 0;
165 if (std::sscanf(args, "%lu", &pin_l) != 1) { send("ERR usage_pin"); return; }
166 uint8_t pin = static_cast<uint8_t>(pin_l);
167
168 adc_channel_t ch;
169 if (!pin_to_adc(pin, ch)) { send("ERR pin_no_adc1"); return; }
170
171 adc_oneshot_unit_handle_t unit;
172 adc_oneshot_unit_init_cfg_t unit_cfg{};
173 unit_cfg.unit_id = ADC_UNIT_1;
174 unit_cfg.ulp_mode = ADC_ULP_MODE_DISABLE;
175 if (adc_oneshot_new_unit(&unit_cfg, &unit) != ESP_OK) { send("ERR adc_unit"); return; }
176
177 adc_oneshot_chan_cfg_t chan_cfg{};
178 chan_cfg.atten = ADC_ATTEN_DB_12;
179 chan_cfg.bitwidth = ADC_BITWIDTH_DEFAULT;
180 if (adc_oneshot_config_channel(unit, ch, &chan_cfg) != ESP_OK) {
181 send("ERR adc_chan_config");
182 adc_oneshot_del_unit(unit);
183 return;
184 }
185 int raw = 0;
186 if (adc_oneshot_read(unit, ch, &raw) != ESP_OK) { send("ERR adc_read"); }
187 else sendf("raw=%d", raw);
188 adc_oneshot_del_unit(unit);
189}
190
191void i2cDispatch(const char* args)
192{
193 if (!args || strncasecmp(args, "SCAN", 4) != 0) { send("ERR usage_I2C_SCAN_bus"); return; }
194 while (*args && *args != ' ') args++;
195 while (*args == ' ') args++;
196 unsigned long bus_l = 1;
197 std::sscanf(args, "%lu", &bus_l);
198 if (bus_l == 0) { send("ERR bus0_reserved"); return; }
199
200 i2c_port_t port = static_cast<i2c_port_t>(bus_l);
201 bool any = false;
202 for (uint8_t addr = 0x08; addr < 0x78; ++addr) {
203 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
204 i2c_master_start(cmd);
205 i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
206 i2c_master_stop(cmd);
207 esp_err_t err = i2c_master_cmd_begin(port, cmd, pdMS_TO_TICKS(20));
208 i2c_cmd_link_delete(cmd);
209 if (err == ESP_OK) {
210 sendf("0x%02X", addr);
211 any = true;
212 }
213 }
214 if (!any) send("(none)");
215}
216
217// SAO EEPROM (24CXX-style, I2C1 @ 0x50, 16-bit register address) ------------
218
219constexpr uint8_t SAO_EEPROM_ADDR = 0x50;
220constexpr i2c_port_t SAO_I2C_PORT = static_cast<i2c_port_t>(1);
221
222void sao_read(const char* args)
223{
224 unsigned long offset = 0, count = 0;
225 if (std::sscanf(args, "%lu %lu", &offset, &count) != 2) {
226 send("ERR usage_offset_count"); return;
227 }
228 if (count == 0 || count > 256) { send("ERR count_1_256"); return; }
229
230 uint8_t reg[2] = { static_cast<uint8_t>(offset >> 8), static_cast<uint8_t>(offset & 0xff) };
231 uint8_t buf[256] = {0};
232 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
233 i2c_master_start(cmd);
234 i2c_master_write_byte(cmd, (SAO_EEPROM_ADDR << 1) | I2C_MASTER_WRITE, true);
235 i2c_master_write(cmd, reg, sizeof(reg), true);
236 i2c_master_start(cmd);
237 i2c_master_write_byte(cmd, (SAO_EEPROM_ADDR << 1) | I2C_MASTER_READ, true);
238 i2c_master_read(cmd, buf, count, I2C_MASTER_LAST_NACK);
239 i2c_master_stop(cmd);
240 esp_err_t err = i2c_master_cmd_begin(SAO_I2C_PORT, cmd, pdMS_TO_TICKS(200));
241 i2c_cmd_link_delete(cmd);
242 if (err != ESP_OK) { send("ERR i2c"); return; }
243
244 char hex[513];
245 for (size_t i = 0; i < count; ++i) std::snprintf(hex + i * 2, 3, "%02X", buf[i]);
246 hex[count * 2] = '\0';
247 send(hex);
248}
249
250void sao_write(const char* args)
251{
252 unsigned long offset = 0;
253 int consumed = 0;
254 if (std::sscanf(args, "%lu %n", &offset, &consumed) != 1 || consumed == 0) {
255 send("ERR usage_offset_hex"); return;
256 }
257 const char* hex = args + consumed;
258 size_t hex_len = std::strlen(hex);
259 if (hex_len == 0 || hex_len % 2 != 0) { send("ERR hex_len"); return; }
260 size_t bytes = hex_len / 2;
261 if (bytes > 128) { send("ERR max_128"); return; }
262
263 uint8_t data[128];
264 for (size_t i = 0; i < bytes; ++i) {
265 int h = hex_val(hex[i * 2]), l = hex_val(hex[i * 2 + 1]);
266 if (h < 0 || l < 0) { send("ERR hex"); return; }
267 data[i] = static_cast<uint8_t>((h << 4) | l);
268 }
269
270 uint8_t reg[2] = { static_cast<uint8_t>(offset >> 8), static_cast<uint8_t>(offset & 0xff) };
271 i2c_cmd_handle_t cmd = i2c_cmd_link_create();
272 i2c_master_start(cmd);
273 i2c_master_write_byte(cmd, (SAO_EEPROM_ADDR << 1) | I2C_MASTER_WRITE, true);
274 i2c_master_write(cmd, reg, sizeof(reg), true);
275 i2c_master_write(cmd, data, bytes, true);
276 i2c_master_stop(cmd);
277 esp_err_t err = i2c_master_cmd_begin(SAO_I2C_PORT, cmd, pdMS_TO_TICKS(200));
278 i2c_cmd_link_delete(cmd);
279 if (err != ESP_OK) { send("ERR i2c"); return; }
280 send("OK");
281}
282
283void saoDispatch(const char* args)
284{
285 if (!args || !*args) { send("ERR missing_subcmd"); return; }
286 while (*args == ' ') args++;
287 const char* space = std::strchr(args, ' ');
288 size_t sub_len = space ? static_cast<size_t>(space - args) : std::strlen(args);
289 const char* rest = space ? space + 1 : "";
290
291 auto eq = [&](const char* w) {
292 return std::strlen(w) == sub_len && strncasecmp(args, w, sub_len) == 0;
293 };
294
295 if (eq("EEPROM_READ")) sao_read(rest);
296 else if (eq("EEPROM_WRITE")) sao_write(rest);
297 else send("ERR usage_SAO_EEPROM_READ_WRITE");
298}
299
300} // namespace
301
303{
305 reg.registerCommand({"GPIO", "GPIO LIST/MODE/WRITE/READ/RELEASE on user pins", gpioDispatch, CMD_MODULE, true});
306 reg.registerCommand({"ADC", "ADC READ <pin>", adcDispatch, CMD_MODULE, true});
307 reg.registerCommand({"I2C", "I2C SCAN <bus>", i2cDispatch, CMD_MODULE, true});
308 reg.registerCommand({"SAO", "SAO EEPROM_READ <off> <count> | EEPROM_WRITE <off> <hex>", saoDispatch, CMD_MODULE, true});
309 LOG_I(TAG, "GPIO/ADC/I2C/SAO serial commands registered");
310}
311
312} // namespace cdc::plugin_manager
Hex-digit decoding shared by the plugin_manager source files.
Single source of truth for plugin-accessible GPIO pins.
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define SAO_EEPROM_ADDR
Definition hw_config.h:49
static const char * CMD_MODULE
int hex_val(char c)
Convert a single hex digit to its numeric value.
Definition HexUtil.h:15
static const char * TAG
ICommandRegistry & getCommandRegistry()
Returns singleton command-registry interface.