chore: snapshot before shared RS485 bus integration
This commit is contained in:
0
components/protocols/CMakeLists.txt
Executable file → Normal file
0
components/protocols/CMakeLists.txt
Executable file → Normal file
17
components/protocols/include/mqtt.h
Executable file → Normal file
17
components/protocols/include/mqtt.h
Executable file → Normal file
@@ -16,30 +16,17 @@ extern "C"
|
||||
#define MQTT_USERNAME_MAX_LEN 32
|
||||
#define MQTT_PASSWORD_MAX_LEN 64
|
||||
|
||||
// =============================
|
||||
// Limit modes (cmd/evse/limit)
|
||||
// =============================
|
||||
typedef enum
|
||||
{
|
||||
LIMIT_MODE_OFF = 0, ///< sem limite, usa max do hardware
|
||||
LIMIT_MODE_MANUAL, ///< corrente fixa definida pelo utilizador
|
||||
LIMIT_MODE_LOADBALANCER, ///< limita pelo contador da instalação
|
||||
LIMIT_MODE_SOLAR ///< só com excedente FV
|
||||
} limit_mode_t;
|
||||
|
||||
/**
|
||||
* @brief Inicializa o módulo MQTT.
|
||||
*
|
||||
* - Carrega configuração da NVS
|
||||
* - Regista handlers de eventos (AUTH, SCHED, LOADBALANCER, METER, EVSE, NETWORK)
|
||||
* - Cria a task de telemetria periódica
|
||||
* - Aplica o modo de limite persistido
|
||||
*
|
||||
* Não inicia a ligação ao broker imediatamente; isso acontece quando existir IP
|
||||
* (NETWORK_EVENT_STA_GOT_IP) e `enabled == true`.
|
||||
*
|
||||
* IMPORTANTE: chamar DEPOIS de loadbalancer_init() para o restauro do modo
|
||||
* de limite funcionar correctamente.
|
||||
* IMPORTANTE: chamar DEPOIS de loadbalancer_init() para o estado publicado
|
||||
* refletir a política local já restaurada.
|
||||
*/
|
||||
esp_err_t mqtt_init(void);
|
||||
|
||||
|
||||
0
components/protocols/include/protocols.h
Executable file → Normal file
0
components/protocols/include/protocols.h
Executable file → Normal file
252
components/protocols/src/mqtt.c
Executable file → Normal file
252
components/protocols/src/mqtt.c
Executable file → Normal file
@@ -55,7 +55,6 @@ static const char *TAG = "mqtt";
|
||||
#define MQTT_KEY_USER "user"
|
||||
#define MQTT_KEY_PASS "pass"
|
||||
#define MQTT_KEY_PERIOD "period"
|
||||
#define MQTT_KEY_LIMITMODE "limitmode"
|
||||
|
||||
#define MQTT_HOST_MAX_LEN 64
|
||||
#define MQTT_TOPIC_MAX_LEN 64
|
||||
@@ -73,7 +72,6 @@ static char s_mqtt_user[MQTT_USER_MAX_LEN] = {0};
|
||||
static char s_mqtt_pass[MQTT_PASS_MAX_LEN] = {0};
|
||||
static uint16_t s_mqtt_periodicity = 30;
|
||||
|
||||
static limit_mode_t s_limit_mode = LIMIT_MODE_OFF;
|
||||
static char s_client_id[MQTT_CLIENT_ID_MAX_LEN] = {0};
|
||||
static bool s_session_was_active = false;
|
||||
|
||||
@@ -93,10 +91,6 @@ static void mqtt_limit_publish_state(void);
|
||||
static void mqtt_session_publish_state(void);
|
||||
static void mqtt_system_publish_heartbeat(void);
|
||||
|
||||
static void mqtt_apply_limit_mode(limit_mode_t mode);
|
||||
static const char *limit_mode_to_str(limit_mode_t m);
|
||||
static bool limit_mode_from_str(const char *s, limit_mode_t *out);
|
||||
|
||||
static void mqtt_subscribe_all(void);
|
||||
static void mqtt_dispatch_command(const char *topic, const char *data);
|
||||
|
||||
@@ -209,25 +203,12 @@ static void mqtt_load_config_from_storage(void)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t u8 = 0;
|
||||
err = storage_get_u8_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, &u8, TO_TICKS_MS(500));
|
||||
if (err == ESP_OK && u8 <= LIMIT_MODE_SOLAR)
|
||||
s_limit_mode = (limit_mode_t)u8;
|
||||
else
|
||||
{
|
||||
s_limit_mode = LIMIT_MODE_OFF;
|
||||
(void)storage_set_u8_async(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, 0);
|
||||
needs_flush = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_flush)
|
||||
(void)storage_flush_async();
|
||||
|
||||
ESP_LOGI(TAG, "Config MQTT (storage): enabled=%d host='%s' topic='%s' period=%u user='%s' limitmode=%s",
|
||||
ESP_LOGI(TAG, "Config MQTT (storage): enabled=%d host='%s' topic='%s' period=%u user='%s'",
|
||||
s_mqtt_enabled, s_mqtt_host, s_mqtt_base_topic,
|
||||
(unsigned)s_mqtt_periodicity, s_mqtt_user, limit_mode_to_str(s_limit_mode));
|
||||
(unsigned)s_mqtt_periodicity, s_mqtt_user);
|
||||
}
|
||||
|
||||
static esp_err_t mqtt_save_config_to_storage(void)
|
||||
@@ -262,10 +243,6 @@ static esp_err_t mqtt_save_config_to_storage(void)
|
||||
if (e != ESP_OK)
|
||||
err = e;
|
||||
|
||||
e = storage_set_u8_async(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, (uint8_t)s_limit_mode);
|
||||
if (e != ESP_OK)
|
||||
err = e;
|
||||
|
||||
(void)storage_flush_async();
|
||||
|
||||
if (err != ESP_OK)
|
||||
@@ -310,85 +287,6 @@ void mqtt_get_password(char *password)
|
||||
|
||||
uint16_t mqtt_get_periodicity(void) { return s_mqtt_periodicity; }
|
||||
|
||||
static const char *limit_mode_to_str(limit_mode_t m)
|
||||
{
|
||||
switch (m)
|
||||
{
|
||||
case LIMIT_MODE_OFF:
|
||||
return "off";
|
||||
case LIMIT_MODE_MANUAL:
|
||||
return "manual";
|
||||
case LIMIT_MODE_LOADBALANCER:
|
||||
return "loadbalancer";
|
||||
case LIMIT_MODE_SOLAR:
|
||||
return "solar";
|
||||
default:
|
||||
return "off";
|
||||
}
|
||||
}
|
||||
|
||||
static bool limit_mode_from_str(const char *s, limit_mode_t *out)
|
||||
{
|
||||
if (!s || !out)
|
||||
return false;
|
||||
if (strcmp(s, "off") == 0)
|
||||
{
|
||||
*out = LIMIT_MODE_OFF;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(s, "manual") == 0)
|
||||
{
|
||||
*out = LIMIT_MODE_MANUAL;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(s, "loadbalancer") == 0)
|
||||
{
|
||||
*out = LIMIT_MODE_LOADBALANCER;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(s, "solar") == 0)
|
||||
{
|
||||
*out = LIMIT_MODE_SOLAR;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void mqtt_apply_limit_mode(limit_mode_t mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case LIMIT_MODE_OFF:
|
||||
loadbalancer_pv_set_enabled(false);
|
||||
loadbalancer_grid_set_enabled(false);
|
||||
loadbalancer_set_enabled(false);
|
||||
evse_set_charging_current(evse_get_max_charging_current());
|
||||
break;
|
||||
|
||||
case LIMIT_MODE_MANUAL:
|
||||
loadbalancer_pv_set_enabled(false);
|
||||
loadbalancer_grid_set_enabled(false);
|
||||
loadbalancer_set_enabled(false);
|
||||
break;
|
||||
|
||||
case LIMIT_MODE_LOADBALANCER:
|
||||
loadbalancer_pv_set_enabled(false);
|
||||
loadbalancer_grid_set_enabled(true);
|
||||
loadbalancer_set_enabled(true);
|
||||
break;
|
||||
|
||||
case LIMIT_MODE_SOLAR:
|
||||
loadbalancer_grid_set_enabled(false);
|
||||
loadbalancer_pv_set_enabled(true);
|
||||
loadbalancer_set_enabled(true);
|
||||
break;
|
||||
}
|
||||
|
||||
s_limit_mode = mode;
|
||||
(void)storage_set_u8_async(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, (uint8_t)mode);
|
||||
(void)storage_flush_async();
|
||||
}
|
||||
|
||||
static void mqtt_build_topic(char *out, size_t out_len, const char *suffix)
|
||||
{
|
||||
const char *base = (s_mqtt_base_topic[0] != '\0') ? s_mqtt_base_topic : "evse";
|
||||
@@ -715,7 +613,7 @@ static void mqtt_limit_publish_state(void)
|
||||
if (!root)
|
||||
return;
|
||||
|
||||
cJSON_AddStringToObject(root, "mode", limit_mode_to_str(s_limit_mode));
|
||||
cJSON_AddStringToObject(root, "mode", loadbalancer_limit_mode_to_str(loadbalancer_get_limit_mode()));
|
||||
cJSON_AddNumberToObject(root, "requestedCurrentA", (int)evse_get_charging_current());
|
||||
cJSON_AddNumberToObject(root, "effectiveCurrentA", (int)evse_get_runtime_charging_current());
|
||||
cJSON_AddNumberToObject(root, "hardwareMaxA", (int)evse_get_max_charging_current());
|
||||
@@ -824,7 +722,6 @@ static void mqtt_loadbalancer_event_handler(void *arg, esp_event_base_t base, in
|
||||
{
|
||||
case LOADBALANCER_EVENT_INIT:
|
||||
case LOADBALANCER_EVENT_STATE_CHANGED:
|
||||
case LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT:
|
||||
mqtt_loadbalancer_publish_state();
|
||||
mqtt_limit_publish_state();
|
||||
break;
|
||||
@@ -1158,8 +1055,8 @@ static void handle_cmd_evse_limit(const char *payload)
|
||||
return;
|
||||
}
|
||||
|
||||
limit_mode_t mode;
|
||||
if (!limit_mode_from_str(j_mode->valuestring, &mode))
|
||||
loadbalancer_limit_mode_t mode;
|
||||
if (!loadbalancer_limit_mode_from_str(j_mode->valuestring, &mode))
|
||||
{
|
||||
ESP_LOGW(TAG, "evse/limit: unknown mode '%s'", j_mode->valuestring);
|
||||
cJSON_Delete(json);
|
||||
@@ -1168,7 +1065,7 @@ static void handle_cmd_evse_limit(const char *payload)
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case LIMIT_MODE_MANUAL:
|
||||
case LOADBALANCER_LIMIT_MODE_MANUAL:
|
||||
{
|
||||
cJSON *j_curr = cJSON_GetObjectItem(json, "requestedCurrentA");
|
||||
if (!j_curr)
|
||||
@@ -1183,62 +1080,172 @@ static void handle_cmd_evse_limit(const char *payload)
|
||||
|
||||
int a = j_curr->valueint;
|
||||
uint8_t hw_max = evse_get_max_charging_current();
|
||||
|
||||
if (a < MIN_CHARGING_CURRENT_LIMIT || a > hw_max)
|
||||
{
|
||||
ESP_LOGW(TAG, "evse/limit manual: currentA %d out of range [%d,%u]",
|
||||
a, MIN_CHARGING_CURRENT_LIMIT, (unsigned)hw_max);
|
||||
ESP_LOGW(TAG,
|
||||
"evse/limit manual: currentA %d out of range [%d,%u]",
|
||||
a,
|
||||
MIN_CHARGING_CURRENT_LIMIT,
|
||||
(unsigned)hw_max);
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t e = evse_set_charging_current((uint16_t)a);
|
||||
if (e != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"evse_set_charging_current failed: %s",
|
||||
esp_err_to_name(e));
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
|
||||
evse_set_charging_current((uint16_t)a);
|
||||
break;
|
||||
}
|
||||
|
||||
case LIMIT_MODE_LOADBALANCER:
|
||||
case LOADBALANCER_LIMIT_MODE_GRID:
|
||||
{
|
||||
cJSON *j = cJSON_GetObjectItem(json, "gridMaxImportA");
|
||||
|
||||
if (cJSON_IsNumber(j))
|
||||
{
|
||||
int a = j->valueint;
|
||||
|
||||
if (a < 6 || a > 100)
|
||||
{
|
||||
ESP_LOGW(TAG, "evse/limit loadbalancer: gridMaxImportA %d out of range", a);
|
||||
ESP_LOGW(TAG,
|
||||
"evse/limit grid: gridMaxImportA %d out of range",
|
||||
a);
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t e = loadbalancer_grid_set_max_import_a((uint8_t)a);
|
||||
if (e != ESP_OK)
|
||||
ESP_LOGW(TAG, "loadbalancer_grid_set_max_import_a failed: %s", esp_err_to_name(e));
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"loadbalancer_grid_set_max_import_a failed: %s",
|
||||
esp_err_to_name(e));
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LIMIT_MODE_SOLAR:
|
||||
case LOADBALANCER_LIMIT_MODE_SOLAR:
|
||||
{
|
||||
cJSON *j = cJSON_GetObjectItem(json, "maxImportW");
|
||||
|
||||
if (cJSON_IsNumber(j))
|
||||
{
|
||||
int w = j->valueint;
|
||||
|
||||
if (w < 0)
|
||||
{
|
||||
ESP_LOGW(TAG, "evse/limit solar: maxImportW %d invalid", w);
|
||||
ESP_LOGW(TAG,
|
||||
"evse/limit solar: maxImportW %d invalid",
|
||||
w);
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t e = loadbalancer_pv_set_max_import_w(w);
|
||||
if (e != ESP_OK)
|
||||
ESP_LOGW(TAG, "loadbalancer_pv_set_max_import_w failed: %s", esp_err_to_name(e));
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"loadbalancer_pv_set_max_import_w failed: %s",
|
||||
esp_err_to_name(e));
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LIMIT_MODE_OFF:
|
||||
case LOADBALANCER_LIMIT_MODE_GRID_SOLAR:
|
||||
{
|
||||
/*
|
||||
* grid_solar = solar optimizer + grid import cap.
|
||||
*
|
||||
* Legacy MQTT flat payload:
|
||||
* {
|
||||
* "mode": "grid_solar",
|
||||
* "gridMaxImportA": 30,
|
||||
* "maxImportW": 0
|
||||
* }
|
||||
*/
|
||||
|
||||
cJSON *j_grid = cJSON_GetObjectItem(json, "gridMaxImportA");
|
||||
if (cJSON_IsNumber(j_grid))
|
||||
{
|
||||
int a = j_grid->valueint;
|
||||
|
||||
if (a < 6 || a > 100)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"evse/limit grid_solar: gridMaxImportA %d out of range",
|
||||
a);
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t e = loadbalancer_grid_set_max_import_a((uint8_t)a);
|
||||
if (e != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"loadbalancer_grid_set_max_import_a failed: %s",
|
||||
esp_err_to_name(e));
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON *j_pv = cJSON_GetObjectItem(json, "maxImportW");
|
||||
if (cJSON_IsNumber(j_pv))
|
||||
{
|
||||
int w = j_pv->valueint;
|
||||
|
||||
if (w < 0)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"evse/limit grid_solar: maxImportW %d invalid",
|
||||
w);
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t e = loadbalancer_pv_set_max_import_w(w);
|
||||
if (e != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"loadbalancer_pv_set_max_import_w failed: %s",
|
||||
esp_err_to_name(e));
|
||||
cJSON_Delete(json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "MQTT: applying limit mode = %s", limit_mode_to_str(mode));
|
||||
mqtt_apply_limit_mode(mode);
|
||||
case LOADBALANCER_LIMIT_MODE_OFF:
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"MQTT: requested limit mode = %s",
|
||||
loadbalancer_limit_mode_to_str(mode));
|
||||
|
||||
esp_err_t mode_err = loadbalancer_set_limit_mode(mode);
|
||||
if (mode_err != ESP_OK)
|
||||
ESP_LOGW(TAG,
|
||||
"loadbalancer_set_limit_mode failed: %s",
|
||||
esp_err_to_name(mode_err));
|
||||
|
||||
cJSON_Delete(json);
|
||||
mqtt_loadbalancer_publish_state();
|
||||
@@ -1543,7 +1550,8 @@ esp_err_t mqtt_init(void)
|
||||
xTaskCreate(mqtt_telemetry_task, "mqtt_telemetry_task", 4096, NULL, 3, &s_telemetry_task);
|
||||
}
|
||||
|
||||
mqtt_apply_limit_mode(s_limit_mode);
|
||||
ESP_LOGI(TAG, "MQTT init: loadbalancer owns limit mode; MQTT will only apply broker commands");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
0
components/protocols/src/protocols.c
Executable file → Normal file
0
components/protocols/src/protocols.c
Executable file → Normal file
Reference in New Issue
Block a user