mirror of
				https://github.com/luc-github/ESP3D.git
				synced 2025-10-24 11:50:52 -07:00 
			
		
		
		
	Serial refactoring and idf 4.4.7 for ESP32 (#1048)
* Split esp32 code from esp8266 for Serial * split receive serial message and processing serial message in esp32 in different task * Make the esp32 code backward compatible with Arduino esp32 2.0.17 / IDF 4.4.7
This commit is contained in:
		| @@ -611,11 +611,6 @@ | ||||
|  */ | ||||
| #define ESP_SAVE_SETTINGS SETTINGS_IN_EEPROM | ||||
|  | ||||
| /* Add serial task | ||||
|  * ESP32 need to add a task to handle serial communication | ||||
|  */ | ||||
| #define SERIAL_INDEPENDANT_TASK | ||||
|  | ||||
| /************************************ | ||||
|  * | ||||
|  * Development setting | ||||
|   | ||||
| @@ -34,8 +34,13 @@ | ||||
| #endif  // __has_include ("rtc_wdt.h") | ||||
| #endif  // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 | ||||
| #include <WiFi.h> | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #include <esp_adc/adc_continuous.h> | ||||
| #include <esp_adc/adc_oneshot.h> | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 2 | ||||
| #include <driver/adc.h> | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 2   | ||||
| #include <esp_task_wdt.h> | ||||
|  | ||||
| #if !CONFIG_IDF_TARGET_ESP32C6 | ||||
|   | ||||
| @@ -20,6 +20,13 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| /************************** | ||||
|  * Arduino core version | ||||
|  * ***********************/ | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 1 | ||||
| #error "ESP3D does not support Arduino Core 1.x.x" | ||||
| #endif | ||||
|  | ||||
| /************************** | ||||
|  * Settings | ||||
|  * ***********************/ | ||||
|   | ||||
| @@ -252,7 +252,12 @@ const char* mDNS_Service::answerIP(uint16_t index) { | ||||
|     return ""; | ||||
|   } | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
|   tmp = MDNS.address(index).toString(); | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 2 | ||||
|   tmp = MDNS.IP(index).toString(); | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 2 | ||||
|  | ||||
| #endif  // ARDUINO_ARCH_ESP32 | ||||
| #if defined(ARDUINO_ARCH_ESP8266) | ||||
|   | ||||
| @@ -24,6 +24,10 @@ | ||||
| #include "../../core/esp3d_client_types.h" | ||||
| #include "../../core/esp3d_message.h" | ||||
|  | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
| #include "../../core/esp3d_messageFifo.h" | ||||
| #endif  // ARDUINO_ARCH_ESP32 | ||||
|  | ||||
| #define ESP3D_SERIAL_BUFFER_SIZE 1024 | ||||
|  | ||||
| extern const uint32_t SupportedBaudList[]; | ||||
| @@ -36,11 +40,10 @@ class ESP3DSerialService final { | ||||
|   void setParameters(); | ||||
|   bool begin(uint8_t serialIndex); | ||||
|   bool end(); | ||||
|   void updateBaudRate(long br); | ||||
|   void updateBaudRate(uint32_t br); | ||||
|   void handle(); | ||||
|   void process(); | ||||
|   bool reset(); | ||||
|   long baudRate(); | ||||
|   uint32_t baudRate(); | ||||
|   uint8_t serialIndex() { return _serialIndex; } | ||||
|   const uint32_t *get_baudratelist(uint8_t *count); | ||||
|   void flush(); | ||||
| @@ -52,8 +55,13 @@ class ESP3DSerialService final { | ||||
|   void initAuthentication(); | ||||
|   void setAuthentication(ESP3DAuthenticationLevel auth) { _auth = auth; } | ||||
|   ESP3DAuthenticationLevel getAuthentication(); | ||||
|  | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
|   void receiveCb(); | ||||
|   static void receiveSerialCb(); | ||||
|   static void receiveBridgeSeialCb(); | ||||
| #endif  // ARDUINO_ARCH_ESP32 | ||||
|  private: | ||||
|   uint32_t _baudRate; | ||||
|   ESP3DAuthenticationLevel _auth; | ||||
|   uint8_t _serialIndex; | ||||
|   ESP3DClientType _origin; | ||||
| @@ -65,6 +73,10 @@ class ESP3DSerialService final { | ||||
|   uint32_t _lastflush; | ||||
|   uint8_t _buffer[ESP3D_SERIAL_BUFFER_SIZE + 1];  // keep space of 0x0 terminal | ||||
|   size_t _buffer_size; | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
|   SemaphoreHandle_t _mutex; | ||||
|   ESP3DMessageFIFO _messagesInFIFO; | ||||
| #endif  // ARDUINO_ARCH_ESP32 | ||||
|   void push2buffer(uint8_t *sbuf, size_t len); | ||||
|   void flushbuffer(); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										437
									
								
								esp3d/src/modules/serial/serial_service_esp32.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										437
									
								
								esp3d/src/modules/serial/serial_service_esp32.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,437 @@ | ||||
| /* | ||||
|   esp3d_serial_service.cpp -  serial services functions class | ||||
|  | ||||
|   Copyright (c) 2014 Luc Lebosse. All rights reserved. | ||||
|  | ||||
|   This code is free software; you can redistribute it and/or | ||||
|   modify it under the terms of the GNU Lesser General Public | ||||
|   License as published by the Free Software Foundation; either | ||||
|   version 2.1 of the License, or (at your option) any later version. | ||||
|  | ||||
|   This code is distributed in the hope that it will be useful, | ||||
|   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||
|   Lesser General Public License for more details. | ||||
|  | ||||
|   You should have received a copy of the GNU Lesser General Public | ||||
|   License along with This code; if not, write to the Free Software | ||||
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| */ | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
| #include "../../include/esp3d_config.h" | ||||
| #if COMMUNICATION_PROTOCOL == RAW_SERIAL || defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
| #include "../../core/esp3d_commands.h" | ||||
| #include "../../core/esp3d_settings.h" | ||||
| #include "../../core/esp3d_string.h" | ||||
| #include "../authentication/authentication_service.h" | ||||
| #include "serial_service.h" | ||||
|  | ||||
| #define SERIAL_COMMUNICATION_TIMEOUT 500 | ||||
|  | ||||
| #if defined(CONFIG_IDF_TARGET_ESP32C3) || \ | ||||
|     defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32S2) | ||||
| #define MAX_SERIAL 2 | ||||
| HardwareSerial *Serials[MAX_SERIAL] = {&Serial, &Serial1}; | ||||
| #else | ||||
| #define MAX_SERIAL 3 | ||||
| HardwareSerial *Serials[MAX_SERIAL] = {&Serial, &Serial1, &Serial2}; | ||||
| #endif | ||||
|  | ||||
| // Serial Parameters | ||||
| #define ESP_SERIAL_PARAM SERIAL_8N1 | ||||
|  | ||||
| ESP3DSerialService esp3d_serial_service = ESP3DSerialService(MAIN_SERIAL); | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
| ESP3DSerialService serial_bridge_service = ESP3DSerialService(BRIDGE_SERIAL); | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT | ||||
|  | ||||
| const uint32_t SupportedBaudList[] = {9600,    19200,   38400,  57600,  74880, | ||||
|                                       115200,  230400,  250000, 500000, 921600, | ||||
|                                       1000000, 1958400, 2000000}; | ||||
| const size_t SupportedBaudListSize = sizeof(SupportedBaudList) / sizeof(uint32_t); | ||||
|  | ||||
| #define TIMEOUT_SERIAL_FLUSH 1500 | ||||
| // Constructor | ||||
| ESP3DSerialService::ESP3DSerialService(uint8_t id) { | ||||
|   _buffer_size = 0; | ||||
|   _mutex = NULL; | ||||
|   _started = false; | ||||
| #if defined(AUTHENTICATION_FEATURE) | ||||
|   _needauthentication = true; | ||||
| #else | ||||
|   _needauthentication = false; | ||||
| #endif  // AUTHENTICATION_FEATURE | ||||
|   _id = id; | ||||
|   switch (_id) { | ||||
|     case MAIN_SERIAL: | ||||
|       _rxPin = ESP_RX_PIN; | ||||
|       _txPin = ESP_TX_PIN; | ||||
|       _origin = ESP3DClientType::serial; | ||||
|       break; | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     case BRIDGE_SERIAL: | ||||
|       _rxPin = ESP_BRIDGE_RX_PIN; | ||||
|       _txPin = ESP_BRIDGE_TX_PIN; | ||||
|       _origin = ESP3DClientType::serial_bridge; | ||||
|       break; | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT | ||||
|     default: | ||||
|       _rxPin = ESP_RX_PIN; | ||||
|       _txPin = ESP_TX_PIN; | ||||
|       _origin = ESP3DClientType::serial; | ||||
|       break; | ||||
|   } | ||||
|   _messagesInFIFO.setId("in"); | ||||
|   _messagesInFIFO.setMaxSize(0);  // no limit | ||||
|   _baudRate = 0; | ||||
| } | ||||
|  | ||||
| // Destructor | ||||
| ESP3DSerialService::~ESP3DSerialService() { end(); } | ||||
|  | ||||
| // extra parameters that do not need a begin | ||||
| void ESP3DSerialService::setParameters() { | ||||
| #if defined(AUTHENTICATION_FEATURE) | ||||
|   _needauthentication = | ||||
|       (ESP3DSettings::readByte(ESP_SECURE_SERIAL) == 0) ? false : true; | ||||
| #else | ||||
|   _needauthentication = false; | ||||
| #endif  // AUTHENTICATION_FEATURE | ||||
| } | ||||
|  | ||||
| void ESP3DSerialService::initAuthentication() { | ||||
| #if defined(AUTHENTICATION_FEATURE) | ||||
|   _auth = ESP3DAuthenticationLevel::guest; | ||||
| #else | ||||
|   _auth = ESP3DAuthenticationLevel::admin; | ||||
| #endif  // AUTHENTICATION_FEATURE | ||||
| } | ||||
| ESP3DAuthenticationLevel ESP3DSerialService::getAuthentication() { | ||||
|   if (_needauthentication) { | ||||
|     return _auth; | ||||
|   } | ||||
|   return ESP3DAuthenticationLevel::admin; | ||||
| } | ||||
|  | ||||
| void ESP3DSerialService::receiveSerialCb() { esp3d_serial_service.receiveCb(); } | ||||
|  | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
| void ESP3DSerialService::receiveBridgeSeialCb() { | ||||
|   serial_bridge_service.receiveCb(); | ||||
| } | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT | ||||
|  | ||||
| void ESP3DSerialService::receiveCb() { | ||||
|   if (!started()) { | ||||
|     return; | ||||
|   } | ||||
|   if (xSemaphoreTake(_mutex, portMAX_DELAY)) { | ||||
|     uint32_t now = millis(); | ||||
|     while ((millis() - now) < SERIAL_COMMUNICATION_TIMEOUT) { | ||||
|       if (Serials[_serialIndex]->available()) { | ||||
|         _buffer[_buffer_size] = Serials[_serialIndex]->read(); | ||||
|         _buffer_size++; | ||||
|         now = millis(); | ||||
|         if (_buffer_size > ESP3D_SERIAL_BUFFER_SIZE || | ||||
|             _buffer[_buffer_size - 1] == '\n' || | ||||
|             _buffer[_buffer_size - 1] == '\r') { | ||||
|           if (_buffer[_buffer_size - 1] == '\r') { | ||||
|             _buffer[_buffer_size - 1] = '\n'; | ||||
|           } | ||||
|           flushbuffer(); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     xSemaphoreGive(_mutex); | ||||
|   } else { | ||||
|     esp3d_log_e("Mutex not taken"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Setup Serial | ||||
| bool ESP3DSerialService::begin(uint8_t serialIndex) { | ||||
|   _mutex = xSemaphoreCreateMutex(); | ||||
|   if (_mutex == NULL) { | ||||
|     esp3d_log_e("Mutex creation failed"); | ||||
|     return false; | ||||
|   } | ||||
|   _serialIndex = serialIndex - 1; | ||||
|   esp3d_log("Serial %d begin for %d", _serialIndex, _id); | ||||
|   if (_id == BRIDGE_SERIAL && | ||||
|       ESP3DSettings::readByte(ESP_SERIAL_BRIDGE_ON) == 0) { | ||||
|     esp3d_log("Serial %d for %d is disabled", _serialIndex, _id); | ||||
|     return true; | ||||
|   } | ||||
|   if (_serialIndex >= MAX_SERIAL) { | ||||
|     esp3d_log_e("Serial %d begin for %d failed, index out of range", | ||||
|                 _serialIndex, _id); | ||||
|     return false; | ||||
|   } | ||||
|   _lastflush = millis(); | ||||
|   // read from settings | ||||
|   uint32_t br = 0; | ||||
|   uint32_t defaultBr = 0; | ||||
|   switch (_id) { | ||||
|     case MAIN_SERIAL: | ||||
|       br = ESP3DSettings::readUint32(ESP_BAUD_RATE); | ||||
|       defaultBr = ESP3DSettings::getDefaultIntegerSetting(ESP_BAUD_RATE); | ||||
|       break; | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     case BRIDGE_SERIAL: | ||||
|       br = ESP3DSettings::readUint32(ESP_SERIAL_BRIDGE_BAUD); | ||||
|       defaultBr = | ||||
|           ESP3DSettings::getDefaultIntegerSetting(ESP_SERIAL_BRIDGE_BAUD); | ||||
|       break; | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT | ||||
|     default: | ||||
|       esp3d_log_e("Serial %d begin for %d failed, unknown id", _serialIndex, | ||||
|                   _id); | ||||
|       return false; | ||||
|   } | ||||
|   setParameters(); | ||||
|   esp3d_log("Baud rate is %d , default is %d", br, defaultBr); | ||||
|   _buffer_size = 0; | ||||
|   // change only if different from current | ||||
|   if (br != baudRate() || (_rxPin != -1) || (_txPin != -1)) { | ||||
|     if (!ESP3DSettings::isValidIntegerSetting(br, ESP_BAUD_RATE)) { | ||||
|       br = defaultBr; | ||||
|     } | ||||
|     Serials[_serialIndex]->setRxBufferSize(SERIAL_RX_BUFFER_SIZE); | ||||
|     Serials[_serialIndex]->begin(br, ESP_SERIAL_PARAM, _rxPin, _txPin); | ||||
|     _baudRate = br; | ||||
|     if (_id == MAIN_SERIAL) { | ||||
|       Serials[_serialIndex]->onReceive(receiveSerialCb); | ||||
|     } | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     if (_id == BRIDGE_SERIAL) { | ||||
|       Serials[_serialIndex]->onReceive(receiveBridgeSeialCb); | ||||
|     } | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT | ||||
|   } | ||||
|   _started = true; | ||||
|   esp3d_log("Serial %d for %d is started", _serialIndex, _id); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // End serial | ||||
| bool ESP3DSerialService::end() { | ||||
|   flush(); | ||||
|   delay(100); | ||||
|   if (_mutex != NULL) { | ||||
|     vSemaphoreDelete(_mutex); | ||||
|     _mutex = NULL; | ||||
|   } | ||||
|   Serials[_serialIndex]->end(); | ||||
|   _buffer_size = 0; | ||||
|   _started = false; | ||||
|   initAuthentication(); | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // return the array of uint32_t and array size | ||||
| const uint32_t *ESP3DSerialService::get_baudratelist(uint8_t *count) { | ||||
|   if (count) { | ||||
|     *count = sizeof(SupportedBaudList) / sizeof(uint32_t); | ||||
|   } | ||||
|   return SupportedBaudList; | ||||
| } | ||||
|  | ||||
| // Function which could be called in other loop | ||||
| void ESP3DSerialService::handle() { | ||||
|   // Do we have some data waiting | ||||
|   size_t len = _messagesInFIFO.size(); | ||||
|   if (len > 0) { | ||||
|     if (len > 10) { | ||||
|       len = 10; | ||||
|     } | ||||
|     while (len > 0) { | ||||
|       esp3d_log_d("Serial in fifo size %d", _messagesInFIFO.size()); | ||||
|       ESP3DMessage *message = _messagesInFIFO.pop(); | ||||
|       if (message) { | ||||
|         esp3d_commands.process(message); | ||||
|       } else { | ||||
|         esp3d_log_e("Cannot create message"); | ||||
|       } | ||||
|       len--; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void ESP3DSerialService::flushbuffer() { | ||||
|   _buffer[_buffer_size] = 0x0; | ||||
|   if (_buffer_size == 1 && _buffer[0] == '\n') { | ||||
|     _buffer_size = 0; | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   // dispatch command | ||||
|   ESP3DMessage *message = esp3d_message_manager.newMsg( | ||||
|       _origin, | ||||
|       _id == MAIN_SERIAL ? ESP3DClientType::all_clients | ||||
|                          : esp3d_commands.getOutputClient(), | ||||
|       (uint8_t *)_buffer, _buffer_size, getAuthentication()); | ||||
|   if (message) { | ||||
|     // process command | ||||
|     message->type = ESP3DMessageType::unique; | ||||
|     esp3d_log_d("Message sent to fifo list"); | ||||
|     _messagesInFIFO.push(message); | ||||
|   } else { | ||||
|     esp3d_log_e("Cannot create message"); | ||||
|   } | ||||
|   _lastflush = millis(); | ||||
|   _buffer_size = 0; | ||||
| } | ||||
|  | ||||
| // push collected data to buffer and proceed accordingly | ||||
| void ESP3DSerialService::push2buffer(uint8_t *sbuf, size_t len) { | ||||
|  /* if (!_started) { | ||||
|     return; | ||||
|   } | ||||
|   esp3d_log("buffer get %d data ", len); | ||||
|   for (size_t i = 0; i < len; i++) { | ||||
|     _lastflush = millis(); | ||||
|     // command is defined | ||||
|     if ((char(sbuf[i]) == '\n') || (char(sbuf[i]) == '\r')) { | ||||
|       if (_buffer_size < ESP3D_SERIAL_BUFFER_SIZE) { | ||||
|         _buffer[_buffer_size] = sbuf[i]; | ||||
|         _buffer_size++; | ||||
|       } | ||||
|       flushbuffer(); | ||||
|     } else if (esp3d_string::isPrintableChar(char(sbuf[i]))) { | ||||
|       if (_buffer_size < ESP3D_SERIAL_BUFFER_SIZE) { | ||||
|         _buffer[_buffer_size] = sbuf[i]; | ||||
|         _buffer_size++; | ||||
|       } else { | ||||
|         flushbuffer(); | ||||
|         _buffer[_buffer_size] = sbuf[i]; | ||||
|         _buffer_size++; | ||||
|       } | ||||
|     } else {  // it is not printable char | ||||
|       // clean buffer first | ||||
|       if (_buffer_size > 0) { | ||||
|         flushbuffer(); | ||||
|       } | ||||
|       // process char | ||||
|       _buffer[_buffer_size] = sbuf[i]; | ||||
|       _buffer_size++; | ||||
|       flushbuffer(); | ||||
|     } | ||||
|   } | ||||
| */} | ||||
|  | ||||
|  // Reset Serial Setting (baud rate) | ||||
|  bool ESP3DSerialService::reset() { | ||||
|    esp3d_log("Reset serial"); | ||||
|    bool res = false; | ||||
|    switch (_id) { | ||||
|      case MAIN_SERIAL: | ||||
|        return ESP3DSettings::writeUint32( | ||||
|            ESP_BAUD_RATE, | ||||
|            ESP3DSettings::getDefaultIntegerSetting(ESP_BAUD_RATE)); | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|      case BRIDGE_SERIAL: | ||||
|        res = ESP3DSettings::writeByte( | ||||
|            ESP_SERIAL_BRIDGE_ON, | ||||
|            ESP3DSettings::getDefaultByteSetting(ESP_SERIAL_BRIDGE_ON)); | ||||
|        return res && ESP3DSettings::writeUint32( | ||||
|                          ESP_SERIAL_BRIDGE_BAUD, | ||||
|                          ESP3DSettings::getDefaultIntegerSetting( | ||||
|                              ESP_SERIAL_BRIDGE_BAUD)); | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT | ||||
|      default: | ||||
|        return res; | ||||
|    } | ||||
|  } | ||||
|  | ||||
|  void ESP3DSerialService::updateBaudRate(uint32_t br) { | ||||
|    if (br != _baudRate) { | ||||
|      Serials[_serialIndex]->flush(); | ||||
|      Serials[_serialIndex]->updateBaudRate(br); | ||||
|      _baudRate = br; | ||||
|    } | ||||
|  } | ||||
|  | ||||
|  // Get current baud rate | ||||
|  uint32_t ESP3DSerialService::baudRate() { | ||||
|     | ||||
|    return _baudRate; | ||||
|  } | ||||
|  | ||||
|  size_t ESP3DSerialService::writeBytes(const uint8_t *buffer, size_t size) { | ||||
|    if (!_started) { | ||||
|      return 0; | ||||
|    } | ||||
|    if ((uint)Serials[_serialIndex]->availableForWrite() >= size) { | ||||
|      return Serials[_serialIndex]->write(buffer, size); | ||||
|    } else { | ||||
|      size_t sizetosend = size; | ||||
|      size_t sizesent = 0; | ||||
|      uint8_t *buffertmp = (uint8_t *)buffer; | ||||
|      uint32_t starttime = millis(); | ||||
|      // loop until all is sent or timeout | ||||
|      while (sizetosend > 0 && ((millis() - starttime) < 100)) { | ||||
|        size_t available = Serials[_serialIndex]->availableForWrite(); | ||||
|        if (available > 0) { | ||||
|          // in case less is sent | ||||
|          available = Serials[_serialIndex]->write( | ||||
|              &buffertmp[sizesent], | ||||
|              (available >= sizetosend) ? sizetosend : available); | ||||
|          sizetosend -= available; | ||||
|          sizesent += available; | ||||
|          starttime = millis(); | ||||
|        } else { | ||||
|          ESP3DHal::wait(5); | ||||
|        } | ||||
|      } | ||||
|      return sizesent; | ||||
|    } | ||||
|  } | ||||
|  | ||||
|  size_t ESP3DSerialService::readBytes(uint8_t *sbuf, size_t len) { | ||||
|    if (!_started) { | ||||
|      return -1; | ||||
|    } | ||||
|    return Serials[_serialIndex]->readBytes(sbuf, len); | ||||
|  } | ||||
|  | ||||
|  void ESP3DSerialService::flush() { | ||||
|    if (!_started) { | ||||
|      return; | ||||
|    } | ||||
|    Serials[_serialIndex]->flush(); | ||||
|  } | ||||
|  | ||||
|  void ESP3DSerialService::swap() { | ||||
|    // Nothing to do | ||||
|  } | ||||
|  | ||||
|  bool ESP3DSerialService::dispatch(ESP3DMessage *message) { | ||||
|    bool done = false; | ||||
|    // Only is serial service is started | ||||
|    if (_started) { | ||||
|      // Only if message is not null | ||||
|      if (message) { | ||||
|        // if message is not null | ||||
|        if (message->data && message->size != 0) { | ||||
|          if (writeBytes(message->data, message->size) == message->size) { | ||||
|            flush(); | ||||
|            // Delete message now | ||||
|            esp3d_message_manager.deleteMsg(message); | ||||
|            done = true; | ||||
|          } else { | ||||
|            esp3d_log_e("Error while sending data"); | ||||
|          } | ||||
|        } else { | ||||
|          esp3d_log_e("Error null data"); | ||||
|        } | ||||
|      } else { | ||||
|        esp3d_log_e("Error null message"); | ||||
|      } | ||||
|    } | ||||
|    return done; | ||||
|  } | ||||
|  | ||||
| #endif  // COMMUNICATION_PROTOCOL == RAW_SERIAL || | ||||
|         // defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
| #endif  // ARDUINO_ARCH_ESP32 | ||||
| @@ -17,10 +17,9 @@ | ||||
|   License along with This code; if not, write to the Free Software | ||||
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| */ | ||||
| 
 | ||||
| #if defined(ARDUINO_ARCH_ESP8266) | ||||
| #include "../../include/esp3d_config.h" | ||||
| #if COMMUNICATION_PROTOCOL == MKS_SERIAL || \ | ||||
|     COMMUNICATION_PROTOCOL == RAW_SERIAL || defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
| #if COMMUNICATION_PROTOCOL == MKS_SERIAL || COMMUNICATION_PROTOCOL == RAW_SERIAL | ||||
| #include "../../core/esp3d_commands.h" | ||||
| #include "../../core/esp3d_settings.h" | ||||
| #include "../../core/esp3d_string.h" | ||||
| @@ -30,43 +29,18 @@ | ||||
| #include "../mks/mks_service.h" | ||||
| #endif  // COMMUNICATION_PROTOCOL == MKS_SERIAL
 | ||||
| #include "../authentication/authentication_service.h" | ||||
| #if defined(ARDUINO_ARCH_ESP8266) | ||||
| #define MAX_SERIAL 2 | ||||
| HardwareSerial *Serials[MAX_SERIAL] = {&Serial, &Serial1}; | ||||
| #endif  // ARDUINO_ARCH_ESP8266
 | ||||
| 
 | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
| 
 | ||||
| #if defined(CONFIG_IDF_TARGET_ESP32C3) ||  defined(CONFIG_IDF_TARGET_ESP32C6) ||defined(CONFIG_IDF_TARGET_ESP32S2) | ||||
| #define MAX_SERIAL 2 | ||||
| HardwareSerial *Serials[MAX_SERIAL] = {&Serial, &Serial1}; | ||||
| #else | ||||
| #define MAX_SERIAL 3 | ||||
| HardwareSerial *Serials[MAX_SERIAL] = {&Serial, &Serial1, &Serial2}; | ||||
| #endif | ||||
| 
 | ||||
| #endif  // ARDUINO_ARCH_ESP32
 | ||||
| 
 | ||||
| // Serial Parameters
 | ||||
| #define ESP_SERIAL_PARAM SERIAL_8N1 | ||||
| 
 | ||||
| #define ESP3DSERIAL_RUNNING_PRIORITY 1 | ||||
| #define ESP3DSERIAL_RUNNING_CORE 1 | ||||
| #define SERIAL_YIELD 10 | ||||
| 
 | ||||
| ESP3DSerialService esp3d_serial_service = ESP3DSerialService(MAIN_SERIAL); | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
| ESP3DSerialService serial_bridge_service = ESP3DSerialService(BRIDGE_SERIAL); | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT
 | ||||
| 
 | ||||
| #if defined(ARDUINO_ARCH_ESP32) && defined(SERIAL_INDEPENDANT_TASK) | ||||
| TaskHandle_t _hserialtask = nullptr; | ||||
| #endif  // ARDUINO_ARCH_ESP32
 | ||||
| 
 | ||||
| const uint32_t SupportedBaudList[] = {9600,    19200,   38400,  57600,  74880, | ||||
|                                       115200,  230400,  250000, 500000, 921600, | ||||
|                                       1000000, 1958400, 2000000}; | ||||
| const size_t SupportedBaudListSize = sizeof(SupportedBaudList) / sizeof(long); | ||||
| const size_t SupportedBaudListSize = sizeof(SupportedBaudList) / sizeof(uint32_t); | ||||
| 
 | ||||
| #define TIMEOUT_SERIAL_FLUSH 1500 | ||||
| // Constructor
 | ||||
| @@ -79,51 +53,15 @@ ESP3DSerialService::ESP3DSerialService(uint8_t id) { | ||||
|   _needauthentication = false; | ||||
| #endif  // AUTHENTICATION_FEATURE
 | ||||
|   _id = id; | ||||
|   switch (_id) { | ||||
|     case MAIN_SERIAL: | ||||
| 
 | ||||
|   _rxPin = ESP_RX_PIN; | ||||
|   _txPin = ESP_TX_PIN; | ||||
|   _origin = ESP3DClientType::serial; | ||||
|       break; | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     case BRIDGE_SERIAL: | ||||
|       _rxPin = ESP_BRIDGE_RX_PIN; | ||||
|       _txPin = ESP_BRIDGE_TX_PIN; | ||||
|       _origin = ESP3DClientType::serial_bridge; | ||||
|       break; | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT
 | ||||
|     default: | ||||
|       _rxPin = ESP_RX_PIN; | ||||
|       _txPin = ESP_TX_PIN; | ||||
|       _origin = ESP3DClientType::serial; | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Destructor
 | ||||
| ESP3DSerialService::~ESP3DSerialService() { end(); } | ||||
| 
 | ||||
| // dedicated serial task
 | ||||
| #if defined(ARDUINO_ARCH_ESP32) && defined(SERIAL_INDEPENDANT_TASK) | ||||
| void ESP3DSerialTaskfn(void *parameter) { | ||||
|   uint8_t id = *((uint8_t *)parameter); | ||||
|   if (id == MAIN_SERIAL) { | ||||
|     esp3d_log("Serial Task for main serial"); | ||||
|   } else { | ||||
|     esp3d_log("Serial Task for bridge serial"); | ||||
|   } | ||||
|   for (;;) { | ||||
|     esp3d_serial_service.process(); | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     esp3d_log("Serial Task for bridge serial"); | ||||
|     serial_bridge_service.process(); | ||||
| #endif                             // ESP_SERIAL_BRIDGE_OUTPUT
 | ||||
|     ESP3DHal::wait(SERIAL_YIELD);  // Yield to other tasks
 | ||||
|   } | ||||
|   vTaskDelete(NULL); | ||||
| } | ||||
| #endif  // ARDUINO_ARCH_ESP32
 | ||||
| 
 | ||||
| // extra parameters that do not need a begin
 | ||||
| void ESP3DSerialService::setParameters() { | ||||
| #if defined(AUTHENTICATION_FEATURE) | ||||
| @@ -152,11 +90,6 @@ ESP3DAuthenticationLevel ESP3DSerialService::getAuthentication() { | ||||
| bool ESP3DSerialService::begin(uint8_t serialIndex) { | ||||
|   _serialIndex = serialIndex - 1; | ||||
|   esp3d_log("Serial %d begin for %d", _serialIndex, _id); | ||||
|   if (_id == BRIDGE_SERIAL && | ||||
|       ESP3DSettings::readByte(ESP_SERIAL_BRIDGE_ON) == 0) { | ||||
|     esp3d_log("Serial %d for %d is disabled", _serialIndex, _id); | ||||
|     return true; | ||||
|   } | ||||
|   if (_serialIndex >= MAX_SERIAL) { | ||||
|     esp3d_log_e("Serial %d begin for %d failed, index out of range", | ||||
|                 _serialIndex, _id); | ||||
| @@ -164,25 +97,11 @@ bool ESP3DSerialService::begin(uint8_t serialIndex) { | ||||
|   } | ||||
|   _lastflush = millis(); | ||||
|   // read from settings
 | ||||
|   long br = 0; | ||||
|   long defaultBr = 0; | ||||
|   switch (_id) { | ||||
|     case MAIN_SERIAL: | ||||
|   uint32_t br = 0; | ||||
|   uint32_t defaultBr = 0; | ||||
| 
 | ||||
|   br = ESP3DSettings::readUint32(ESP_BAUD_RATE); | ||||
|   defaultBr = ESP3DSettings::getDefaultIntegerSetting(ESP_BAUD_RATE); | ||||
|       break; | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     case BRIDGE_SERIAL: | ||||
|       br = ESP3DSettings::readUint32(ESP_SERIAL_BRIDGE_BAUD); | ||||
|       defaultBr = | ||||
|           ESP3DSettings::getDefaultIntegerSetting(ESP_SERIAL_BRIDGE_BAUD); | ||||
|       break; | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT
 | ||||
|     default: | ||||
|       esp3d_log_e("Serial %d begin for %d failed, unknown id", _serialIndex, | ||||
|                   _id); | ||||
|       return false; | ||||
|   } | ||||
|   setParameters(); | ||||
|   esp3d_log("Baud rate is %d , default is %d", br, defaultBr); | ||||
|   _buffer_size = 0; | ||||
| @@ -192,36 +111,11 @@ bool ESP3DSerialService::begin(uint8_t serialIndex) { | ||||
|       br = defaultBr; | ||||
|     } | ||||
|     Serials[_serialIndex]->setRxBufferSize(SERIAL_RX_BUFFER_SIZE); | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
|     Serials[_serialIndex]->begin(br, ESP_SERIAL_PARAM, SERIAL_FULL, | ||||
|                                  (_txPin == -1) ? 1 : _txPin); | ||||
|     if (_rxPin != -1) { | ||||
|       Serials[_serialIndex]->pins((_txPin == -1) ? 1 : _txPin, _rxPin); | ||||
|     } | ||||
| 
 | ||||
| #endif  // ARDUINO_ARCH_ESP8266
 | ||||
| #if defined(ARDUINO_ARCH_ESP32) | ||||
|     Serials[_serialIndex]->begin(br, ESP_SERIAL_PARAM, _rxPin, _txPin); | ||||
| #if defined(SERIAL_INDEPENDANT_TASK) | ||||
|     // create serial task once
 | ||||
|     esp3d_log("Serial %d for %d Task creation", _serialIndex, _id); | ||||
|     if (_hserialtask == nullptr && (_id == MAIN_SERIAL)) { | ||||
|       xTaskCreatePinnedToCore( | ||||
|           ESP3DSerialTaskfn,   /* Task function. */ | ||||
|           "ESP3D Serial Task", /* name of task. */ | ||||
|           8192,                /* Stack size of task */ | ||||
|           &_id,                /* parameter of the task = is main or bridge*/ | ||||
|           ESP3DSERIAL_RUNNING_PRIORITY, /* priority of the task */ | ||||
|           &_hserialtask, /* Task handle to keep track of created task */ | ||||
|           ESP3DSERIAL_RUNNING_CORE /* Core to run the task */ | ||||
|       ); | ||||
|     } | ||||
|     if (_hserialtask == nullptr) { | ||||
|       esp3d_log_e("Serial %d for %d Task creation failed", _serialIndex, _id); | ||||
|       return false; | ||||
|     } | ||||
| #endif  // SERIAL_INDEPENDANT_TASK
 | ||||
| #endif  // ARDUINO_ARCH_ESP32
 | ||||
|   } | ||||
|   _started = true; | ||||
|   esp3d_log("Serial %d for %d is started", _serialIndex, _id); | ||||
| @@ -242,13 +136,13 @@ bool ESP3DSerialService::end() { | ||||
| // return the array of uint32_t and array size
 | ||||
| const uint32_t *ESP3DSerialService::get_baudratelist(uint8_t *count) { | ||||
|   if (count) { | ||||
|     *count = sizeof(SupportedBaudList) / sizeof(long); | ||||
|     *count = sizeof(SupportedBaudList) / sizeof(uint32_t); | ||||
|   } | ||||
|   return SupportedBaudList; | ||||
| } | ||||
| 
 | ||||
| // Function which could be called in other loop
 | ||||
| void ESP3DSerialService::process() { | ||||
| void ESP3DSerialService::handle() { | ||||
|   if (!_started) { | ||||
|     return; | ||||
|   } | ||||
| @@ -275,18 +169,6 @@ void ESP3DSerialService::process() { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Function which could be called in other loop
 | ||||
| void ESP3DSerialService::handle() { | ||||
|   // the serial bridge do not use independant task
 | ||||
|   // not sure if it is sill necessary to do it for the main serial
 | ||||
|   // TBC..
 | ||||
| #if defined(ARDUINO_ARCH_ESP32) && defined(SERIAL_INDEPENDANT_TASK) | ||||
|   if (_id == MAIN_SERIAL) { | ||||
|     return; | ||||
|   } | ||||
| #endif  // ARDUINO_ARCH_ESP32 && SERIAL_INDEPENDANT_TASK0
 | ||||
|   process(); | ||||
| } | ||||
| 
 | ||||
| void ESP3DSerialService::flushbuffer() { | ||||
|   _buffer[_buffer_size] = 0x0; | ||||
| @@ -445,28 +327,11 @@ void ESP3DSerialService::push2buffer(uint8_t *sbuf, size_t len) { | ||||
| // Reset Serial Setting (baud rate)
 | ||||
| bool ESP3DSerialService::reset() { | ||||
|   esp3d_log("Reset serial"); | ||||
|   bool res = false; | ||||
|   switch (_id) { | ||||
|     case MAIN_SERIAL: | ||||
|   return ESP3DSettings::writeUint32( | ||||
|           ESP_BAUD_RATE, | ||||
|           ESP3DSettings::getDefaultIntegerSetting(ESP_BAUD_RATE)); | ||||
| #if defined(ESP_SERIAL_BRIDGE_OUTPUT) | ||||
|     case BRIDGE_SERIAL: | ||||
|       res = ESP3DSettings::writeByte( | ||||
|           ESP_SERIAL_BRIDGE_ON, | ||||
|           ESP3DSettings::getDefaultByteSetting(ESP_SERIAL_BRIDGE_ON)); | ||||
|       return res && | ||||
|              ESP3DSettings::writeUint32(ESP_SERIAL_BRIDGE_BAUD, | ||||
|                                         ESP3DSettings::getDefaultIntegerSetting( | ||||
|                                             ESP_SERIAL_BRIDGE_BAUD)); | ||||
| #endif  // ESP_SERIAL_BRIDGE_OUTPUT
 | ||||
|     default: | ||||
|       return res; | ||||
|   } | ||||
|       ESP_BAUD_RATE, ESP3DSettings::getDefaultIntegerSetting(ESP_BAUD_RATE)); | ||||
| } | ||||
| 
 | ||||
| void ESP3DSerialService::updateBaudRate(long br) { | ||||
| void ESP3DSerialService::updateBaudRate(uint32_t br) { | ||||
|   if (br != baudRate()) { | ||||
|     Serials[_serialIndex]->flush(); | ||||
|     Serials[_serialIndex]->updateBaudRate(br); | ||||
| @@ -474,19 +339,8 @@ void ESP3DSerialService::updateBaudRate(long br) { | ||||
| } | ||||
| 
 | ||||
| // Get current baud rate
 | ||||
| long ESP3DSerialService::baudRate() { | ||||
|   long br = 0; | ||||
|   br = Serials[_serialIndex]->baudRate(); | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
|   // workaround for ESP32
 | ||||
|   if (br == 115201) { | ||||
|     br = 115200; | ||||
|   } | ||||
|   if (br == 230423) { | ||||
|     br = 230400; | ||||
|   } | ||||
| #endif  // ARDUINO_ARCH_ESP32
 | ||||
|   return br; | ||||
| uint32_t ESP3DSerialService::baudRate() { | ||||
|   return Serials[_serialIndex]->baudRate(); | ||||
| } | ||||
| 
 | ||||
| size_t ESP3DSerialService::writeBytes(const uint8_t *buffer, size_t size) { | ||||
| @@ -567,3 +421,4 @@ bool ESP3DSerialService::dispatch(ESP3DMessage *message) { | ||||
| 
 | ||||
| #endif  // COMMUNICATION_PROTOCOL == MKS_SERIAL || COMMUNICATION_PROTOCOL ==
 | ||||
|         // RAW_SERIAL
 | ||||
| #endif  // ARDUINO_ARCH_ESP8266
 | ||||
| @@ -22,7 +22,9 @@ | ||||
| #if defined(WIFI_FEATURE) | ||||
| #ifdef ARDUINO_ARCH_ESP32 | ||||
| #include <esp_wifi.h> | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #include <esp_wifi_ap_get_sta_list.h> | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #endif  // ARDUINO_ARCH_ESP32 | ||||
| #ifdef ARDUINO_ARCH_ESP8266 | ||||
| #endif  // ARDUINO_ARCH_ESP8266 | ||||
| @@ -515,19 +517,30 @@ const char* WiFiConfig::getConnectedSTA(uint8_t* totalcount, bool reset) { | ||||
|     current = 0; | ||||
|   } | ||||
| static wifi_sta_list_t sta_list; | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| static wifi_sta_mac_ip_list_t tcpip_sta_list; | ||||
|  | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 2 | ||||
|   static tcpip_adapter_sta_list_t tcpip_sta_list; | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 2  | ||||
|   if (reset) { | ||||
|     count = 0; | ||||
|   } | ||||
|   if (count == 0) { | ||||
|     current = 0; | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 3     | ||||
|     if(esp_wifi_ap_get_sta_list(&sta_list)!=ESP_OK){ | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 2 | ||||
|     if(tcpip_adapter_get_sta_list(&sta_list, &tcpip_sta_list)!=ESP_OK){ | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 2 | ||||
|   return ""; | ||||
| } | ||||
|     if (sta_list.num > 0) { | ||||
| #if ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
|       ESP_ERROR_CHECK( | ||||
|           esp_wifi_ap_get_sta_list_with_ip(&sta_list, &tcpip_sta_list)); | ||||
| #endif  // ESP_ARDUINO_VERSION_MAJOR == 3 | ||||
|     } | ||||
|     count = sta_list.num; | ||||
|   } | ||||
|   | ||||
| @@ -43,6 +43,8 @@ EspLuaEngine::PauseFunction EspLuaEngine::_pauseFunction = nullptr; | ||||
| String EspLuaEngine::_lastError; | ||||
|  | ||||
| /*Public methods*/ | ||||
| std::atomic<bool> EspLuaEngine::_isPaused{false}; | ||||
| std::atomic<bool> EspLuaEngine::_isRunning{false}; | ||||
|  | ||||
| EspLuaEngine::EspLuaEngine() : _lua_state(nullptr) { | ||||
|   _lua_state = luaL_newstate(); | ||||
|   | ||||
| @@ -71,8 +71,8 @@ class EspLuaEngine { | ||||
|   lua_State* _lua_state; | ||||
|   static PauseFunction _pauseFunction; | ||||
|   static String _lastError; | ||||
|   static inline std::atomic<bool> _isPaused{false}; | ||||
|   static inline std::atomic<bool> _isRunning{false}; | ||||
|   static std::atomic<bool> _isPaused; | ||||
|   static std::atomic<bool> _isRunning; | ||||
|   | ||||
|   static void hookFunction(lua_State* L, lua_Debug* ar); | ||||
|   static void _defaultPauseFunction(); | ||||
|   | ||||
| @@ -16,7 +16,10 @@ libdeps_dir = .piolibdeps | ||||
| data_dir = esp3d/data | ||||
| default_envs = esp32dev | ||||
|  | ||||
|  | ||||
|  | ||||
| [env:esp32dev] | ||||
| ; Arduino  core 3.0.4 - IDF 5.1.4 | ||||
| platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip | ||||
| board = esp32dev | ||||
| framework = arduino | ||||
| @@ -39,6 +42,33 @@ extra_scripts = pre:platformIO/extra_script.py | ||||
| lib_ignore =  | ||||
|     TFT_eSPI  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| [env:esp32_4_4_7] | ||||
| ; Arduino  core 2.0.17 - IDF 4.4.7 | ||||
| platform = espressif32@6.8.1 | ||||
| board = esp32dev | ||||
| framework = arduino | ||||
| monitor_speed = 115200 | ||||
| monitor_echo = yes | ||||
| monitor_filters = send_on_enter, colorize, esp32_exception_decoder | ||||
| ; set frequency to 240MHz | ||||
| ;board_build.f_cpu = 240000000L | ||||
| ; set frequency to 80MHz | ||||
| ;board_build.f_flash = 80000000L | ||||
| ;board_build.flash_mode = qio | ||||
| ;uncomment and modify if board is not 4MB | ||||
| ;board_upload.flash_size = 16MB | ||||
| ;board_build.partitions = default_16MB.csv | ||||
| ; None | ||||
| build_flags =   -DCORE_DEBUG_LEVEL=0 | ||||
| board_build.partitions = min_spiffs.csv | ||||
| upload_speed = 460800 | ||||
| extra_scripts = pre:platformIO/extra_script.py | ||||
| lib_ignore =  | ||||
|     TFT_eSPI  | ||||
|  | ||||
| [env:esp32cam] | ||||
| platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip | ||||
| board = esp32dev | ||||
|   | ||||
		Reference in New Issue
	
	Block a user