#include "comms/SerialClient.h" #include "comms/MeshEnvelope.h" #include "util/ILog.h" #include #ifdef ARCH_ESP32 #include "driver/uart.h" #include "esp_sleep.h" #include #endif #if defined(HAS_FREE_RTOS) || defined(ARCH_ESP32) #include "esp_task_wdt.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #else #include #endif #include "Arduino.h" #ifndef SLEEP_TIME_IDLE #define SLEEP_TIME_IDLE 50 // ms #endif #ifndef SLEEP_TIME_ACTIVE #define SLEEP_TIME_ACTIVE 2 // ms #endif SerialClient *SerialClient::instance = nullptr; SerialClient::SerialClient(const char *name) : pb_size(0), notifyConnectionStatus(nullptr), connectionStatus(eDisconnected), clientStatus(eDisconnected), connectionInfo(nullptr), shutdown(false), threadName(name) { buffer = new uint8_t[PB_BUFSIZE + MT_HEADER_SIZE]; instance = this; } void SerialClient::init(void) { ILOG_TRACE("SerialClient::init() creating %s task", threadName); #if defined(HAS_FREE_RTOS) || defined(ARCH_ESP32) xTaskCreateUniversal(task_loop, threadName, 8192, NULL, 1, NULL, 0); #elif defined(ARCH_PORTDUINO) new std::thread([] { #ifdef __APPLE__ pthread_setname_np(threadName); #else pthread_setname_np(pthread_self(), instance->threadName); #endif instance->task_loop(nullptr); }); #else // #error "unsupported architecture" #endif } /** * @brief Do light sleep, wake on pin GPIO (touch IRQ or button press) * * @param pin * @return true - wake reason was due to (button) pin GPIO * @return false - other reason (received serial data) */ bool SerialClient::sleep(int16_t pin) { #ifdef ARCH_ESP32 esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); #if defined(WAKE_ON_SERIAL) && defined(USE_SERIAL0) // TODO: check: - can only work if using the refclock, which is limited to about 9600 bps assert(uart_set_wakeup_threshold(UART_NUM_0, 3) == ESP_OK); assert(esp_sleep_enable_uart_wakeup(UART_NUM_0) == ESP_OK); #elif defined(WAKE_ON_SERIAL) && defined(USE_SERIAL1) assert(uart_set_wakeup_threshold(UART_NUM_1, 3) == ESP_OK); assert(esp_sleep_enable_uart_wakeup(UART_NUM_1) == ESP_OK); #else // serial2 is unsupported for wakeup // TODO: set flag for possible re-syncronisation with device #endif #ifdef BUTTON_PIN pin = (gpio_num_t)BUTTON_PIN; #if defined(BUTTON_NEED_PULLUP) gpio_pullup_en((gpio_num_t)pin); #endif gpio_intr_disable((gpio_num_t)pin); gpio_wakeup_enable((gpio_num_t)pin, GPIO_INTR_LOW_LEVEL); #else gpio_wakeup_enable((gpio_num_t)pin, GPIO_INTR_LOW_LEVEL); #endif auto res = esp_sleep_enable_gpio_wakeup(); if (res != ESP_OK) { ILOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res); } ILOG_INFO("going to sleep, wake up on GPIO%02d", pin); delay(5); res = esp_light_sleep_start(); if (res != ESP_OK) { ILOG_ERROR("esp_light_sleep_start result %d", res); } esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); ILOG_INFO("Exit light sleep cause: %d, gpio=%d", (int)cause, (int)digitalRead((uint8_t)pin)); #ifdef BUTTON_PIN gpio_wakeup_disable((gpio_num_t)pin); #endif #if defined(WAKE_ON_SERIAL) #if defined(USE_SERIAL0) esp_sleep_disable_uart_wakeup(UART_NUM_0); #elif defined(USE_SERIAL1) esp_sleep_disable_uart_wakeup(UART_NUM_1); #endif #endif return cause == ESP_SLEEP_WAKEUP_GPIO; #else return false; #endif } bool SerialClient::connect(void) { clientStatus = eConnected; return clientStatus == eConnected; } bool SerialClient::disconnect(void) { clientStatus = eDisconnected; return clientStatus == eDisconnected; } bool SerialClient::isConnected(void) { return clientStatus == eConnected; } void SerialClient::setConnectionStatus(ConnectionStatus status, const char *info) { ILOG_TRACE("SerialClient::setConnectionStatus() status=%d, info=%s", status, info); this->clientStatus = status; this->connectionInfo = info; } void SerialClient::setNotifyCallback(NotifyCallback notifyConnectionStatus) { this->notifyConnectionStatus = notifyConnectionStatus; } bool SerialClient::isStandalone(void) { return true; } bool SerialClient::send(meshtastic_ToRadio &&to) { static uint32_t id = 1; ILOG_DEBUG("SerialClient::send() push packet %d to server", id); queue.clientSend(DataPacket(id++, to)); return false; } meshtastic_FromRadio SerialClient::receive(void) { if (queue.serverQueueSize() != 0) { ILOG_TRACE("SerialClient::receive() got a packet from queue"); auto p = queue.clientReceive(); if (p) { return static_cast *>(p->move().get())->getData(); } else { ILOG_ERROR("SerialClient::receive() no packet in queue"); } } return meshtastic_FromRadio(); } void SerialClient::task_handler(void) { // check for connection status change if (notifyConnectionStatus) { if (connectionStatus != clientStatus || (connectionStatus == eConnected && !isConnected())) { connectionStatus = clientStatus; notifyConnectionStatus(connectionStatus, connectionInfo); } } } SerialClient::~SerialClient() { shutdown = true; delete[] buffer; }; // --- protected part --- bool SerialClient::send(const uint8_t *buf, size_t len) { ILOG_ERROR("SerialClient::send not implemented"); return false; } size_t SerialClient::receive(uint8_t *buf, size_t space_left) { ILOG_ERROR("SerialClient::receive not implemented"); return 0; } /** * @brief put received bytes into envelope and send to client queue * */ void SerialClient::handlePacketReceived(void) { ILOG_TRACE("SerialClient::handlePacketReceived pb_size=%d", pb_size); MeshEnvelope envelope(buffer, pb_size); meshtastic_FromRadio fromRadio = envelope.decode(); if (fromRadio.which_payload_variant != 0) { queue.serverSend(DataPacket(fromRadio.id, fromRadio)); ILOG_TRACE("server queue size=%d", queue.serverQueueSize()); } } void SerialClient::handleSendPacket(void) { auto p = queue.serverReceive(); if (p) { meshtastic_ToRadio toRadio = static_cast *>(p->move().get())->getData(); // meshtastic_ToRadio toRadio{std::move(static_cast *>(p.get())->getData())}; MeshEnvelope envelope; const std::vector &pb_buf = envelope.encode(toRadio); if (pb_buf.size() > 0) { send(&pb_buf[0], pb_buf.size()); } } else { ILOG_ERROR("SerialClient::handleSendPacket() no packet in queue!"); } } /** * @brief Sending and receiving packets to/from packet queue * */ void SerialClient::task_loop(void *) { delay(1000); ILOG_TRACE("SerialClient::task_loop running"); while (!instance->shutdown) { int sleep_time = SLEEP_TIME_IDLE; size_t space_left = PB_BUFSIZE - instance->pb_size; if (instance->clientStatus == eConnected) { size_t bytes_read = instance->receive(&instance->buffer[instance->pb_size], space_left); if (bytes_read > 0) { instance->pb_size += bytes_read; size_t payload_len; bool valid = false; do { valid = MeshEnvelope::validate(instance->buffer, instance->pb_size, payload_len); if (valid) { instance->handlePacketReceived(); MeshEnvelope::invalidate(instance->buffer, instance->pb_size, payload_len); } } while (valid && instance->pb_size > 0); sleep_time = SLEEP_TIME_ACTIVE; } } if (instance->clientStatus == eConnected) { // send a packet if available if (instance->queue.clientQueueSize() > 0) { instance->handleSendPacket(); } } #if defined(HAS_FREE_RTOS) || defined(ARCH_ESP32) vTaskDelay((TickType_t)sleep_time); // yield, do not remove #else std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time)); #endif } }