333 lines
9.6 KiB
C
333 lines
9.6 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: CC0-1.0
|
|
*
|
|
* OpenThread Command Line Example
|
|
*
|
|
* This example code is in the Public Domain (or CC0 licensed, at your option.)
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, this
|
|
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
* CONDITIONS OF ANY KIND, either express or implied.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#include "sdkconfig.h"
|
|
#include "esp_err.h"
|
|
#include "esp_event.h"
|
|
#include "esp_log.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_netif_types.h"
|
|
#include "esp_openthread.h"
|
|
#include "esp_openthread_cli.h"
|
|
#include "esp_openthread_lock.h"
|
|
#include "esp_openthread_netif_glue.h"
|
|
#include "esp_openthread_types.h"
|
|
#include "esp_ot_config.h"
|
|
#include "esp_vfs_eventfd.h"
|
|
#include "driver/uart.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "hal/uart_types.h"
|
|
#include "nvs_flash.h"
|
|
#include "openthread/cli.h"
|
|
#include "openthread/instance.h"
|
|
#include "openthread/logging.h"
|
|
#include "openthread/tasklet.h"
|
|
#include "esp_http_client.h"
|
|
#include "mqtt_client.h"
|
|
|
|
#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
|
|
#include "ot_led_strip.h"
|
|
#endif
|
|
|
|
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
|
#include "esp_ot_cli_extension.h"
|
|
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
|
|
|
#define TAG "ot_esp_cli"
|
|
|
|
void thread_state_changed_callback(uint32_t flags, void *context)
|
|
{
|
|
ESP_LOGI(TAG, "State changed\n");
|
|
otInstance *instance = (otInstance *)context;
|
|
|
|
if (flags & OT_CHANGED_THREAD_ROLE)
|
|
{
|
|
otDeviceRole role = otThreadGetDeviceRole(instance);
|
|
if (role == OT_DEVICE_ROLE_DETACHED || role == OT_DEVICE_ROLE_DISABLED)
|
|
{
|
|
ESP_LOGI(TAG, "Disconnected from thread network\n");
|
|
} else {
|
|
ESP_LOGI(TAG, "Connect to thread network\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config)
|
|
{
|
|
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
|
|
esp_netif_t *netif = esp_netif_new(&cfg);
|
|
assert(netif != NULL);
|
|
ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(config)));
|
|
|
|
return netif;
|
|
}
|
|
|
|
static void ot_task_worker(void *aContext)
|
|
{
|
|
esp_openthread_platform_config_t config = {
|
|
.radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(),
|
|
.host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(),
|
|
.port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(),
|
|
};
|
|
|
|
// 1. Initialize OpenThread stack
|
|
ESP_ERROR_CHECK(esp_openthread_init(&config));
|
|
|
|
otInstance *instance = esp_openthread_get_instance();
|
|
otSetStateChangedCallback(instance, thread_state_changed_callback, instance);
|
|
|
|
#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
|
|
ESP_ERROR_CHECK(esp_openthread_state_indicator_init(instance));
|
|
#endif
|
|
|
|
#if CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC
|
|
(void)otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL);
|
|
#endif
|
|
|
|
#if CONFIG_OPENTHREAD_CLI
|
|
esp_openthread_cli_init();
|
|
#endif
|
|
|
|
esp_netif_t *openthread_netif = init_openthread_netif(&config);
|
|
esp_netif_set_default_netif(openthread_netif);
|
|
|
|
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
|
esp_cli_custom_command_init();
|
|
#endif
|
|
#if CONFIG_OPENTHREAD_AUTO_START
|
|
|
|
static const uint8_t dataset_tlv[] = {
|
|
0x0e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
|
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0f, 0x4a,
|
|
0x03, 0x00, 0x00, 0x10, 0x35, 0x06, 0x00, 0x04,
|
|
0x00, 0x1f, 0xff, 0xe0, 0x02, 0x08, 0xc4, 0xa8,
|
|
0x12, 0xe1, 0x4e, 0x69, 0x1d, 0x3c, 0x07, 0x08,
|
|
0xfd, 0xc0, 0xa0, 0xdc, 0x4e, 0xbb, 0x2e, 0x9c,
|
|
0x05, 0x10, 0x58, 0x4a, 0x84, 0xc2, 0xa1, 0xbc,
|
|
0x2e, 0xcb, 0x2f, 0xfc, 0xb2, 0xa5, 0xa0, 0x54,
|
|
0x6b, 0xac, 0x03, 0x0f, 0x4f, 0x70, 0x65, 0x6e,
|
|
0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x2d, 0x39,
|
|
0x64, 0x61, 0x33, 0x01, 0x02, 0x9d, 0xa3, 0x04,
|
|
0x10, 0x6c, 0xa6, 0x7c, 0x62, 0x4f, 0xc6, 0xd4,
|
|
0x37, 0x25, 0x39, 0x2b, 0xde, 0x6c, 0xec, 0xb1,
|
|
0x3a, 0x0c, 0x04, 0x02, 0xa0, 0xf7, 0xf8
|
|
};
|
|
|
|
otOperationalDatasetTlvs dataset;
|
|
dataset.mLength = sizeof(dataset_tlv);
|
|
memcpy(dataset.mTlvs, dataset_tlv, sizeof(dataset_tlv));
|
|
|
|
// 2. Auto-start Thread with dataset
|
|
otError err = esp_openthread_auto_start(&dataset);
|
|
ESP_LOGI(TAG, "Auto-start returned: %d", err);
|
|
#endif
|
|
|
|
#if CONFIG_OPENTHREAD_CLI
|
|
esp_openthread_cli_create_task();
|
|
#endif
|
|
|
|
// 3. Launch main loop
|
|
esp_openthread_launch_mainloop();
|
|
|
|
// 4. Clean up
|
|
esp_openthread_netif_glue_deinit();
|
|
esp_netif_destroy(openthread_netif);
|
|
esp_vfs_eventfd_unregister();
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
static bool mqtt_connected = false;
|
|
|
|
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
|
esp_mqtt_event_handle_t event = event_data;
|
|
switch (event->event_id) {
|
|
case MQTT_EVENT_CONNECTED:
|
|
ESP_LOGI(TAG, "MQTT Connected");
|
|
mqtt_connected = true;
|
|
break;
|
|
case MQTT_EVENT_DISCONNECTED:
|
|
ESP_LOGI(TAG, "MQTT Disconnected");
|
|
mqtt_connected = false;
|
|
break;
|
|
case MQTT_EVENT_PUBLISHED:
|
|
ESP_LOGI(TAG, "Message published, msg_id=%d", event->msg_id);
|
|
break;
|
|
case MQTT_EVENT_ERROR:
|
|
ESP_LOGE(TAG, "MQTT Error");
|
|
mqtt_connected = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static esp_mqtt_client_handle_t client = NULL;
|
|
void mqtt_init(void)
|
|
{
|
|
esp_mqtt_client_config_t mqtt_cfg = {
|
|
.broker.address.uri = "mqtt://192.168.15.119:1883",
|
|
.credentials = {
|
|
.username = "device",
|
|
.authentication = {
|
|
.password = "hepl",
|
|
},
|
|
.client_id = NULL,
|
|
.set_null_client_id = true
|
|
},
|
|
.session.keepalive = 30,
|
|
.session.disable_clean_session = false,
|
|
};
|
|
|
|
client = esp_mqtt_client_init(&mqtt_cfg);
|
|
if (!client) {
|
|
ESP_LOGE(TAG, "Failed to init MQTT client");
|
|
return;
|
|
}
|
|
|
|
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
|
|
|
ESP_LOGI(TAG, "Starting MQTT client...");
|
|
esp_mqtt_client_start(client);
|
|
}
|
|
|
|
void send_gps_data(const char *topic, const char *json)
|
|
{
|
|
if (!client || !json) return;
|
|
|
|
if (!mqtt_connected) {
|
|
ESP_LOGW(TAG, "MQTT not connected, skipping GPS publish");
|
|
return;
|
|
}
|
|
|
|
int msg_id = esp_mqtt_client_publish(client, topic, json, 0, 1, 0);
|
|
if (msg_id < 0) {
|
|
ESP_LOGE(TAG, "Failed to publish GPS");
|
|
} else {
|
|
ESP_LOGI(TAG, "Published GPS: msg_id=%d", msg_id);
|
|
}
|
|
}
|
|
|
|
#define GPS_UART_NUM UART_NUM_1
|
|
#define GPS_TX_PIN 4
|
|
#define GPS_RX_PIN 5
|
|
#define BUF_SIZE 1024
|
|
static uint8_t gps_buffer[BUF_SIZE];
|
|
void init_gps() {
|
|
const uart_config_t uart_config = {
|
|
.baud_rate = 9600,
|
|
.data_bits = UART_DATA_8_BITS,
|
|
.parity = UART_PARITY_DISABLE,
|
|
.stop_bits = UART_STOP_BITS_1,
|
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
|
|
};
|
|
|
|
uart_param_config(GPS_UART_NUM, &uart_config);
|
|
uart_set_pin(GPS_UART_NUM, GPS_TX_PIN, GPS_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
|
uart_driver_install(GPS_UART_NUM, BUF_SIZE, 0, 0, NULL, 0);
|
|
}
|
|
|
|
// Convertit NMEA "ddmm.mmmm" + N/S/E/W en degrés décimaux
|
|
double nmea_to_decimal(const char* nmea_coord, const char direction) {
|
|
double raw = atof(nmea_coord);
|
|
int degrees = (int)(raw / 100);
|
|
double minutes = raw - (degrees * 100);
|
|
double decimal = degrees + minutes / 60.0;
|
|
if (direction == 'S' || direction == 'W') decimal = -decimal;
|
|
return decimal;
|
|
}
|
|
|
|
char* gps_to_json(char* gps_data) {
|
|
static char json[128];
|
|
|
|
double latitude = 0.0;
|
|
double longitude = 0.0;
|
|
|
|
if (gps_data && strncmp(gps_data, "$GPGGA,", 7) == 0) {
|
|
char* fields[15];
|
|
int i = 0;
|
|
char gps_copy[BUF_SIZE];
|
|
strncpy(gps_copy, gps_data, BUF_SIZE - 1);
|
|
gps_copy[BUF_SIZE - 1] = '\0';
|
|
|
|
char* token = strtok(gps_copy, ",");
|
|
while (token && i < 15) {
|
|
fields[i++] = token;
|
|
token = strtok(NULL, ",");
|
|
}
|
|
|
|
if (i >= 6 && fields[2][0] != '\0' && fields[4][0] != '\0') {
|
|
latitude = nmea_to_decimal(fields[2], fields[3][0]);
|
|
longitude = nmea_to_decimal(fields[4], fields[5][0]);
|
|
}
|
|
}
|
|
|
|
snprintf(json, sizeof(json), "{\"data\":{\"latitude\":%.6f,\"longitude\":%.6f}}",
|
|
latitude, longitude);
|
|
|
|
return json;
|
|
}
|
|
|
|
char* read_gps() {
|
|
int len = uart_read_bytes(GPS_UART_NUM, gps_buffer, BUF_SIZE - 1, 100 / portTICK_PERIOD_MS);
|
|
if (len > 0) {
|
|
gps_buffer[len] = '\0';
|
|
return (char*)gps_buffer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void send_position_task(void *pvParameters)
|
|
{
|
|
const char* gps_topic = "/system/sensor/gps";
|
|
|
|
while (1) {
|
|
char* json;
|
|
char* raw_data = read_gps();
|
|
if (raw_data) {
|
|
char* json = gps_to_json(raw_data);
|
|
send_gps_data(gps_topic, json);
|
|
} else {
|
|
ESP_LOGI(TAG, "NO GPS DATA");
|
|
}
|
|
vTaskDelay(25000 / portTICK_PERIOD_MS);
|
|
}
|
|
}
|
|
|
|
void app_main(void)
|
|
{
|
|
// Used eventfds:
|
|
// * netif
|
|
// * ot task queue
|
|
// * radio driver
|
|
|
|
esp_vfs_eventfd_config_t eventfd_config = {
|
|
.max_fds = 3,
|
|
};
|
|
|
|
ESP_ERROR_CHECK(nvs_flash_init());
|
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
|
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
|
|
|
|
mqtt_init();
|
|
init_gps();
|
|
|
|
xTaskCreate(ot_task_worker, "ot_cli_main", 10240, xTaskGetCurrentTaskHandle(), 5, NULL);
|
|
xTaskCreate(send_position_task, "gps_task", 4096, NULL, 5, NULL);
|
|
} |