/* * 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 #include #include #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]); } } time_t current_time = time(NULL); snprintf(json, sizeof(json), "{\"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); }