chore: snapshot before shared RS485 bus integration
This commit is contained in:
10
components/hmi_link/CMakeLists.txt
Normal file
10
components/hmi_link/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
set(srcs
|
||||
"src/hmi_link.c"
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES evse
|
||||
PRIV_REQUIRES driver esp_timer json
|
||||
)
|
||||
26
components/hmi_link/include/hmi_link.h
Normal file
26
components/hmi_link/include/hmi_link.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef HMI_LINK_H
|
||||
#define HMI_LINK_H
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Start the local HMI UART link.
|
||||
*
|
||||
* Default pins:
|
||||
* EVSE GPIO22 = TX -> LCD RX
|
||||
* EVSE GPIO21 = RX <- LCD TX
|
||||
* Baudrate = 115200 8N1
|
||||
*
|
||||
* The link uses one JSON object per line ('\n').
|
||||
*/
|
||||
esp_err_t hmi_link_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // HMI_LINK_H
|
||||
406
components/hmi_link/src/hmi_link.c
Normal file
406
components/hmi_link/src/hmi_link.c
Normal file
@@ -0,0 +1,406 @@
|
||||
#include "hmi_link.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cJSON.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "evse_api.h"
|
||||
#include "evse_config.h"
|
||||
#include "evse_error.h"
|
||||
#include "evse_meter.h"
|
||||
#include "evse_session.h"
|
||||
#include "evse_state.h"
|
||||
|
||||
#ifndef HMI_LINK_UART_PORT
|
||||
#define HMI_LINK_UART_PORT UART_NUM_1
|
||||
#endif
|
||||
|
||||
#ifndef HMI_LINK_UART_TX_GPIO
|
||||
#define HMI_LINK_UART_TX_GPIO GPIO_NUM_22
|
||||
#endif
|
||||
|
||||
#ifndef HMI_LINK_UART_RX_GPIO
|
||||
#define HMI_LINK_UART_RX_GPIO GPIO_NUM_21
|
||||
#endif
|
||||
|
||||
#ifndef HMI_LINK_UART_BAUDRATE
|
||||
#define HMI_LINK_UART_BAUDRATE 115200
|
||||
#endif
|
||||
|
||||
#ifndef HMI_LINK_STATUS_PERIOD_MS
|
||||
#define HMI_LINK_STATUS_PERIOD_MS 1000
|
||||
#endif
|
||||
|
||||
#define HMI_LINK_RX_BUF_SIZE 1024
|
||||
#define HMI_LINK_TX_BUF_SIZE 1024
|
||||
#define HMI_LINK_LINE_MAX 512
|
||||
#define HMI_LINK_JSON_MAX 896
|
||||
#define HMI_LINK_TASK_STACK 4096
|
||||
#define HMI_LINK_TASK_PRIO 5
|
||||
|
||||
static const char *TAG = "hmi_link";
|
||||
|
||||
static TaskHandle_t s_task = NULL;
|
||||
static uint32_t s_seq = 0;
|
||||
|
||||
static void hmi_link_write_line(const char *line)
|
||||
{
|
||||
if (!line)
|
||||
return;
|
||||
|
||||
uart_write_bytes(HMI_LINK_UART_PORT, line, strlen(line));
|
||||
uart_write_bytes(HMI_LINK_UART_PORT, "\n", 1);
|
||||
}
|
||||
|
||||
static void hmi_link_send_ack(const char *cmd, bool ok, esp_err_t err)
|
||||
{
|
||||
char out[192];
|
||||
snprintf(out, sizeof(out),
|
||||
"{\"type\":\"ack\",\"cmd\":\"%s\",\"ok\":%s,\"err\":\"%s\"}",
|
||||
cmd ? cmd : "",
|
||||
ok ? "true" : "false",
|
||||
ok ? "OK" : esp_err_to_name(err));
|
||||
hmi_link_write_line(out);
|
||||
}
|
||||
|
||||
static void hmi_link_send_error(const char *reason)
|
||||
{
|
||||
char out[192];
|
||||
snprintf(out, sizeof(out),
|
||||
"{\"type\":\"error\",\"reason\":\"%s\"}",
|
||||
reason ? reason : "unknown");
|
||||
hmi_link_write_line(out);
|
||||
}
|
||||
|
||||
static bool json_bool_value(const cJSON *root, const char *name, bool *out)
|
||||
{
|
||||
const cJSON *item = cJSON_GetObjectItemCaseSensitive(root, name);
|
||||
if (cJSON_IsBool(item))
|
||||
{
|
||||
*out = cJSON_IsTrue(item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool json_u16_value_any(const cJSON *root, uint16_t *out, const char *a, const char *b, const char *c)
|
||||
{
|
||||
const char *names[3] = {a, b, c};
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
{
|
||||
if (!names[i])
|
||||
continue;
|
||||
|
||||
const cJSON *item = cJSON_GetObjectItemCaseSensitive(root, names[i]);
|
||||
if (cJSON_IsNumber(item) && item->valuedouble >= 0.0 && item->valuedouble <= 65535.0)
|
||||
{
|
||||
*out = (uint16_t)item->valueint;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void hmi_link_send_status(void)
|
||||
{
|
||||
int power[EVSE_METER_PHASE_COUNT] = {0};
|
||||
float voltage[EVSE_METER_PHASE_COUNT] = {0};
|
||||
float current[EVSE_METER_PHASE_COUNT] = {0};
|
||||
evse_session_t session = {0};
|
||||
bool has_session = evse_get_session(&session);
|
||||
|
||||
evse_meter_get_power(power);
|
||||
evse_meter_get_voltage(voltage);
|
||||
evse_meter_get_current(current);
|
||||
|
||||
evse_state_t state = evse_get_state();
|
||||
uint32_t error_bits = evse_get_error();
|
||||
uint64_t uptime_ms = (uint64_t)(esp_timer_get_time() / 1000ULL);
|
||||
|
||||
char out[HMI_LINK_JSON_MAX];
|
||||
snprintf(out, sizeof(out),
|
||||
"{"
|
||||
"\"type\":\"status\","
|
||||
"\"seq\":%lu,"
|
||||
"\"uptimeMs\":%llu,"
|
||||
"\"state\":\"%s\","
|
||||
"\"plugged\":%s,"
|
||||
"\"charging\":%s,"
|
||||
"\"enabled\":%s,"
|
||||
"\"available\":%s,"
|
||||
"\"authorized\":%s,"
|
||||
"\"limitA\":%u,"
|
||||
"\"runtimeA\":%u,"
|
||||
"\"maxA\":%u,"
|
||||
"\"tempLimitC\":%u,"
|
||||
"\"powerW\":%d,"
|
||||
"\"energyWh\":%d,"
|
||||
"\"error\":%lu,"
|
||||
"\"v\":[%.1f,%.1f,%.1f],"
|
||||
"\"i\":[%.3f,%.3f,%.3f],"
|
||||
"\"p\":[%d,%d,%d],"
|
||||
"\"session\":{\"present\":%s,\"current\":%s,\"id\":%lu,\"durationS\":%lu,\"energyWh\":%lu,\"avgPowerW\":%lu}"
|
||||
"}",
|
||||
(unsigned long)++s_seq,
|
||||
(unsigned long long)uptime_ms,
|
||||
evse_state_to_str(state),
|
||||
evse_state_is_plugged(state) ? "true" : "false",
|
||||
evse_state_is_charging(state) ? "true" : "false",
|
||||
evse_config_is_enabled() ? "true" : "false",
|
||||
evse_config_is_available() ? "true" : "false",
|
||||
evse_state_get_authorized() ? "true" : "false",
|
||||
(unsigned)evse_get_charging_current(),
|
||||
(unsigned)evse_get_runtime_charging_current(),
|
||||
(unsigned)evse_get_max_charging_current(),
|
||||
(unsigned)evse_get_temp_threshold(),
|
||||
evse_meter_get_instant_power(),
|
||||
evse_meter_get_total_energy(),
|
||||
(unsigned long)error_bits,
|
||||
voltage[0], voltage[1], voltage[2],
|
||||
current[0], current[1], current[2],
|
||||
power[0], power[1], power[2],
|
||||
has_session ? "true" : "false",
|
||||
(has_session && session.is_current) ? "true" : "false",
|
||||
has_session ? (unsigned long)session.session_id : 0UL,
|
||||
has_session ? (unsigned long)session.duration_s : 0UL,
|
||||
has_session ? (unsigned long)session.energy_wh : 0UL,
|
||||
has_session ? (unsigned long)session.avg_power_w : 0UL);
|
||||
|
||||
hmi_link_write_line(out);
|
||||
}
|
||||
|
||||
static void hmi_link_handle_command(const char *line)
|
||||
{
|
||||
cJSON *root = cJSON_Parse(line);
|
||||
if (!root)
|
||||
{
|
||||
hmi_link_send_error("invalid_json");
|
||||
return;
|
||||
}
|
||||
|
||||
const cJSON *cmd_item = cJSON_GetObjectItemCaseSensitive(root, "cmd");
|
||||
if (!cJSON_IsString(cmd_item) || cmd_item->valuestring == NULL)
|
||||
{
|
||||
cJSON_Delete(root);
|
||||
hmi_link_send_error("missing_cmd");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *cmd = cmd_item->valuestring;
|
||||
|
||||
if (strcmp(cmd, "get") == 0)
|
||||
{
|
||||
hmi_link_send_ack(cmd, true, ESP_OK);
|
||||
hmi_link_send_status();
|
||||
}
|
||||
else if (strcmp(cmd, "ping") == 0)
|
||||
{
|
||||
char out[128];
|
||||
snprintf(out, sizeof(out), "{\"type\":\"pong\",\"uptimeMs\":%llu}",
|
||||
(unsigned long long)(esp_timer_get_time() / 1000ULL));
|
||||
hmi_link_write_line(out);
|
||||
}
|
||||
else if (strcmp(cmd, "set_current") == 0)
|
||||
{
|
||||
uint16_t amps = 0;
|
||||
if (!json_u16_value_any(root, &s, "a", "currentA", "limitA"))
|
||||
{
|
||||
hmi_link_send_ack(cmd, false, ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
else
|
||||
{
|
||||
esp_err_t err = evse_set_charging_current(amps);
|
||||
hmi_link_send_ack(cmd, err == ESP_OK, err);
|
||||
if (err == ESP_OK)
|
||||
hmi_link_send_status();
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "set_temp_threshold") == 0)
|
||||
{
|
||||
uint16_t temp_c = 0;
|
||||
if (!json_u16_value_any(root, &temp_c, "c", "tempC", "temperatureLimit"))
|
||||
{
|
||||
hmi_link_send_ack(cmd, false, ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
else
|
||||
{
|
||||
esp_err_t err = evse_set_temp_threshold((uint8_t)temp_c);
|
||||
hmi_link_send_ack(cmd, err == ESP_OK, err);
|
||||
if (err == ESP_OK)
|
||||
hmi_link_send_status();
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "set_enabled") == 0)
|
||||
{
|
||||
bool enabled = false;
|
||||
if (!json_bool_value(root, "enabled", &enabled))
|
||||
{
|
||||
hmi_link_send_ack(cmd, false, ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
else
|
||||
{
|
||||
evse_set_enabled(enabled);
|
||||
hmi_link_send_ack(cmd, true, ESP_OK);
|
||||
hmi_link_send_status();
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "set_available") == 0)
|
||||
{
|
||||
bool available = false;
|
||||
if (!json_bool_value(root, "available", &available))
|
||||
{
|
||||
hmi_link_send_ack(cmd, false, ESP_ERR_INVALID_ARG);
|
||||
}
|
||||
else
|
||||
{
|
||||
evse_set_available(available);
|
||||
hmi_link_send_ack(cmd, true, ESP_OK);
|
||||
hmi_link_send_status();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hmi_link_send_ack(cmd, false, ESP_ERR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
static void hmi_link_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
char line[HMI_LINK_LINE_MAX];
|
||||
size_t line_len = 0;
|
||||
uint8_t rx[128];
|
||||
TickType_t last_status = 0;
|
||||
|
||||
hmi_link_write_line("{\"type\":\"hello\",\"name\":\"hmi_link\",\"version\":1}");
|
||||
hmi_link_send_status();
|
||||
last_status = xTaskGetTickCount();
|
||||
|
||||
while (true)
|
||||
{
|
||||
int n = uart_read_bytes(HMI_LINK_UART_PORT, rx, sizeof(rx), pdMS_TO_TICKS(100));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
uint8_t ch = rx[i];
|
||||
|
||||
if (ch == '\r')
|
||||
continue;
|
||||
|
||||
if (ch == '\n')
|
||||
{
|
||||
if (line_len > 0)
|
||||
{
|
||||
line[line_len] = '\0';
|
||||
hmi_link_handle_command(line);
|
||||
line_len = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line_len < (sizeof(line) - 1))
|
||||
{
|
||||
line[line_len++] = (char)ch;
|
||||
}
|
||||
else
|
||||
{
|
||||
line_len = 0;
|
||||
hmi_link_send_error("line_too_long");
|
||||
}
|
||||
}
|
||||
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
if ((now - last_status) >= pdMS_TO_TICKS(HMI_LINK_STATUS_PERIOD_MS))
|
||||
{
|
||||
hmi_link_send_status();
|
||||
last_status = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t hmi_link_init(void)
|
||||
{
|
||||
if (s_task != NULL)
|
||||
{
|
||||
ESP_LOGW(TAG, "Already started");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const uart_config_t uart_config = {
|
||||
.baud_rate = HMI_LINK_UART_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
esp_err_t err = uart_driver_install(HMI_LINK_UART_PORT,
|
||||
HMI_LINK_RX_BUF_SIZE,
|
||||
HMI_LINK_TX_BUF_SIZE,
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "uart_driver_install failed: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uart_param_config(HMI_LINK_UART_PORT, &uart_config);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "uart_param_config failed: %s", esp_err_to_name(err));
|
||||
uart_driver_delete(HMI_LINK_UART_PORT);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uart_set_pin(HMI_LINK_UART_PORT,
|
||||
HMI_LINK_UART_TX_GPIO,
|
||||
HMI_LINK_UART_RX_GPIO,
|
||||
UART_PIN_NO_CHANGE,
|
||||
UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "uart_set_pin failed: %s", esp_err_to_name(err));
|
||||
uart_driver_delete(HMI_LINK_UART_PORT);
|
||||
return err;
|
||||
}
|
||||
|
||||
BaseType_t ok = xTaskCreate(hmi_link_task,
|
||||
"hmi_link",
|
||||
HMI_LINK_TASK_STACK,
|
||||
NULL,
|
||||
HMI_LINK_TASK_PRIO,
|
||||
&s_task);
|
||||
if (ok != pdPASS)
|
||||
{
|
||||
ESP_LOGE(TAG, "xTaskCreate failed");
|
||||
s_task = NULL;
|
||||
uart_driver_delete(HMI_LINK_UART_PORT);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Started: UART%d, TX GPIO%d, RX GPIO%d, %d baud",
|
||||
(int)HMI_LINK_UART_PORT,
|
||||
(int)HMI_LINK_UART_TX_GPIO,
|
||||
(int)HMI_LINK_UART_RX_GPIO,
|
||||
(int)HMI_LINK_UART_BAUDRATE);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user