text: /*
   * TELNET SERVER FOR ESP8266 / ESP32
   * Cloning the serial port via Telnet.
   *
   * Written by Wolfgang Mattis (arduino@yasheena.de).
   * Version 1.1 / September 7, 2018.
   * MIT license, all text above must be included in any redistribution.
   */
  
  /*
   * DESCRIPTION
   *
   * This module allows you "Debugging over the air". So if you already use
   * ArduinoOTA this is a helpful extension for wireless development. Use
   * "TelnetSpy" instead of "Serial" to send data to the serial port and a copy
   * to a telnet connection. There is a circular buffer which allows to store the
   * data while the telnet connection is not established. So its possible to
   * collect data even when the Wifi and Telnet connections are still not
   * established. Its also possible to create a telnet session only if it is
   * neccessary: then you will get the already collected data as far as it is
   * still stored in the circular buffer. Data send from telnet terminal to
   * ESP8266 / ESP32 will be handled as data received by serial port. It is also
   * possible to use more than one instance of TelnetSpy, for example to send
   * control information on the first instance and data dumps on the second
   * instance.
   *
   * USAGE
   *
   * Add the following line to your sketch:
   *		#include <TelnetSpy.h>
   *		TelnetSpy LOG;
   *
   * Add the following line to your initialisation block ( void setup() ):
   *		LOG.begin();
   *
   * Add the following line at the beginning of your main loop ( void loop() ):
   *		LOG.handle();
   *
   * Use the following functions of the TelnetSpy object to modify behavior
   *
   * Change the port number of this telnet server. If a client is already
   * connected it will be disconnected.
   * Default: 23
   *		void setPort(uint16_t portToUse);
   *
   * Change the message which will be send to the telnet client after a session
   * is established.
   * Default: "Connection established via TelnetSpy.\n"
   *		void setWelcomeMsg(char* msg);
   *
   * Change the message which will be send to the telnet client if another
   * session is already established.
   * Default: "TelnetSpy: Only one connection possible.\n"
   *		void setRejectMsg(char* msg);
   *
   * Change the amount of characters to collect before sending a telnet block.
   * Default: 64
   *		void setMinBlockSize(uint16_t minSize);
   *
   * Change the time (in ms) to wait before sending a telnet block if its size is
   * less than <minSize> (defined by setMinBlockSize).
   * Default: 100
   *		void setCollectingTime(uint16_t colTime);
   *
   * Change the maximum size of the telnet packets to send.
   * Default: 512
   *		void setMaxBlockSize(uint16_t maxSize);
   *
   * Change the size of the ring buffer. Set it to 0 to disable buffering.
   * Changing size tries to preserve the already collected data. If the new
   * buffer size is too small the youngest data will be preserved only. Returns
   * false if the requested buffer size cannot be set.
   * Default: 3000
   *		bool setBufferSize(uint16_t newSize);
   *
   * This function returns the actual size of the ring buffer.
   *		uint16_t getBufferSize();
   *
   * Enable / disable storing new data in the ring buffer if no telnet connection
   * is established. This function allows you to store important data only. You
   * can do this by disabling "storeOffline" for sending less important data.
   * Default: true
   *		void setStoreOffline(bool store);
   *
   * Get actual state of storing data when offline.
   *		bool getStoreOffline();
   *
   * If no data is sent via TelnetSpy the detection of a disconnected client has
   * a long timeout. Use setPingTime to define the time (in ms) without traffic
   * after which a ping (chr(0)) is sent to the telnet client to detect a
   * disconnect earlier. Use 0 as parameter to disable pings.
   * Default: 1500
   *		void setPingTime(uint16_t pngTime);
   *
   * Set the serial port you want to use with this object (especially for ESP32)
   * or NULL if no serial port should be used (telnet only).
   * Default: Serial
   *		void setSerial(HardwareSerial* usedSerial);
   *
   * This function returns true, if a telnet client is connected.
   *		bool isClientConnected();
   *
   * This function installs a callback function which will be called on every
   * telnet connect of this object (except rejected connect tries). Use NULL to
   * remove the callback.
   * Default: NULL
   *		void setCallbackOnConnect(void (*callback)());
   *
   * This function installs a callback function which will be called on every
   * telnet disconnect of this object (except rejected connect tries). Use NULL
   * to remove the callback.
   * Default: NULL
   *		void setCallbackOnDisconnect(void (*callback)());
   *
   * HINT
   *
   * Add the following lines to your sketch:
   *		#define SERIAL TelnetSpy
   *		//#define SERIAL Serial
   *
   * Replace "Serial" with "SERIAL" in your sketch. Now you can switch between
   * serial only and serial with telnet by changing the comments of the defines
   * only.
   *
   * IMPORTANT
   *
   * To connect to the telnet server you have to:
   *	- establish the Wifi connection
   *	- execute "TelnetSpy.begin(WhatEverYouWant);"
   *
   * The order is not important.
   *
   * All you do with "Serial" you can also do with "TelnetSpy", but remember:
   * Transfering data also via telnet will need more performance than the serial
   * port only. So time critical things may be influenced.
   *
   * It is not possible to establish more than one telnet connection at the same
   * time. But its possible to use more than one instance of TelnetSpy.
   *
   * If you have problems with low memory you may reduce the value of the define
   * TELNETSPY_BUFFER_LEN for a smaller ring buffer on initialisation.
   *
   * Usage of void setDebugOutput(bool) to enable / disable of capturing of
   * os_print calls when you have more than one TelnetSpy instance: That
   * TelnetSpy object will handle this functionallity where you used
   * setDebugOutput at last. On default TelnetSpy has the capturing of OS_print
   * calls enabled. So if you have more instances the last created instance will
   * handle the capturing.
   */
  
  #ifndef TelnetSpy_h
  #define TelnetSpy_h
  
  #define TELNETSPY_BUFFER_LEN 3000
  #define TELNETSPY_MIN_BLOCK_SIZE 64
  #define TELNETSPY_COLLECTING_TIME 100
  #define TELNETSPY_MAX_BLOCK_SIZE 512
  #define TELNETSPY_PING_TIME 1500
  #define TELNETSPY_PORT 23
  #define TELNETSPY_CAPTURE_OS_PRINT true
  #define TELNETSPY_WELCOME_MSG "Connection established via TelnetSpy.\r\n"
  #define TELNETSPY_REJECT_MSG "TelnetSpy: Only one connection possible.\r\n"
  
  #ifdef ESP8266
  #include <ESP8266WiFi.h>
  // empty defines, so on ESP8266 nothing will be changed
  #define CRITCAL_SECTION_MUTEX
  #define CRITCAL_SECTION_START
  #define CRITCAL_SECTION_END
  #else // ESP32
  #include <WiFi.h>
  // add spinlock for ESP32
  #define CRITCAL_SECTION_MUTEX portMUX_TYPE AtomicMutex = portMUX_INITIALIZER_UNLOCKED;
  // Non-static Data Member Initializers, see: https://web.archive.org/web/20160316174223/https://blogs.oracle.com/pcarlini/entry/c_11_tidbits_non_static
  #define CRITCAL_SECTION_START portENTER_CRITICAL(&AtomicMutex);
  #define CRITCAL_SECTION_END portEXIT_CRITICAL(&AtomicMutex);
  #endif
  #include <WiFiClient.h>
  
  class TelnetSpy : public Stream
  {
  public:
  	TelnetSpy();
  	~TelnetSpy();
  	void handle(void);
  	void setPort(uint16_t portToUse);
  	void setWelcomeMsg(char *msg);
  	void setRejectMsg(char *msg);
  	void setMinBlockSize(uint16_t minSize);
  	void setCollectingTime(uint16_t colTime);
  	void setMaxBlockSize(uint16_t maxSize);
  	bool setBufferSize(uint16_t newSize);
  	uint16_t getBufferSize();
  	void setStoreOffline(bool store);
  	bool getStoreOffline();
  	void setPingTime(uint16_t pngTime);
  	void setSerial(HardwareSerial *usedSerial);
  	bool isClientConnected();
  	void setCallbackOnConnect(void (*callback)());
  	void setCallbackOnDisconnect(void (*callback)());
  	// Functions offered by HardwareSerial class:
  #ifdef ESP8266
  	void begin(unsigned long baud)
  	{
  		begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
  	}
  	void begin(unsigned long baud, SerialConfig config) { begin(baud, config, SERIAL_FULL, 1); }
  	void begin(unsigned long baud, SerialConfig config, SerialMode mode) { begin(baud, config, mode, 1); }
  	void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
  #else // ESP32
  	void begin(unsigned long baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false);
  #endif
  	void end();
  #ifdef ESP8266
  	void swap()
  	{
  		swap(1);
  	}
  	void swap(uint8_t tx_pin);
  	void set_tx(uint8_t tx_pin);
  	void pins(uint8_t tx, uint8_t rx);
  	bool isTxEnabled(void);
  	bool isRxEnabled(void);
  #endif
  	int available(void) override;
  	int peek(void) override;
  	int read(void) override;
  	int availableForWrite(void);
  	void flush(void) override;
  	size_t write(uint8_t) override;
  	inline size_t write(unsigned long n) { return write((uint8_t)n); }
  	inline size_t write(long n) { return write((uint8_t)n); }
  	inline size_t write(unsigned int n) { return write((uint8_t)n); }
  	inline size_t write(int n) { return write((uint8_t)n); }
  	using Print::write;
  	operator bool() const;
  	void setDebugOutput(bool);
  	uint32_t baudRate(void);
  
  protected:
  	CRITCAL_SECTION_MUTEX
  	void sendBlock(void);
  	void addTelnetBuf(char c);
  	char pullTelnetBuf();
  	char peekTelnetBuf();
  	int telnetAvailable();
  	WiFiServer *telnetServer;
  	WiFiClient client;
  	uint16_t port;
  	HardwareSerial *usedSer;
  	bool storeOffline;
  	bool started;
  	bool listening;
  	bool firstMainLoop;
  	unsigned long waitRef;
  	unsigned long pingRef;
  	uint16_t pingTime;
  	char *welcomeMsg;
  	char *rejectMsg;
  	uint16_t minBlockSize;
  	uint16_t collectingTime;
  	uint16_t maxBlockSize;
  	bool debugOutput;
  	char *telnetBuf;
  	uint16_t bufLen;
  	uint16_t bufUsed;
  	uint16_t bufRdIdx;
  	uint16_t bufWrIdx;
  	bool connected;
  	void (*callbackConnect)();
  	void (*callbackDisconnect)();
  };
  
  #endif
  