vision/main/robot_link.c

192 lines
5.0 KiB
C

#include "robot_link.h"
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
static const char *TAG = "robot_link";
static SemaphoreHandle_t s_lock;
static uint32_t s_seq;
void robot_link_init(void)
{
s_lock = xSemaphoreCreateMutex();
s_seq = 0;
ESP_LOGI(TAG, "ready (I2C bridge stub — local ACK only)");
}
static uint32_t next_seq(void)
{
uint32_t seq = 0;
if (xSemaphoreTake(s_lock, portMAX_DELAY) == pdTRUE) {
seq = ++s_seq;
xSemaphoreGive(s_lock);
}
return seq;
}
robot_cmd_dir_t robot_link_parse_dir(const char *dir)
{
if (!dir) {
return ROBOT_CMD_INVALID;
}
if (strcmp(dir, "up") == 0) {
return ROBOT_CMD_UP;
}
if (strcmp(dir, "down") == 0) {
return ROBOT_CMD_DOWN;
}
if (strcmp(dir, "left") == 0) {
return ROBOT_CMD_LEFT;
}
if (strcmp(dir, "right") == 0) {
return ROBOT_CMD_RIGHT;
}
if (strcmp(dir, "stop") == 0) {
return ROBOT_CMD_STOP;
}
return ROBOT_CMD_INVALID;
}
/*
* Placeholder for future RP2040 I2C write.
* For now we only acknowledge that the ESP32 received the command.
*/
static esp_err_t forward_to_rp2040_stub(const char *kind, const char *payload)
{
ESP_LOGI(TAG, "ACK %s '%s' (no I2C yet)", kind, payload ? payload : "");
return ESP_OK;
}
esp_err_t robot_link_handle_cmd(const char *dir, robot_ack_t *ack)
{
if (!ack) {
return ESP_ERR_INVALID_ARG;
}
memset(ack, 0, sizeof(*ack));
ack->type = "cmd";
ack->seq = next_seq();
ack->dir = dir ? dir : "";
if (robot_link_parse_dir(dir) == ROBOT_CMD_INVALID) {
ack->ok = false;
ack->fwd = "none";
ack->error = "bad_dir";
ESP_LOGW(TAG, "reject cmd seq=%lu dir='%s'", (unsigned long)ack->seq,
ack->dir);
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = forward_to_rp2040_stub("cmd", dir);
if (err != ESP_OK) {
ack->ok = false;
ack->fwd = "fail";
ack->error = "forward_failed";
return err;
}
ack->ok = true;
ack->fwd = "ack";
return ESP_OK;
}
esp_err_t robot_link_handle_msg(const char *text, size_t len, robot_ack_t *ack)
{
if (!ack || !text) {
return ESP_ERR_INVALID_ARG;
}
memset(ack, 0, sizeof(*ack));
ack->type = "msg";
ack->seq = next_seq();
ack->text = text;
if (len == 0) {
ack->ok = false;
ack->fwd = "none";
ack->error = "empty";
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = forward_to_rp2040_stub("msg", text);
if (err != ESP_OK) {
ack->ok = false;
ack->fwd = "fail";
ack->error = "forward_failed";
return err;
}
ack->ok = true;
ack->fwd = "ack";
return ESP_OK;
}
static void json_escape_into(const char *in, char *out, size_t outlen)
{
size_t o = 0;
if (!in || outlen == 0) {
return;
}
for (size_t i = 0; in[i] != '\0' && o + 2 < outlen; i++) {
char c = in[i];
if (c == '"' || c == '\\') {
if (o + 3 >= outlen) {
break;
}
out[o++] = '\\';
out[o++] = c;
} else if ((unsigned char)c < 0x20) {
/* skip control chars */
continue;
} else {
out[o++] = c;
}
}
out[o] = '\0';
}
int robot_link_format_ack(const robot_ack_t *ack, char *buf, size_t buflen)
{
if (!ack || !buf || buflen < 16) {
return -1;
}
char esc[256];
if (ack->type && strcmp(ack->type, "msg") == 0) {
json_escape_into(ack->text ? ack->text : "", esc, sizeof(esc));
if (ack->ok) {
return snprintf(buf, buflen,
"{\"ok\":true,\"seq\":%lu,\"type\":\"msg\","
"\"text\":\"%s\",\"fwd\":\"%s\"}",
(unsigned long)ack->seq, esc,
ack->fwd ? ack->fwd : "ack");
}
return snprintf(buf, buflen,
"{\"ok\":false,\"seq\":%lu,\"type\":\"msg\","
"\"error\":\"%s\",\"fwd\":\"%s\"}",
(unsigned long)ack->seq,
ack->error ? ack->error : "error",
ack->fwd ? ack->fwd : "none");
}
json_escape_into(ack->dir ? ack->dir : "", esc, sizeof(esc));
if (ack->ok) {
return snprintf(buf, buflen,
"{\"ok\":true,\"seq\":%lu,\"type\":\"cmd\","
"\"dir\":\"%s\",\"fwd\":\"%s\"}",
(unsigned long)ack->seq, esc,
ack->fwd ? ack->fwd : "ack");
}
return snprintf(buf, buflen,
"{\"ok\":false,\"seq\":%lu,\"type\":\"cmd\","
"\"dir\":\"%s\",\"error\":\"%s\",\"fwd\":\"%s\"}",
(unsigned long)ack->seq, esc,
ack->error ? ack->error : "error",
ack->fwd ? ack->fwd : "none");
}