Files
watch-watch/main/usb_cdc_cli.c

389 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "usb_cdc_cli.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "dcdc_controller.h"
#include "esp_check.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "ws2812_status.h"
#include "ina226_monitor.h"
#include "uart_mux.h"
#define CLI_LINE_MAX_LEN 128
#define CLI_QUEUE_LEN 8
#ifndef CONFIG_TINYUSB_CDC_RX_BUFSIZE
#define CONFIG_TINYUSB_CDC_RX_BUFSIZE 64
#endif
#ifndef CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN
#define CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN 128
#endif
typedef struct {
size_t length;
uint8_t data[CONFIG_TINYUSB_CDC_RX_BUFSIZE];
} usb_cli_msg_t;
static const char *TAG = "usb_cli";
static QueueHandle_t s_cli_queue;
static bool s_cli_initialized;
static bool s_host_ready;
static void usb_cli_task(void *arg);
static void usb_cli_process_line(char *line);
static void usb_cli_prompt(void);
static void usb_cli_write_raw(const char *data, size_t len)
{
if (!s_host_ready || len == 0) {
return;
}
if (tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (uint8_t *)data, len) == ESP_OK) {
esp_err_t err = tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "CDC flush error: %s", esp_err_to_name(err));
}
}
}
static void usb_cli_printf(const char *fmt, ...)
{
if (!s_host_ready) {
return;
}
char buffer[192];
va_list args;
va_start(args, fmt);
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
if (len > 0) {
if (len >= (int)sizeof(buffer)) {
len = (int)sizeof(buffer) - 1;
}
usb_cli_write_raw(buffer, len);
}
}
static const char *PROMPT = "\r\nwatch> ";
static char *usb_cli_trim(char *str)
{
while (*str && isspace((unsigned char)*str)) {
str++;
}
char *end = str + strlen(str);
while (end > str && isspace((unsigned char)*(end - 1))) {
*(--end) = '\0';
}
return str;
}
static bool usb_cli_parse_channel(const char *token, dcdc_channel_t *channel)
{
if (!token) {
return false;
}
char *end = NULL;
long val = strtol(token, &end, 10);
if (end == token || val < 0 || val >= DCDC_CHANNEL_COUNT) {
return false;
}
*channel = (dcdc_channel_t)val;
return true;
}
static void usb_cli_print_status(void)
{
usb_cli_printf("\r\nСтан каналів:\r\n");
for (size_t i = 0; i < dcdc_channel_count(); ++i) {
dcdc_channel_t ch = (dcdc_channel_t)i;
usb_cli_printf(" - CH%u: %s (GPIO %d)\r\n",
(unsigned)i,
dcdc_get_state(ch) ? "ON" : "OFF",
(int)dcdc_get_gpio(ch));
}
}
static void usb_cli_prompt(void)
{
usb_cli_write_raw(PROMPT, strlen(PROMPT));
}
static void usb_cli_handle_switch(const char *cmd, char *args)
{
dcdc_channel_t channel;
char *ctx = NULL;
char *channel_str = strtok_r(args, " ", &ctx);
if (!usb_cli_parse_channel(channel_str, &channel)) {
usb_cli_printf("\r\nНевірний номер каналу\r\n");
return;
}
esp_err_t err = ESP_OK;
if (strcasecmp(cmd, "enable") == 0) {
err = dcdc_enable(channel);
} else if (strcasecmp(cmd, "disable") == 0) {
err = dcdc_disable(channel);
} else {
err = dcdc_toggle(channel);
}
if (err != ESP_OK) {
usb_cli_printf("\r\nПомилка керування каналом: %s\r\n", esp_err_to_name(err));
} else {
bool state = dcdc_get_state(channel);
usb_cli_printf("\r\nКанал %d -> %s\r\n", channel, state ? "ON" : "OFF");
ws2812_status_set_channel_state(channel, state);
}
}
static void usb_cli_print_measurement(size_t channel, const ina226_reading_t *reading)
{
usb_cli_printf("\r\nCH%u: %.2f В, %.1f мА, %.1f мВт",
(unsigned)channel, reading->voltage_v, reading->current_ma, reading->power_mw);
}
static void usb_cli_handle_sense(char *args)
{
if (!ina226_monitor_ready()) {
usb_cli_printf("\r\nМоніторинг INA226 недоступний\r\n");
return;
}
ina226_reading_t reading = {0};
if (ina226_monitor_sample(&reading) == ESP_OK) {
usb_cli_print_measurement(0, &reading);
} else {
usb_cli_printf("\r\nНе вдалося зчитати дані з INA226\r\n");
}
}
static void usb_cli_handle_uart(char *args)
{
if (!uart_mux_ready()) {
usb_cli_printf("\r\nUART мультиплексор недоступний\r\n");
return;
}
char *ctx = NULL;
char *action = strtok_r(args, " ", &ctx);
if (!action) {
usb_cli_printf("\r\nUART usage: uart send <channel> <text> | uart read <channel> [len]\r\n");
return;
}
char *channel_str = strtok_r(NULL, " ", &ctx);
dcdc_channel_t channel;
if (!usb_cli_parse_channel(channel_str, &channel)) {
usb_cli_printf("\r\nНевірний номер каналу\r\n");
return;
}
if (strcasecmp(action, "send") == 0) {
char *payload = ctx;
if (!payload || *payload == '\0') {
usb_cli_printf("\r\nНемає даних для відправлення\r\n");
return;
}
size_t len = strlen(payload);
if (uart_mux_write(channel, (const uint8_t *)payload, len, pdMS_TO_TICKS(200)) == ESP_OK) {
usb_cli_printf("\r\nВідправлено %u байт\r\n", (unsigned)len);
} else {
usb_cli_printf("\r\nПомилка відправлення UART\r\n");
}
} else if (strcasecmp(action, "read") == 0) {
char *len_str = ctx ? strtok_r(NULL, " ", &ctx) : NULL;
size_t req_len = len_str ? strtoul(len_str, NULL, 10) : CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN;
if (req_len == 0) {
req_len = CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN;
}
if (req_len > 256) {
req_len = 256;
}
uint8_t buffer[256];
size_t received = 0;
if (uart_mux_read(channel, buffer, req_len, &received, pdMS_TO_TICKS(200)) == ESP_OK && received > 0) {
usb_cli_printf("\r\nCH%u UART (%u байт): ", (unsigned)channel, (unsigned)received);
for (size_t i = 0; i < received; ++i) {
usb_cli_write_raw((char *)&buffer[i], 1);
}
} else {
usb_cli_printf("\r\nДані відсутні\r\n");
}
} else {
usb_cli_printf("\r\nНевірна дія '%s'\r\n", action);
}
}
static void usb_cli_process_line(char *line)
{
char *trimmed = usb_cli_trim(line);
if (*trimmed == '\0') {
return;
}
char *save_ptr = NULL;
char *cmd = strtok_r(trimmed, " ", &save_ptr);
if (!cmd) {
return;
}
if (strcasecmp(cmd, "help") == 0) {
usb_cli_printf(
"\r\nДоступні команди:\r\n"
" help - показати довідку\r\n"
" status - стан всіх каналів\r\n"
" enable <n> - увімкнути канал n\r\n"
" disable <n> - вимкнути канал n\r\n"
" toggle <n> - перемкнути канал n\r\n"
" sense [n] - показати напругу/струм/потужність (опц. канал)\r\n"
" uart send <n> <msg> - відправити повідомлення Pi n\r\n"
" uart read <n> [len] - прочитати дані з Pi n\r\n");
} else if (strcasecmp(cmd, "status") == 0) {
usb_cli_print_status();
} else if (strcasecmp(cmd, "enable") == 0 ||
strcasecmp(cmd, "disable") == 0 ||
strcasecmp(cmd, "toggle") == 0) {
usb_cli_handle_switch(cmd, save_ptr);
} else if (strcasecmp(cmd, "sense") == 0) {
usb_cli_handle_sense(save_ptr);
} else if (strcasecmp(cmd, "uart") == 0) {
usb_cli_handle_uart(save_ptr);
} else {
usb_cli_printf("\r\nНевідома команда '%s'\r\n", cmd);
}
}
static void usb_cli_task(void *arg)
{
usb_cli_msg_t msg;
char line[CLI_LINE_MAX_LEN];
size_t line_len = 0;
while (xQueueReceive(s_cli_queue, &msg, portMAX_DELAY)) {
for (size_t i = 0; i < msg.length; ++i) {
char ch = (char)msg.data[i];
if (ch == '\r' || ch == '\n') {
if (line_len > 0) {
line[line_len] = '\0';
usb_cli_process_line(line);
line_len = 0;
}
usb_cli_prompt();
} else if (ch == 0x7F || ch == '\b') {
if (line_len > 0) {
line_len--;
}
} else if (isprint((unsigned char)ch)) {
if (line_len < CLI_LINE_MAX_LEN - 1) {
line[line_len++] = ch;
} else {
usb_cli_printf("\r\nРядок занадто довгий\r\n");
line_len = 0;
usb_cli_prompt();
}
}
}
}
}
static void usb_cdc_rx_callback(int itf, cdcacm_event_t *event)
{
if (!s_cli_queue) {
return;
}
usb_cli_msg_t msg = { 0 };
size_t rx_size = 0;
esp_err_t ret = tinyusb_cdcacm_read(itf, msg.data, sizeof(msg.data), &rx_size);
if (ret == ESP_OK && rx_size > 0) {
msg.length = rx_size;
if (xQueueSend(s_cli_queue, &msg, 0) != pdTRUE) {
ESP_LOGW(TAG, "CLI queue overflow, втрачено %u байт", (unsigned)rx_size);
}
}
}
static void usb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
{
const bool dtr = event->line_state_changed_data.dtr;
const bool rts = event->line_state_changed_data.rts;
ESP_LOGI(TAG, "CDC лінія %d змінилась: DTR=%d RTS=%d", itf, dtr, rts);
s_host_ready = dtr;
if (s_host_ready) {
usb_cli_printf("\r\nwatch-watch CLI готовий. Надрукуйте 'help'.\r\n");
usb_cli_prompt();
}
}
esp_err_t usb_cdc_cli_init(void)
{
if (s_cli_initialized) {
return ESP_OK;
}
s_cli_queue = xQueueCreate(CLI_QUEUE_LEN, sizeof(usb_cli_msg_t));
if (!s_cli_queue) {
return ESP_ERR_NO_MEM;
}
if (xTaskCreate(usb_cli_task, "usb_cli", 4096, NULL, 5, NULL) != pdPASS) {
vQueueDelete(s_cli_queue);
s_cli_queue = NULL;
return ESP_ERR_NO_MEM;
}
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif
};
ESP_RETURN_ON_ERROR(tinyusb_driver_install(&tusb_cfg), TAG, "TinyUSB init failed");
const tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
.callback_rx = usb_cdc_rx_callback,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL,
};
ESP_RETURN_ON_ERROR(tusb_cdc_acm_init(&acm_cfg), TAG, "CDC init failed");
ESP_RETURN_ON_ERROR(
tinyusb_cdcacm_register_callback(TINYUSB_CDC_ACM_0,
CDC_EVENT_LINE_STATE_CHANGED,
usb_cdc_line_state_changed_callback),
TAG,
"Line state callback error");
s_cli_initialized = true;
ESP_LOGI(TAG, "USB CDC CLI ініціалізовано");
return ESP_OK;
}