mirror of
				https://github.com/KevinMidboe/TinyGSM.git
				synced 2025-10-29 18:00:18 +00:00 
			
		
		
		
	Add source and examples
This commit is contained in:
		
							
								
								
									
										438
									
								
								TinyGsmClient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										438
									
								
								TinyGsmClient.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,438 @@ | |||||||
|  | /** | ||||||
|  |  * @file       TinyGsmClient.h | ||||||
|  |  * @author     Volodymyr Shymanskyy | ||||||
|  |  * @license    LGPL-3.0 | ||||||
|  |  * @copyright  Copyright (c) 2016 Volodymyr Shymanskyy | ||||||
|  |  * @date       Nov 2016 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef TinyGsmClient_h | ||||||
|  | #define TinyGsmClient_h | ||||||
|  |  | ||||||
|  | #if defined(SPARK) || defined(PARTICLE) | ||||||
|  |     #include "Particle.h" | ||||||
|  | #elif defined(ARDUINO) | ||||||
|  |     #if ARDUINO >= 100 | ||||||
|  |         #include "Arduino.h" | ||||||
|  |     #else | ||||||
|  |         #include "WProgram.h" | ||||||
|  |     #endif | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #include <Client.h> | ||||||
|  | #include <TinyGsmFifo.h> | ||||||
|  |  | ||||||
|  | #if defined(__AVR__) | ||||||
|  |   #define GSM_PROGMEM PROGMEM | ||||||
|  |   typedef const __FlashStringHelper* GsmConstStr; | ||||||
|  |   #define FP(x) (reinterpret_cast<GsmConstStr>(x)) | ||||||
|  | #else | ||||||
|  |   #define GSM_PROGMEM | ||||||
|  |   typedef const char* GsmConstStr; | ||||||
|  |   #define FP(x) x | ||||||
|  |   #undef F | ||||||
|  |   #define F(x) x | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | //#define GSM_USE_HEX | ||||||
|  | #define GSM_RX_BUFFER 64 | ||||||
|  |  | ||||||
|  | #define GSM_NL "\r\n" | ||||||
|  | static constexpr char GSM_OK[] GSM_PROGMEM = "OK" GSM_NL; | ||||||
|  | static constexpr char GSM_ERROR[] GSM_PROGMEM = "ERROR" GSM_NL; | ||||||
|  |  | ||||||
|  | class TinyGsmClient | ||||||
|  |     : public Client | ||||||
|  | { | ||||||
|  |   typedef TinyGsmFifo<uint8_t, GSM_RX_BUFFER> RxFifo; | ||||||
|  |  | ||||||
|  | #ifdef GSM_DEBUG | ||||||
|  |   template<typename T> | ||||||
|  |   void DBG(T last) { | ||||||
|  |     GSM_DEBUG.println(last); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<typename T, typename... Args> | ||||||
|  |   void DBG(T head, Args... tail) { | ||||||
|  |     GSM_DEBUG.print(head); | ||||||
|  |     GSM_DEBUG.print(' '); | ||||||
|  |     DBG(tail...); | ||||||
|  |   } | ||||||
|  | #else | ||||||
|  |   #define DBG(...) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |   TinyGsmClient(Stream& stream, uint8_t mux = 1) | ||||||
|  |     : stream(stream) | ||||||
|  |     , mux(mux) | ||||||
|  |     , sock_available(0) | ||||||
|  |     , sock_connected(false) | ||||||
|  |   {} | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |  | ||||||
|  |   virtual int connect(const char *host, uint16_t port) { | ||||||
|  |     return modemConnect(host, port); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual int connect(IPAddress ip, uint16_t port) { | ||||||
|  |     String host; host.reserve(16); | ||||||
|  |     host += ip[0]; | ||||||
|  |     host += "."; | ||||||
|  |     host += ip[1]; | ||||||
|  |     host += "."; | ||||||
|  |     host += ip[2]; | ||||||
|  |     host += "."; | ||||||
|  |     host += ip[3]; | ||||||
|  |     return modemConnect(host.c_str(), port); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual void stop() { | ||||||
|  |     sendAT(F("+CIPCLOSE="), mux); | ||||||
|  |     sock_connected = false; | ||||||
|  |     waitResponse(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual size_t write(const uint8_t *buf, size_t size) { | ||||||
|  |     maintain(); | ||||||
|  |     return modemSend(buf, size); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual size_t write(uint8_t c) { | ||||||
|  |     return write(&c, 1); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual int available() { | ||||||
|  |     maintain(); | ||||||
|  |     size_t res = rx.size(); | ||||||
|  |     if (res > 0) { | ||||||
|  |       return res; | ||||||
|  |     } | ||||||
|  |     return sock_available; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual int read(uint8_t *buf, size_t size) { | ||||||
|  |     maintain(); | ||||||
|  |     size_t cnt = 0; | ||||||
|  |     while (cnt < size) { | ||||||
|  |       size_t chunk = min(size-cnt, rx.size()); | ||||||
|  |       if (chunk > 0) { | ||||||
|  |         rx.get(buf, chunk); | ||||||
|  |         buf += chunk; | ||||||
|  |         cnt += chunk; | ||||||
|  |       } else { | ||||||
|  |         modemRead(rx.free()); //TODO: min(rx.free(), sock_available) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return cnt; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual int read() { | ||||||
|  |     uint8_t c; | ||||||
|  |     if (read(&c, 1) == 1) { | ||||||
|  |       return c; | ||||||
|  |     } | ||||||
|  |     return -1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   virtual int peek() { return -1; } //TODO | ||||||
|  |   virtual void flush() { stream.flush(); } | ||||||
|  |  | ||||||
|  |   virtual uint8_t connected() { | ||||||
|  |     maintain(); | ||||||
|  |     return sock_connected; | ||||||
|  |   } | ||||||
|  |   virtual operator bool() { return connected(); } | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |   bool factoryDefault() { | ||||||
|  |     if (!autoBaud()) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     sendAT(F("&FZE0&W"));  // Factory + Reset + Echo Off + Write | ||||||
|  |     waitResponse(); | ||||||
|  |     sendAT(F("+IPR=0"));   // Auto-baud | ||||||
|  |     waitResponse(); | ||||||
|  |     sendAT(F("+IFC=0,0")); // No Flow Control | ||||||
|  |     waitResponse(); | ||||||
|  |     sendAT(F("+ICF=3,3")); // 8 data 0 parity 1 stop | ||||||
|  |     waitResponse(); | ||||||
|  |     sendAT(F("+CSCLK=0")); // Disable Slow Clock | ||||||
|  |     waitResponse(); | ||||||
|  |     sendAT(F("&W"));       // Write configuration | ||||||
|  |     return waitResponse() == 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool restart() { | ||||||
|  |     autoBaud(); | ||||||
|  |     sendAT(F("+CFUN=0")); | ||||||
|  |     if (waitResponse(10000L) != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     sendAT(F("+CFUN=1,1")); | ||||||
|  |     if (waitResponse(10000L) != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     delay(3000); | ||||||
|  |     autoBaud(); | ||||||
|  |  | ||||||
|  |     sendAT(F("E0")); | ||||||
|  |     if (waitResponse() != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return 1 == waitResponse(60000, F("Ready")); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool networkConnect(const char* apn, const char* user, const char* pwd) { | ||||||
|  |     autoBaud(); | ||||||
|  |  | ||||||
|  |     // AT+CPIN=pin-code | ||||||
|  |     // AT+CREG? | ||||||
|  |  | ||||||
|  |     networkDisconnect(); | ||||||
|  |  | ||||||
|  |     // AT+CGATT? | ||||||
|  |     // AT+CGATT=1 | ||||||
|  |  | ||||||
|  |     sendAT(F("+CIPMUX=1")); | ||||||
|  |     if (waitResponse() != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendAT(F("+CIPQSEND=1")); | ||||||
|  |     if (waitResponse() != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendAT(F("+CIPRXGET=1")); | ||||||
|  |     if (waitResponse() != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendAT(F("+CSTT=\""), apn, F("\",\""), user, F("\",\""), pwd, F("\"")); | ||||||
|  |     if (waitResponse(60000L) != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendAT(F("+CIICR")); | ||||||
|  |     if (waitResponse(60000L) != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendAT(F("+CIFSR;E0")); | ||||||
|  |     String data; | ||||||
|  |     if (waitResponse(10000L, data) != 1) { | ||||||
|  |       data.replace(GSM_NL, ""); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sendAT(F("+CDNSCFG=\"8.8.8.8\",\"8.8.4.4\"")); | ||||||
|  |     if (waitResponse() != 1) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // AT+CIPSTATUS | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool networkDisconnect() { | ||||||
|  |     sendAT(F("+CIPSHUT")); | ||||||
|  |     return waitResponse(60000L) == 1; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool autoBaud(unsigned long timeout = 10000L) { | ||||||
|  |     for (unsigned long start = millis(); millis() - start < timeout; ) { | ||||||
|  |       sendAT(""); | ||||||
|  |       if (waitResponse() == 1) { | ||||||
|  |           delay(100); | ||||||
|  |           return true; | ||||||
|  |       } | ||||||
|  |       delay(100); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void maintain() { | ||||||
|  |     while (stream.available()) { | ||||||
|  |       waitResponse(10); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |   int modemConnect(const char* host, uint16_t port) { | ||||||
|  |     sendAT(F("+CIPSTART="), mux, ',', F("\"TCP"), F("\",\""), host, F("\","), port); | ||||||
|  |     sock_connected = (1 == waitResponse(75000L, F("CONNECT OK" GSM_NL), F("CONNECT FAIL" GSM_NL), F("ALREADY CONNECT" GSM_NL))); | ||||||
|  |     return sock_connected; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int modemSend(const void* buff, size_t len) { | ||||||
|  |     sendAT(F("+CIPSEND="), mux, ',', len); | ||||||
|  |     if (waitResponse(F(">")) != 1) { | ||||||
|  |       return -1; | ||||||
|  |     } | ||||||
|  |     stream.write((uint8_t*)buff, len); | ||||||
|  |     if (waitResponse(F(GSM_NL "DATA ACCEPT:")) != 1) { | ||||||
|  |       return -1; | ||||||
|  |     } | ||||||
|  |     stream.readStringUntil(','); | ||||||
|  |     String data = stream.readStringUntil('\n'); | ||||||
|  |     return data.toInt(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   size_t modemRead(size_t size) { | ||||||
|  | #ifdef GSM_USE_HEX | ||||||
|  |     sendAT(F("+CIPRXGET=3,"), mux, ',', size); | ||||||
|  |     if (waitResponse(F("+CIPRXGET: 3,")) != 1) { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  | #else | ||||||
|  |     sendAT(F("+CIPRXGET=2,"), mux, ',', size); | ||||||
|  |     if (waitResponse(F("+CIPRXGET: 2,")) != 1) { | ||||||
|  |       return 0; | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |     stream.readStringUntil(','); // Skip mux | ||||||
|  |     size_t len = stream.readStringUntil(',').toInt(); | ||||||
|  |     sock_available = stream.readStringUntil('\n').toInt(); | ||||||
|  |  | ||||||
|  |     for (size_t i=0; i<len; i++) { | ||||||
|  | #ifdef GSM_USE_HEX | ||||||
|  |       while (stream.available() < 2) { delay(1); } | ||||||
|  |       char buf[4] = { 0, }; | ||||||
|  |       buf[0] = stream.read(); | ||||||
|  |       buf[1] = stream.read(); | ||||||
|  |       char c = strtol(buf, NULL, 16); | ||||||
|  | #else | ||||||
|  |       while (stream.available() < 1) { delay(1); } | ||||||
|  |       char c = stream.read(); | ||||||
|  | #endif | ||||||
|  |       rx.put(c); | ||||||
|  |     } | ||||||
|  |     waitResponse(); | ||||||
|  |     return len; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   size_t modemGetAvailable() { | ||||||
|  |     sendAT(F("+CIPRXGET=4,"), mux); | ||||||
|  |     size_t result = 0; | ||||||
|  |     for (byte i = 0; i < 2; i++) { | ||||||
|  |       int res = waitResponse(F("+CIPRXGET: 4"), FP(GSM_OK), FP(GSM_ERROR)); | ||||||
|  |       if (res == 1) { | ||||||
|  |         stream.readStringUntil(','); | ||||||
|  |         stream.readStringUntil(','); | ||||||
|  |         result = stream.readStringUntil('\n').toInt(); | ||||||
|  |       } else if (res == 2) { | ||||||
|  |       } else { | ||||||
|  |         return result; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (!result) { | ||||||
|  |       sock_connected = modemGetConnected(); | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   bool modemGetConnected() { | ||||||
|  |     sendAT(F("+CIPSTATUS="), mux); | ||||||
|  |     int res = waitResponse(F(",\"CONNECTED\""), F(",\"CLOSED\""), F(",\"CLOSING\""), F(",\"INITIAL\"")); | ||||||
|  |     waitResponse(); | ||||||
|  |     return 1 == res; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /* Utilities */ | ||||||
|  |   template<typename T> | ||||||
|  |   void streamWrite(T last) { | ||||||
|  |     stream.print(last); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<typename T, typename... Args> | ||||||
|  |   void streamWrite(T head, Args... tail) { | ||||||
|  |     stream.print(head); | ||||||
|  |     streamWrite(tail...); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   int streamRead() { return stream.read(); } | ||||||
|  |   void streamReadAll() { while(stream.available()) { stream.read(); } } | ||||||
|  |  | ||||||
|  |   template<typename... Args> | ||||||
|  |   void sendAT(Args... cmd) { | ||||||
|  |     streamWrite("AT", cmd..., GSM_NL); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // TODO: Optimize this! | ||||||
|  |   uint8_t waitResponse(uint32_t timeout, String& data, | ||||||
|  |                        GsmConstStr r1=FP(GSM_OK), GsmConstStr r2=FP(GSM_ERROR), | ||||||
|  |                        GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) | ||||||
|  |   { | ||||||
|  |     data.reserve(64); | ||||||
|  |     bool gotNewData = false; | ||||||
|  |     int index = 0; | ||||||
|  |     for (unsigned long start = millis(); millis() - start < timeout; ) { | ||||||
|  |       while (stream.available() > 0) { | ||||||
|  |         int a = streamRead(); | ||||||
|  |         if (a < 0) continue; //? | ||||||
|  |         data += (char)a; | ||||||
|  |         if (data.indexOf(r1) >= 0) { | ||||||
|  |           index = 1; | ||||||
|  |           goto finish; | ||||||
|  |         } else if (r2 && data.indexOf(r2) >= 0) { | ||||||
|  |           index = 2; | ||||||
|  |           goto finish; | ||||||
|  |         } else if (r3 && data.indexOf(r3) >= 0) { | ||||||
|  |           index = 3; | ||||||
|  |           goto finish; | ||||||
|  |         } else if (r4 && data.indexOf(r4) >= 0) { | ||||||
|  |           index = 4; | ||||||
|  |           goto finish; | ||||||
|  |         } else if (r5 && data.indexOf(r5) >= 0) { | ||||||
|  |           index = 5; | ||||||
|  |           goto finish; | ||||||
|  |         } else if (data.indexOf(F(GSM_NL "+CIPRXGET: 1,1" GSM_NL)) >= 0) { //TODO: use mux | ||||||
|  |           gotNewData = true; | ||||||
|  |           data = ""; | ||||||
|  |         } else if (data.indexOf(F(GSM_NL "1, CLOSED" GSM_NL)) >= 0) { //TODO: use mux | ||||||
|  |           sock_connected = false; | ||||||
|  |           data = ""; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | finish: | ||||||
|  |     if (!index) { | ||||||
|  |       if (data.length()) { | ||||||
|  |         DBG("### Unhandled:", data); | ||||||
|  |       } | ||||||
|  |       data = ""; | ||||||
|  |     } | ||||||
|  |     if (gotNewData) { | ||||||
|  |       sock_available = modemGetAvailable(); | ||||||
|  |     } | ||||||
|  |     return index; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t waitResponse(uint32_t timeout, | ||||||
|  |                        GsmConstStr r1=FP(GSM_OK), GsmConstStr r2=FP(GSM_ERROR), | ||||||
|  |                        GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) | ||||||
|  |   { | ||||||
|  |     String data; | ||||||
|  |     return waitResponse(timeout, data, r1, r2, r3, r4, r5); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   uint8_t waitResponse(GsmConstStr r1=FP(GSM_OK), GsmConstStr r2=FP(GSM_ERROR), | ||||||
|  |                        GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) | ||||||
|  |   { | ||||||
|  |     return waitResponse(1000, r1, r2, r3, r4, r5); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |   Stream&       stream; | ||||||
|  |   const uint8_t mux; | ||||||
|  |  | ||||||
|  |   RxFifo        rx; | ||||||
|  |   uint16_t      sock_available; | ||||||
|  |   bool          sock_connected; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										136
									
								
								TinyGsmFifo.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								TinyGsmFifo.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | #ifndef TinyGsmFifo_h | ||||||
|  | #define TinyGsmFifo_h | ||||||
|  |  | ||||||
|  | template <class T, unsigned N> | ||||||
|  | class TinyGsmFifo | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |     TinyGsmFifo() | ||||||
|  |     { | ||||||
|  |         clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void clear() | ||||||
|  |     { | ||||||
|  |         _r = 0; | ||||||
|  |         _w = 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // writing thread/context API | ||||||
|  |     //------------------------------------------------------------- | ||||||
|  |  | ||||||
|  |     bool writeable(void) | ||||||
|  |     { | ||||||
|  |         return free() > 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     int free(void) | ||||||
|  |     { | ||||||
|  |         int s = _r - _w; | ||||||
|  |         if (s <= 0) | ||||||
|  |             s += N; | ||||||
|  |         return s - 1; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     T put(const T& c) | ||||||
|  |     { | ||||||
|  |         int i = _w; | ||||||
|  |         int j = i; | ||||||
|  |         i = _inc(i); | ||||||
|  |         while (i == _r) // = !writeable() | ||||||
|  |             /* nothing / just wait */; | ||||||
|  |         _b[j] = c; | ||||||
|  |         _w = i; | ||||||
|  |         return c; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     int put(const T* p, int n, bool t = false) | ||||||
|  |     { | ||||||
|  |         int c = n; | ||||||
|  |         while (c) | ||||||
|  |         { | ||||||
|  |             int f; | ||||||
|  |             while ((f = free()) == 0) // wait for space | ||||||
|  |             { | ||||||
|  |                 if (!t) return n - c; // no more space and not blocking | ||||||
|  |                 /* nothing / just wait */; | ||||||
|  |             } | ||||||
|  |             // check free space | ||||||
|  |             if (c < f) f = c; | ||||||
|  |             int w = _w; | ||||||
|  |             int m = N - w; | ||||||
|  |             // check wrap | ||||||
|  |             if (f > m) f = m; | ||||||
|  |             memcpy(&_b[w], p, f); | ||||||
|  |             _w = _inc(w, f); | ||||||
|  |             c -= f; | ||||||
|  |             p += f; | ||||||
|  |         } | ||||||
|  |         return n - c; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // reading thread/context API | ||||||
|  |     // -------------------------------------------------------- | ||||||
|  |  | ||||||
|  |     bool readable(void) | ||||||
|  |     { | ||||||
|  |         return (_r != _w); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     size_t size(void) | ||||||
|  |     { | ||||||
|  |         int s = _w - _r; | ||||||
|  |         if (s < 0) | ||||||
|  |             s += N; | ||||||
|  |         return s; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     T get(void) | ||||||
|  |     { | ||||||
|  |         int r = _r; | ||||||
|  |         while (r == _w) // = !readable() | ||||||
|  |             /* nothing / just wait */; | ||||||
|  |         T t = _b[r]; | ||||||
|  |         _r = _inc(r); | ||||||
|  |         return t; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     int get(T* p, int n, bool t = false) | ||||||
|  |     { | ||||||
|  |         int c = n; | ||||||
|  |         while (c) | ||||||
|  |         { | ||||||
|  |             int f; | ||||||
|  |             for (;;) // wait for data | ||||||
|  |             { | ||||||
|  |                 f = size(); | ||||||
|  |                 if (f)  break;        // free space | ||||||
|  |                 if (!t) return n - c; // no space and not blocking | ||||||
|  |                 /* nothing / just wait */; | ||||||
|  |             } | ||||||
|  |             // check available data | ||||||
|  |             if (c < f) f = c; | ||||||
|  |             int r = _r; | ||||||
|  |             int m = N - r; | ||||||
|  |             // check wrap | ||||||
|  |             if (f > m) f = m; | ||||||
|  |             memcpy(p, &_b[r], f); | ||||||
|  |             _r = _inc(r, f); | ||||||
|  |             c -= f; | ||||||
|  |             p += f; | ||||||
|  |         } | ||||||
|  |         return n - c; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     int _inc(int i, int n = 1) | ||||||
|  |     { | ||||||
|  |         return (i + n) % N; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     T    _b[N]; | ||||||
|  |     int  _w; | ||||||
|  |     int  _r; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif | ||||||
							
								
								
									
										75
									
								
								examples/BlynkClient/BlynkClient.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								examples/BlynkClient/BlynkClient.ino
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | /************************************************************** | ||||||
|  |  * | ||||||
|  |  * For this example, you need to install Blynk library: | ||||||
|  |  *   https://github.com/blynkkk/blynk-library/releases/latest | ||||||
|  |  * | ||||||
|  |  * TinyGSM Getting Started guide: | ||||||
|  |  *   http://tiny.cc/tiny-gsm-readme | ||||||
|  |  * | ||||||
|  |  ************************************************************** | ||||||
|  |  * | ||||||
|  |  * Blynk is a platform with iOS and Android apps to control | ||||||
|  |  * Arduino, Raspberry Pi and the likes over the Internet. | ||||||
|  |  * You can easily build graphic interfaces for all your | ||||||
|  |  * projects by simply dragging and dropping widgets. | ||||||
|  |  * | ||||||
|  |  * Blynk supports many development boards with WiFi, Ethernet, | ||||||
|  |  * GSM, Bluetooth, BLE, USB/Serial connection methods. | ||||||
|  |  * See more in Blynk library examples and community forum. | ||||||
|  |  * | ||||||
|  |  *                http://www.blynk.io/ | ||||||
|  |  * | ||||||
|  |  * Change GPRS apm, user, pass, and Blynk auth token to run :) | ||||||
|  |  **************************************************************/ | ||||||
|  |  | ||||||
|  | #define BLYNK_PRINT Serial    // Comment this out to disable prints and save space | ||||||
|  |  | ||||||
|  | // Default heartbeat interval for GSM is 60 | ||||||
|  | // If you want override this value, uncomment and set this option: | ||||||
|  | //#define BLYNK_HEARTBEAT 30 | ||||||
|  |  | ||||||
|  | #include <TinyGsmClient.h> | ||||||
|  | #include <BlynkSimpleSIM800.h> | ||||||
|  |  | ||||||
|  | // You should get Auth Token in the Blynk App. | ||||||
|  | // Go to the Project Settings (nut icon). | ||||||
|  | char auth[] = "YourAuthToken"; | ||||||
|  |  | ||||||
|  | // Your GPRS credentials | ||||||
|  | // Leave empty, if missing user or pass | ||||||
|  | char apn[]  = "YourAPN"; | ||||||
|  | char user[] = ""; | ||||||
|  | char pass[] = ""; | ||||||
|  |  | ||||||
|  | // Hardware Serial on Mega, Leonardo, Micro | ||||||
|  | #define GsmSerial Serial1 | ||||||
|  |  | ||||||
|  | // or Software Serial on Uno, Nano | ||||||
|  | //#include <SoftwareSerial.h> | ||||||
|  | //SoftwareSerial GsmSerial(2, 3); // RX, TX | ||||||
|  |  | ||||||
|  | TinyGsmClient gsm(GsmSerial); | ||||||
|  |  | ||||||
|  | void setup() | ||||||
|  | { | ||||||
|  |   // Set console baud rate | ||||||
|  |   Serial.begin(115200); | ||||||
|  |   delay(10); | ||||||
|  |  | ||||||
|  |   // Set GSM module baud rate | ||||||
|  |   GsmSerial.begin(115200); | ||||||
|  |   delay(3000); | ||||||
|  |  | ||||||
|  |   // Restart takes quite some time | ||||||
|  |   // You can skip it in many cases | ||||||
|  |   Serial.println("Restarting modem..."); | ||||||
|  |   gsm.restart(); | ||||||
|  |  | ||||||
|  |   Blynk.begin(auth, gsm, apn, user, pass); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void loop() | ||||||
|  | { | ||||||
|  |   Blynk.run(); | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										153
									
								
								examples/FileDownload/FileDownload.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								examples/FileDownload/FileDownload.ino
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | |||||||
|  | /************************************************************** | ||||||
|  |  * | ||||||
|  |  * For this example, you need to install CRC32 library: | ||||||
|  |  *   https://github.com/vshymanskyy/CRC32.git | ||||||
|  |  * | ||||||
|  |  * TinyGSM Getting Started guide: | ||||||
|  |  *   http://tiny.cc/tiny-gsm-readme | ||||||
|  |  * | ||||||
|  |  **************************************************************/ | ||||||
|  |  | ||||||
|  | #include <TinyGsmClient.h> | ||||||
|  | #include <CRC32.h> | ||||||
|  |  | ||||||
|  | // Your GPRS credentials | ||||||
|  | // Leave empty, if missing user or pass | ||||||
|  | char apn[]  = "YourAPN"; | ||||||
|  | char user[] = ""; | ||||||
|  | char pass[] = ""; | ||||||
|  |  | ||||||
|  | // Use Hardware Serial on Mega, Leonardo, Micro | ||||||
|  | #define GsmSerial Serial1 | ||||||
|  |  | ||||||
|  | // or Software Serial on Uno, Nano | ||||||
|  | //#include <SoftwareSerial.h> | ||||||
|  | //SoftwareSerial GsmSerial(2, 3); // RX, TX | ||||||
|  |  | ||||||
|  | TinyGsmClient client(GsmSerial); | ||||||
|  |  | ||||||
|  | char server[] = "cdn.rawgit.com"; | ||||||
|  | char resource[] = "/vshymanskyy/tinygsm/master/extras/test_10k.hex"; | ||||||
|  | uint32_t knownCRC32 = 0x54b3dcbf; | ||||||
|  | uint32_t knownFileSize = 10240;   // In case server does not send it | ||||||
|  |  | ||||||
|  | void setup() { | ||||||
|  |   // Set console baud rate | ||||||
|  |   Serial.begin(115200); | ||||||
|  |   delay(10); | ||||||
|  |  | ||||||
|  |   // Set GSM module baud rate | ||||||
|  |   GsmSerial.begin(115200); | ||||||
|  |   delay(3000); | ||||||
|  |  | ||||||
|  |   // Restart takes quite some time | ||||||
|  |   // You can skip it in many cases | ||||||
|  |   Serial.println("Restarting modem..."); | ||||||
|  |   client.restart(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void printPercent(uint32_t readLength, uint32_t contentLength) { | ||||||
|  |   // If we know the total length | ||||||
|  |   if (contentLength != -1) { | ||||||
|  |     Serial.print(String("\r ") + ((100.0 * readLength) / contentLength) + "%"); | ||||||
|  |   } else { | ||||||
|  |     Serial.println(readLength); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void loop() { | ||||||
|  |   Serial.print("Connecting to "); | ||||||
|  |   Serial.print(apn); | ||||||
|  |   if (!client.networkConnect(apn, user, pass)) { | ||||||
|  |     Serial.println(" failed"); | ||||||
|  |     delay(10000); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   Serial.println(" OK"); | ||||||
|  |  | ||||||
|  |   Serial.print("Connecting to "); | ||||||
|  |   Serial.print(server); | ||||||
|  |  | ||||||
|  |   // if you get a connection, report back via serial: | ||||||
|  |   if (!client.connect(server, 80)) { | ||||||
|  |     Serial.println(" failed"); | ||||||
|  |     delay(10000); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   Serial.println(" OK"); | ||||||
|  |   // Make a HTTP request: | ||||||
|  |   client.print(String("GET ") + resource + " HTTP/1.0\r\n"); | ||||||
|  |   client.print(String("Host: ") + server + "\r\n"); | ||||||
|  |   client.print("Connection: close\r\n\r\n"); | ||||||
|  |  | ||||||
|  |   long timeout = millis(); | ||||||
|  |   while (client.available() == 0) { | ||||||
|  |     if (millis() - timeout > 5000L) { | ||||||
|  |       Serial.println(">>> Client Timeout !"); | ||||||
|  |       client.stop(); | ||||||
|  |       delay(10000L); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Serial.println("Reading response header"); | ||||||
|  |   uint32_t contentLength = knownFileSize; | ||||||
|  |  | ||||||
|  |   while (client.available()) { | ||||||
|  |     String line = client.readStringUntil('\n'); | ||||||
|  |     line.trim(); | ||||||
|  |     //Serial.println(line);    // Uncomment this to show response header | ||||||
|  |     line.toLowerCase(); | ||||||
|  |     if (line.startsWith("content-length:")) { | ||||||
|  |       contentLength = line.substring(line.lastIndexOf(':') + 1).toInt();   | ||||||
|  |     } else if (line.length() == 0) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Serial.println("Reading response data"); | ||||||
|  |   timeout = millis(); | ||||||
|  |   uint32_t readLength = 0; | ||||||
|  |   CRC32 crc; | ||||||
|  |  | ||||||
|  |   unsigned long timeElapsed = millis(); | ||||||
|  |   printPercent(readLength, contentLength); | ||||||
|  |   while (readLength < contentLength && client.connected() && millis() - timeout < 10000L) { | ||||||
|  |     while (client.available()) { | ||||||
|  |       uint8_t c = client.read(); | ||||||
|  |       //Serial.print((char)c);       // Uncomment this to show data | ||||||
|  |       crc.update(c); | ||||||
|  |       readLength++; | ||||||
|  |       if (readLength % (contentLength / 13) == 0) { | ||||||
|  |         printPercent(readLength, contentLength); | ||||||
|  |       } | ||||||
|  |       timeout = millis(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   printPercent(readLength, contentLength); | ||||||
|  |   timeElapsed = millis() - timeElapsed; | ||||||
|  |   Serial.println(); | ||||||
|  |  | ||||||
|  |   client.stop(); | ||||||
|  |   Serial.println("Server disconnected"); | ||||||
|  |  | ||||||
|  |   client.networkDisconnect(); | ||||||
|  |   Serial.println("GPRS disconnected"); | ||||||
|  |   Serial.println(); | ||||||
|  |  | ||||||
|  |   float timeSpent = float(timeElapsed) / 1000; | ||||||
|  |   float theorLimit = ((8.0 * readLength) / 85.6) / 1000; | ||||||
|  |  | ||||||
|  |   Serial.print("Content-Length: ");   Serial.println(contentLength); | ||||||
|  |   Serial.print("Actually read:  ");   Serial.println(readLength); | ||||||
|  |   Serial.print("Calc. CRC32:    0x"); Serial.println(crc.finalize(), HEX); | ||||||
|  |   Serial.print("Known CRC32:    0x"); Serial.println(knownCRC32, HEX); | ||||||
|  |   Serial.print("Time spent:     ");   Serial.print(timeSpent); Serial.println("s"); | ||||||
|  |   Serial.print("85.6kBps limit: ");   Serial.print(theorLimit); Serial.println("s"); | ||||||
|  |  | ||||||
|  |   // Do nothing forevermore | ||||||
|  |   while (true) { | ||||||
|  |     delay(1000); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										130
									
								
								examples/MqttClient/MqttClient.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								examples/MqttClient/MqttClient.ino
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | /************************************************************** | ||||||
|  |  * | ||||||
|  |  * For this example, you need to install PubSubClient library: | ||||||
|  |  *   https://github.com/knolleary/pubsubclient/releases/latest | ||||||
|  |  * | ||||||
|  |  * TinyGSM Getting Started guide: | ||||||
|  |  *   http://tiny.cc/tiny-gsm-readme | ||||||
|  |  * | ||||||
|  |  ************************************************************** | ||||||
|  |  * Use Mosquitto client tools to work with MQTT | ||||||
|  |  *   Ubuntu/Linux: sudo apt-get install mosquitto-clients | ||||||
|  |  *   Windows:      https://mosquitto.org/download/ | ||||||
|  |  * | ||||||
|  |  * Subscribe for messages: | ||||||
|  |  *   mosquitto_sub -h test.mosquitto.org -t GsmClientTest/init -t GsmClientTest/ledStatus -q 1 | ||||||
|  |  * Toggle led: | ||||||
|  |  *   mosquitto_pub -h test.mosquitto.org -t GsmClientTest/led -q 1 -m "toggle" | ||||||
|  |  * | ||||||
|  |  * You can use Node-RED for wiring together MQTT-enabled devices | ||||||
|  |  *   https://nodered.org/ | ||||||
|  |  * Also, take a look at these additional Node-RED modules: | ||||||
|  |  *   node-red-contrib-blynk-websockets | ||||||
|  |  *   node-red-dashboard | ||||||
|  |  * | ||||||
|  |  **************************************************************/ | ||||||
|  |  | ||||||
|  | #include <TinyGsmClient.h> | ||||||
|  | #include <PubSubClient.h> | ||||||
|  |  | ||||||
|  | // Your GPRS credentials | ||||||
|  | // Leave empty, if missing user or pass | ||||||
|  | char apn[]  = "YourAPN"; | ||||||
|  | char user[] = ""; | ||||||
|  | char pass[] = ""; | ||||||
|  |  | ||||||
|  | // Use Hardware Serial on Mega, Leonardo, Micro | ||||||
|  | #define GsmSerial Serial1 | ||||||
|  |  | ||||||
|  | // or Software Serial on Uno, Nano | ||||||
|  | //#include <SoftwareSerial.h> | ||||||
|  | //SoftwareSerial GsmSerial(2, 3); // RX, TX | ||||||
|  |  | ||||||
|  | TinyGsmClient gsm(GsmSerial); | ||||||
|  | PubSubClient mqtt(gsm); | ||||||
|  |  | ||||||
|  | const char* broker = "test.mosquitto.org"; | ||||||
|  |  | ||||||
|  | const char* topicLed = "GsmClientTest/led"; | ||||||
|  | const char* topicInit = "GsmClientTest/init"; | ||||||
|  | const char* topicLedStatus = "GsmClientTest/ledStatus"; | ||||||
|  |  | ||||||
|  | #define LED_PIN 13 | ||||||
|  | int ledStatus = LOW; | ||||||
|  |  | ||||||
|  | long lastReconnectAttempt = 0; | ||||||
|  |  | ||||||
|  | void setup() { | ||||||
|  |   pinMode(LED_PIN, OUTPUT); | ||||||
|  |  | ||||||
|  |   // Set console baud rate | ||||||
|  |   Serial.begin(115200); | ||||||
|  |   delay(10); | ||||||
|  |  | ||||||
|  |   // Set GSM module baud rate | ||||||
|  |   GsmSerial.begin(115200); | ||||||
|  |   delay(3000); | ||||||
|  |  | ||||||
|  |   // Restart takes quite some time | ||||||
|  |   // You can skip it in many cases | ||||||
|  |   Serial.println("Restarting modem..."); | ||||||
|  |   gsm.restart(); | ||||||
|  |  | ||||||
|  |   Serial.print("Connecting to "); | ||||||
|  |   Serial.print(apn); | ||||||
|  |   if (!gsm.networkConnect(apn, user, pass)) { | ||||||
|  |     Serial.println(" failed"); | ||||||
|  |     while (true); | ||||||
|  |   } | ||||||
|  |   Serial.println(" OK"); | ||||||
|  |  | ||||||
|  |   // MQTT Broker setup | ||||||
|  |   mqtt.setServer(broker, 1883); | ||||||
|  |   mqtt.setCallback(mqttCallback); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | boolean mqttConnect() { | ||||||
|  |   Serial.print("Connecting to "); | ||||||
|  |   Serial.print(broker); | ||||||
|  |   if (!mqtt.connect("GsmClientTest")) { | ||||||
|  |     Serial.println(" failed"); | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |   Serial.println(" OK"); | ||||||
|  |   mqtt.publish(topicInit, "GsmClientTest started"); | ||||||
|  |   mqtt.subscribe(topicLed); | ||||||
|  |   return mqtt.connected(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void loop() { | ||||||
|  |  | ||||||
|  |   if (mqtt.connected()) { | ||||||
|  |     mqtt.loop(); | ||||||
|  |   } else { | ||||||
|  |     // Reconnect every 10 seconds | ||||||
|  |     unsigned long t = millis(); | ||||||
|  |     if (t - lastReconnectAttempt > 10000L) { | ||||||
|  |       lastReconnectAttempt = t; | ||||||
|  |       if (mqttConnect()) { | ||||||
|  |         lastReconnectAttempt = 0; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void mqttCallback(char* topic, byte* payload, unsigned int len) { | ||||||
|  |   Serial.print("Message arrived ["); | ||||||
|  |   Serial.print(topic); | ||||||
|  |   Serial.print("]: "); | ||||||
|  |   Serial.write(payload, len); | ||||||
|  |   Serial.println(); | ||||||
|  |  | ||||||
|  |   // Only proceed if incoming message's topic matches | ||||||
|  |   if (String(topic) == topicLed) { | ||||||
|  |     ledStatus = !ledStatus; | ||||||
|  |     digitalWrite(LED_PIN, ledStatus); | ||||||
|  |     mqtt.publish(topicLedStatus, ledStatus ? "1" : "0"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										92
									
								
								examples/WebClient/WebClient.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								examples/WebClient/WebClient.ino
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | /************************************************************** | ||||||
|  |  * | ||||||
|  |  * This sketch connects to a website and downloads a page. | ||||||
|  |  * It can be used to perform HTTP/RESTful API calls. | ||||||
|  |  * | ||||||
|  |  * TinyGSM Getting Started guide: | ||||||
|  |  *   http://tiny.cc/tiny-gsm-readme | ||||||
|  |  * | ||||||
|  |  **************************************************************/ | ||||||
|  |  | ||||||
|  | #include <TinyGsmClient.h> | ||||||
|  |  | ||||||
|  | // Your GPRS credentials | ||||||
|  | // Leave empty, if missing user or pass | ||||||
|  | char apn[]  = "YourAPN"; | ||||||
|  | char user[] = ""; | ||||||
|  | char pass[] = ""; | ||||||
|  |  | ||||||
|  | // Use Hardware Serial on Mega, Leonardo, Micro | ||||||
|  | #define GsmSerial Serial1 | ||||||
|  |  | ||||||
|  | // or Software Serial on Uno, Nano | ||||||
|  | //#include <SoftwareSerial.h> | ||||||
|  | //SoftwareSerial GsmSerial(2, 3); // RX, TX | ||||||
|  |  | ||||||
|  | TinyGsmClient client(GsmSerial); | ||||||
|  |  | ||||||
|  | char server[] = "cdn.rawgit.com"; | ||||||
|  | char resource[] = "/vshymanskyy/tinygsm/master/extras/logo.txt"; | ||||||
|  |  | ||||||
|  | void setup() { | ||||||
|  |   // Set console baud rate | ||||||
|  |   Serial.begin(115200); | ||||||
|  |   delay(10); | ||||||
|  |  | ||||||
|  |   // Set GSM module baud rate | ||||||
|  |   GsmSerial.begin(115200); | ||||||
|  |   delay(3000); | ||||||
|  |  | ||||||
|  |   // Restart takes quite some time | ||||||
|  |   // You can skip it in many cases | ||||||
|  |   Serial.println("Restarting modem..."); | ||||||
|  |   client.restart(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void loop() { | ||||||
|  |   Serial.print("Connecting to "); | ||||||
|  |   Serial.print(apn); | ||||||
|  |   if (!client.networkConnect(apn, user, pass)) { | ||||||
|  |     Serial.println(" failed"); | ||||||
|  |     delay(10000); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   Serial.println(" OK"); | ||||||
|  |  | ||||||
|  |   Serial.print("Connecting to "); | ||||||
|  |   Serial.print(server); | ||||||
|  |   if (!client.connect(server, 80)) { | ||||||
|  |     Serial.println(" failed"); | ||||||
|  |     delay(10000); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   Serial.println(" OK"); | ||||||
|  |  | ||||||
|  |   // Make a HTTP GET request: | ||||||
|  |   client.print(String("GET ") + resource + " HTTP/1.0\r\n"); | ||||||
|  |   client.print(String("Host: ") + server + "\r\n"); | ||||||
|  |   client.print("Connection: close\r\n\r\n"); | ||||||
|  |  | ||||||
|  |   unsigned long timeout = millis(); | ||||||
|  |   while (client.connected() && millis() - timeout < 10000L) { | ||||||
|  |     // Print available data | ||||||
|  |     while (client.available()) { | ||||||
|  |       char c = client.read(); | ||||||
|  |       Serial.print(c); | ||||||
|  |       timeout = millis(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   Serial.println(); | ||||||
|  |  | ||||||
|  |   client.stop(); | ||||||
|  |   Serial.println("Server disconnected"); | ||||||
|  |  | ||||||
|  |   client.networkDisconnect(); | ||||||
|  |   Serial.println("GPRS disconnected"); | ||||||
|  |  | ||||||
|  |   // Do nothing forevermore | ||||||
|  |   while (true) { | ||||||
|  |     delay(1000); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
		Reference in New Issue
	
	Block a user