├── .gitignore ├── BC95.cpp ├── BC95.h ├── BC95Udp.cpp ├── BC95Udp.h ├── CoAP.cpp ├── CoAP.h ├── Dns.cpp ├── Dns.h ├── Examples ├── CoAPClient │ └── CoAPClient.ino ├── DNSResolve │ └── DNSResolve.ino ├── NTPClient │ └── NTPClient.ino └── UDPClient │ └── UDPClient.ino ├── NTPClient.cpp ├── NTPClient.h ├── README.md ├── library.properties └── settings.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /BC95.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | BC95 adapter class for Arduino. 3 | Author: Chavee Issariyapat 4 | E-mail: chavee@nexpie.com 5 | 6 | This software is released under the MIT License. 7 | */ 8 | 9 | #include "BC95.h" 10 | 11 | #if BC95_USE_EXTERNAL_BUFFER == 1 12 | char *buffer = NULL; 13 | size_t buffersize = 0; 14 | #else 15 | char buffer[BC95_BUFFER_SIZE]; 16 | #define buffersize c 17 | #endif 18 | 19 | const char STOPPER[][STOPPERLEN] = {END_LINE, END_OK, END_ERROR}; 20 | 21 | #define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) 22 | #define STOPPERCOUNT COUNT_OF(STOPPER) 23 | 24 | #if BC95_USE_EXTERNAL_BUFFER == 1 25 | void BC95Class::setExternalBuffer(char *sbuffer, size_t sbuffersize) { 26 | buffer = sbuffer; 27 | buffersize = sbuffersize; 28 | } 29 | #endif 30 | 31 | void BC95Class::begin(Stream &serial) { 32 | BC95Serial = &serial; 33 | memset(socketpool, 0, sizeof(SOCKD)*MAXSOCKET); 34 | } 35 | 36 | char* BC95Class::fetchSocketPacket(SOCKD *socket, uint16_t len) { 37 | char *p; 38 | if((p = getSerialResponse("\x0D\x0A", 0))) return p; 39 | 40 | cmdPrint(F("AT+NSORF=")); 41 | cmdPrint(socket->sockid); 42 | cmdPrint(","); 43 | cmdPrintln(len); 44 | 45 | p = getSerialResponse("\x0D\x0A"); 46 | return p; 47 | } 48 | 49 | // return a null-terminated response text from AT-command 50 | char* BC95Class::getSerialResponse(char *prefix) { 51 | return getSerialResponse(prefix, BC95_DEFAULT_SERIAL_TIMEOUT); 52 | } 53 | 54 | char* BC95Class::getSerialResponse(char *prefix, uint32_t timeout) { 55 | 56 | if (readUntilDone(buffer, timeout, buffersize) >= 0) { 57 | char *p,*r; 58 | uint8_t sno; 59 | size_t plen; 60 | 61 | p = strstr((const char *)buffer, "+NSONMI:"); 62 | if (p != NULL) { 63 | #ifdef LOG_DEBUG 64 | Serial.println(p); 65 | #endif 66 | 67 | p = p+8; // skip +NSONMI: 68 | plen = 0; 69 | for (r=p+2; *r>='0' && *r<='9'; r++) { 70 | plen = 10*plen + *r-'0'; 71 | } 72 | sno = (unsigned char)*p - '0'; 73 | if (sno>=0 && sno= 0) { 78 | return parseResponse(buffer, prefix); 79 | } 80 | else { 81 | return NULL; 82 | } 83 | } 84 | else { 85 | #ifdef LOG_DEBUG 86 | Serial.print("Incoming data : "); 87 | Serial.println(buffer); 88 | #endif 89 | return parseResponse(buffer, prefix); 90 | } 91 | } 92 | else { 93 | return NULL; 94 | } 95 | } 96 | 97 | // parse text with a prefix 98 | char* BC95Class::parseResponse(char *rawstr, char* pref) { 99 | uint8_t i; 100 | char *r, *p; 101 | 102 | if (pref==NULL || *pref=='\0') { 103 | return rawstr; 104 | } 105 | p = strstr((const char *)rawstr, (const char *)pref); 106 | if (p != NULL) { 107 | p += strlen(pref); 108 | // skip STOPPER[0] 109 | 110 | for (i=1; iavailable()) { 131 | *p = BC95Serial->read(); 132 | } 133 | else { 134 | if (millis() - starttm > timeout) { 135 | *p = '\0'; 136 | return READ_TIMEOUT; 137 | } 138 | else { 139 | delay(1); 140 | continue; 141 | } 142 | } 143 | 144 | for (i=0; i 0 || (i==0 && strstr(buff, "+NSONMI:")!=NULL)) { 151 | *(p+1) = '\0'; 152 | return i; 153 | } 154 | } 155 | if (p - buff > maxsize) { 156 | *(p+1) = '\0'; 157 | return READ_OVERFLOW; 158 | } 159 | if (*p == '\0') { 160 | return READ_INCOMPLETE; 161 | } 162 | } 163 | p++; 164 | } 165 | return buff; 166 | } 167 | 168 | void BC95Class::reset() { 169 | cmdPrintln(F("AT+NRB")); 170 | delay(2000); 171 | getSerialResponse("REBOOTING"); 172 | cmdPrintln(F("AT+CFUN=1")); 173 | delay(2000); 174 | cmdPrintln(F("AT")); 175 | delay(2000); 176 | while(BC95Serial->available() > 0) BC95Serial->read(); 177 | } 178 | 179 | bool BC95Class::attachNetwork() { 180 | cmdPrintln(F("AT+CGATT=1")); 181 | cmdPrintln(F("AT+CGATT?")); 182 | return getSerialResponse("+CGATT:1"); 183 | } 184 | 185 | bool BC95Class::isAttached() { 186 | cmdPrintln(F("AT+CGATT?")); 187 | return getSerialResponse("+CGATT:1"); 188 | } 189 | 190 | char* BC95Class::getIMEI() { 191 | cmdPrintln(F("AT+CGSN=1")); 192 | return getSerialResponse("+CGSN:"); 193 | } 194 | 195 | char* BC95Class::getIMSI() { 196 | cmdPrintln(F("AT+CIMI")); 197 | return getSerialResponse("\x0D\x0A"); 198 | } 199 | 200 | /* This function needs BC95_BUFFER_SIZE > 200 */ 201 | char* BC95Class::getManufacturerModel() { 202 | cmdPrintln(F("AT+CGMM")); 203 | return getSerialResponse("\x0D\x0A"); 204 | } 205 | 206 | char* BC95Class::getManufacturerRevision() { 207 | cmdPrintln(F("AT+CGMR")); 208 | return getSerialResponse("\x0D\x0A"); 209 | } 210 | 211 | char* BC95Class::getIPAddress() { 212 | cmdPrintln(F("AT+CGPADDR=0")); 213 | return getSerialResponse("+CGPADDR:0,"); 214 | } 215 | 216 | int8_t BC95Class::getSignalStrength() { 217 | char *p; 218 | int8_t r = 0, i; 219 | cmdPrintln(F("AT+CSQ")); 220 | p = getSerialResponse("+CSQ:"); 221 | for (i=0; i<2; i++) { 222 | if (*p>='0' && *p<='9') r=10*r + *p-'0'; 223 | p++; 224 | } 225 | return 2*r-113; 226 | } 227 | 228 | SOCKD* BC95Class::createSocket(uint16_t port){ 229 | uint8_t no; 230 | cmdPrint(F("AT+NSOCR=DGRAM,17,")); 231 | cmdPrint( port ); 232 | cmdPrintln(F(",1")); 233 | no = *(getSerialResponse("\x0D\x0A")) - '0'; 234 | 235 | if (no>=0 && nosockid].status = 0; 277 | } 278 | 279 | uint8_t* getBuffer() { 280 | return (uint8_t *)buffer; 281 | } 282 | 283 | #if !defined NO_GLOBAL_INSTANCES && !defined NO_GLOBAL_BC95 284 | BC95Class BC95; 285 | #endif 286 | -------------------------------------------------------------------------------- /BC95.h: -------------------------------------------------------------------------------- 1 | /* 2 | BC95 adapter class for Arduino. 3 | Author: Chavee Issariyapat 4 | E-mail: chavee@nexpie.com 5 | 6 | This software is released under the MIT License. 7 | */ 8 | 9 | #ifndef BC95_h 10 | #define BC95_h 11 | 12 | #include "settings.h" 13 | #include 14 | #include 15 | 16 | #if BC95_PRINT_DEBUG == 1 17 | #define ECHO_DEBUG 18 | #define LOG_DEBUG 19 | #endif 20 | 21 | #define MAXSOCKET 7 22 | 23 | #define READ_TIMEOUT -1 24 | #define READ_OVERFLOW -2 25 | #define READ_INCOMPLETE -3 26 | #define STOPPERLEN 12 27 | 28 | #define END_LINE "\x0D\x0A" 29 | #define END_OK "\x0D\x0A\x0D\x0A\x4F\x4B\x0D\x0A" 30 | #define END_ERROR "\x0D\x0A\x0D\x0A\x45\x52\x52\x4F\x52\x0D\x0A" 31 | 32 | #define CMDPRINTMACRO(_1,_2,NAME,...) NAME 33 | #define cmdPrint(...) CMDPRINTMACRO(__VA_ARGS__, cmdPrint2, cmdPrint1)(__VA_ARGS__) 34 | 35 | #define CMDPRINTLNMACRO(_1,_2,NAME,...) NAME 36 | #define cmdPrintln(...) CMDPRINTMACRO(__VA_ARGS__, cmdPrintln2, cmdPrintln1)(__VA_ARGS__) 37 | 38 | #ifdef ECHO_DEBUG 39 | #define cmdPrint1(CMD) if(1) { \ 40 | BC95Serial->print(CMD); \ 41 | Serial.print(CMD); \ 42 | } 43 | 44 | #define cmdPrintln1(CMD) if(1) { \ 45 | BC95Serial->println(CMD); \ 46 | Serial.println(CMD); \ 47 | } 48 | 49 | #define cmdPrint2(CMD, ARG) if(1) { \ 50 | BC95Serial->print(CMD,ARG); \ 51 | Serial.print(CMD,ARG); \ 52 | } 53 | 54 | #define cmdPrintln2(CMD, ARG) if(1) { \ 55 | BC95Serial->println(CMD,ARG); \ 56 | Serial.println(CMD,ARG); \ 57 | } 58 | #else 59 | #define cmdPrint1(CMD) BC95Serial->print(CMD); 60 | #define cmdPrintln1(CMD) BC95Serial->println(CMD); 61 | #define cmdPrint2(CMD, ARG) BC95Serial->print(CMD,ARG); 62 | #define cmdPrintln2(CMD, ARG) BC95Serial->println(CMD,ARG); 63 | #endif 64 | 65 | typedef struct SOCKD SOCKD; 66 | struct SOCKD { 67 | uint8_t sockid; 68 | uint8_t status; 69 | uint16_t port; 70 | uint16_t msglen; 71 | uint16_t buff_msglen; 72 | uint16_t bc95_msglen; 73 | }; 74 | 75 | class BC95Class { 76 | public: 77 | BC95(); 78 | 79 | #if BC95_USE_EXTERNAL_BUFFER == 1 80 | void BC95Class::setExternalBuffer(char *sbuffer, size_t sbuffersize); 81 | #endif 82 | 83 | SOCKD socketpool[MAXSOCKET]; 84 | void begin(Stream &); 85 | char* getIMEI(); 86 | char* getIMEI(char *); 87 | char* getIMSI(); 88 | int8_t getSignalStrength(); 89 | char* getManufacturerModel(); 90 | char* getManufacturerRevision(); 91 | 92 | bool attachNetwork(); 93 | bool isAttached(); 94 | char* getIPAddress(); 95 | SOCKD* createSocket(uint16_t); 96 | void reset(); 97 | int sendPacket(SOCKD* socket, IPAddress, uint16_t destPort, char *payload); 98 | int sendPacket(SOCKD* socket, IPAddress, uint16_t destPort, uint8_t *payload, size_t); 99 | char* fetchSocketPacket(SOCKD *socket, uint16_t len); 100 | void closeSocket(SOCKD *socket); 101 | 102 | uint8_t* getBuffer(); 103 | 104 | private: 105 | Stream* BC95Serial; 106 | int readUntilDone(char* buff, uint32_t timeout, size_t); 107 | char* parseResponse(char* rawstr, char* pref); 108 | char* getSerialResponse(char *prefix); 109 | char* getSerialResponse(char *prefix, uint32_t timeout); 110 | }; 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /BC95Udp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | BC95 UDP adapter class for Arduino. 3 | Author: Chavee Issariyapat 4 | E-mail: chavee@nexpie.com 5 | 6 | This software is released under the MIT License. 7 | */ 8 | 9 | #include "settings.h" 10 | #include "Dns.h" 11 | #include 12 | #include "BC95.h" 13 | #include "BC95Udp.h" 14 | 15 | #if BC95UDP_SHARE_GLOBAL_BUFFER == 1 16 | #if BC95UDP_USE_EXTERNAL_BUFFER == 1 17 | uint8_t *pbuffer = NULL; 18 | size_t pbuffersize = 0; 19 | #else 20 | uint8_t pbuffer[BC95UDP_BUFFER_SIZE]; 21 | #define pbuffersize BC95UDP_BUFFER_SIZE 22 | #endif 23 | uint16_t pbufferlen; 24 | #endif 25 | 26 | BC95UDP::BC95UDP() { 27 | _bc95 = &BC95; 28 | pbufferlen = 0; 29 | 30 | #if BC95_USE_EXTERNAL_BUFFER == 1 31 | _bc95->setExternalBuffer(pbuffer, pbuffersize); 32 | #endif 33 | } 34 | 35 | #if BC95UDP_USE_EXTERNAL_BUFFER == 1 36 | void BC95UDP::setExternalBuffer(uint8_t *sbuffer, size_t sbuffersize) { 37 | pbuffer = sbuffer; 38 | pbuffersize = sbuffersize; 39 | 40 | #if BC95UDP_USE_EXTERNAL_BUFFER == 1 41 | _bc95->setExternalBuffer((char *)sbuffer, sbuffersize); 42 | #endif 43 | } 44 | #endif 45 | 46 | uint8_t BC95UDP::begin(uint16_t port) { 47 | socket = _bc95->createSocket(port); 48 | } 49 | 50 | void BC95UDP::stop() { 51 | _bc95->closeSocket(socket); 52 | } 53 | 54 | int BC95UDP::beginPacket(const char *host, uint16_t port) { 55 | int ret = 0; 56 | DNSClient *dns; 57 | IPAddress remote_addr; 58 | 59 | dns = new DNSClient(); 60 | dns->begin(); 61 | ret = dns->getHostByName(host, remote_addr); 62 | delete dns; 63 | 64 | if (ret == 1) { 65 | return beginPacket(remote_addr, port); 66 | } 67 | else return ret; 68 | } 69 | 70 | int BC95UDP::beginPacket(IPAddress ip, uint16_t port) { 71 | flush(); 72 | dip = ip; 73 | dport = port; 74 | return 1; 75 | } 76 | 77 | size_t BC95UDP::write(const uint8_t *buffer, size_t size) { 78 | if (pbufferlen + size <= pbuffersize) { 79 | memcpy(pbuffer+pbufferlen, buffer, size); 80 | pbufferlen += size; 81 | return size; 82 | } 83 | else return 0; 84 | } 85 | 86 | size_t BC95UDP::write(uint8_t byte) { 87 | write(&byte, 1); 88 | return 1; 89 | } 90 | 91 | size_t BC95UDP::write(const __FlashStringHelper *buffer, size_t size) { 92 | if (pbufferlen + size <= pbuffersize) { 93 | memcpy_P(pbuffer+pbufferlen, buffer, size); 94 | pbufferlen += size; 95 | return size; 96 | } 97 | else return 0; 98 | } 99 | 100 | int BC95UDP::endPacket() { 101 | int result; 102 | result = _bc95->sendPacket(socket, dip, dport, pbuffer, pbufferlen); 103 | flush(); 104 | return result; 105 | } 106 | 107 | int BC95UDP::parsePacket() { 108 | uint8_t *p,*q, *k, field; 109 | uint8_t i; 110 | size_t len; 111 | if (pbufferlen > 0) return pbufferlen; 112 | 113 | pbufferlen = 0; 114 | memset(pbuffer, 0, pbuffersize); 115 | q = pbuffer; 116 | do { 117 | len = 0; 118 | #if BC95_USE_EXTERNAL_BUFFER == 1 119 | _bc95->setExternalBuffer(q, pbuffersize-(q-pbuffer)); 120 | #endif 121 | 122 | char *msg = _bc95->fetchSocketPacket(socket, BC95UDP_SERIAL_READ_CHUNK_SIZE); 123 | 124 | if (msg == NULL) { 125 | break; 126 | } 127 | p = msg; 128 | field = 0; 129 | while (field<4) { 130 | if (*p == ',') { 131 | field++; 132 | if (field==3) k = p+1; // point to the beginning of returned size 133 | else if (field == 4) { 134 | *p = '\0'; 135 | len = atoi((char *)k); 136 | // shift p and break to avoid terminator checking below 137 | p++; 138 | break; 139 | } 140 | } 141 | 142 | if (*p=='\x00' || *p=='\x0D' || *p=='\x0A') { 143 | *pbuffer = '\0'; 144 | break; 145 | } 146 | p++; 147 | } 148 | while (*p != '\0' && *p != ',') { 149 | *q = 0; 150 | for (i=0,*q=0; i<2; i++,p++) { 151 | *q = 16*(*q) + ((*p>='A' && *p<='F')?(*p-'A'+10):((*p>='0' && *p<='9')?(*p-'0'):0)); 152 | } 153 | pbufferlen++; 154 | q++; 155 | } 156 | if (len < socket->bc95_msglen) socket->bc95_msglen -= len; 157 | else socket->bc95_msglen = 0; 158 | 159 | } while (socket->bc95_msglen > 0); 160 | 161 | socket->buff_msglen = pbufferlen; 162 | return pbufferlen; 163 | } 164 | 165 | int BC95UDP::available() { 166 | return pbufferlen; 167 | } 168 | 169 | int BC95UDP::read() { 170 | unsigned char c; 171 | read(&c,1); 172 | return c; 173 | } 174 | 175 | int BC95UDP::read(unsigned char* buffer, size_t len) { 176 | uint16_t rlen; 177 | rlen = len>pbufferlen?pbufferlen:len; 178 | 179 | memcpy(buffer, pbuffer, rlen); 180 | memmove(pbuffer, pbuffer+rlen, pbufferlen-rlen); 181 | pbufferlen -= rlen; 182 | return rlen; 183 | } 184 | 185 | int BC95UDP::read(char* buffer, size_t len) { 186 | return read((unsigned char *)buffer, len); 187 | } 188 | 189 | int BC95UDP::peek() { 190 | if (pbufferlen > 0) return (int)*pbuffer; 191 | else return -1; 192 | } 193 | 194 | void BC95UDP::flush() { 195 | if (pbufferlen > 0) { 196 | memset(pbuffer, 0, pbuffersize); 197 | pbufferlen = 0; 198 | } 199 | } 200 | 201 | IPAddress BC95UDP::remoteIP() { 202 | return dip; 203 | } 204 | 205 | uint16_t BC95UDP::remotePort() { 206 | return dport; 207 | } 208 | -------------------------------------------------------------------------------- /BC95Udp.h: -------------------------------------------------------------------------------- 1 | /* 2 | BC95 UDP adapter class for Arduino. 3 | Author: Chavee Issariyapat 4 | E-mail: chavee@nexpie.com 5 | 6 | This software is released under the MIT License. 7 | */ 8 | 9 | #ifndef BC95Udp_h 10 | #define BC95Udp_h 11 | 12 | #include "BC95.h" 13 | #include 14 | 15 | extern BC95Class BC95; 16 | 17 | class BC95UDP : public UDP { 18 | public: 19 | BC95UDP(); 20 | 21 | virtual uint8_t begin(uint16_t); 22 | virtual void stop(); 23 | virtual int beginPacket(IPAddress ip, uint16_t port); 24 | virtual int beginPacket(const char *host, uint16_t port); 25 | virtual int endPacket(); 26 | 27 | virtual size_t write(uint8_t); 28 | virtual size_t write(const uint8_t *buffer, size_t size); 29 | virtual size_t write(const __FlashStringHelper *buffer, size_t size); 30 | 31 | virtual int parsePacket(); 32 | virtual int available(); 33 | virtual int read(); 34 | virtual int read(unsigned char* buffer, size_t len); 35 | virtual int read(char* buffer, size_t len); 36 | virtual int peek(); 37 | virtual void flush(); 38 | 39 | virtual IPAddress remoteIP(); 40 | virtual uint16_t remotePort(); 41 | 42 | #if BC95UDP_USE_EXTERNAL_BUFFER == 1 43 | void setExternalBuffer(uint8_t *sbuffer, size_t sbuffersize); 44 | #endif 45 | private: 46 | BC95Class *_bc95; 47 | IPAddress dip; 48 | uint16_t dport; 49 | SOCKD *socket; 50 | uint8_t *pktcur; 51 | #if BC95UDP_SHARE_GLOBAL_BUFFER == 0 52 | uint8_t pbuffer[BC95UDP_BUFFER_SIZE]; 53 | uint16_t pbufferlen; 54 | #endif 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /CoAP.cpp: -------------------------------------------------------------------------------- 1 | #include "Dns.h" 2 | #include "CoAP.h" 3 | #include "Arduino.h" 4 | 5 | #define LOGGING 6 | 7 | Coap::Coap(UDP& udp) { 8 | this->_udp = &udp; 9 | this->resp = NULL; 10 | } 11 | 12 | bool Coap::start() { 13 | this->start(COAP_DEFAULT_PORT); 14 | return true; 15 | } 16 | 17 | bool Coap::start(int port) { 18 | this->_udp->begin(port); 19 | return true; 20 | } 21 | 22 | uint16_t Coap::sendPacket(CoapPacket &packet, IPAddress ip) { 23 | return this->sendPacket(packet, ip, COAP_DEFAULT_PORT); 24 | } 25 | 26 | uint16_t Coap::sendPacket(CoapPacket &packet, IPAddress ip, int port) { 27 | uint8_t buffer[BUF_MAX_SIZE]; 28 | uint8_t *p = buffer; 29 | uint16_t running_delta = 0; 30 | uint16_t packetSize = 0; 31 | 32 | // make coap packet base header 33 | *p = 0x01 << 6; 34 | *p |= (packet.type & 0x03) << 4; 35 | *p++ |= (packet.tokenlen & 0x0F); 36 | *p++ = packet.code; 37 | *p++ = (packet.messageid >> 8); 38 | *p++ = (packet.messageid & 0xFF); 39 | p = buffer + COAP_HEADER_SIZE; 40 | packetSize += 4; 41 | 42 | // make token 43 | if (packet.token != NULL && packet.tokenlen <= 0x0F) { 44 | memcpy(p, packet.token, packet.tokenlen); 45 | p += packet.tokenlen; 46 | packetSize += packet.tokenlen; 47 | } 48 | 49 | // make option header 50 | for (int i = 0; i < packet.optionnum; i++) { 51 | uint32_t optdelta; 52 | uint8_t len, delta; 53 | 54 | if (packetSize + 5 + packet.options[i].length >= BUF_MAX_SIZE) { 55 | return 0; 56 | } 57 | optdelta = packet.options[i].number - running_delta; 58 | COAP_OPTION_DELTA(optdelta, &delta); 59 | COAP_OPTION_DELTA((uint32_t)packet.options[i].length, &len); 60 | 61 | *p++ = (0xFF & (delta << 4 | len)); 62 | if (delta == 13) { 63 | *p++ = (optdelta - 13); 64 | packetSize++; 65 | } else if (delta == 14) { 66 | *p++ = ((optdelta - 269) >> 8); 67 | *p++ = (0xFF & (optdelta - 269)); 68 | packetSize+=2; 69 | } if (len == 13) { 70 | *p++ = (packet.options[i].length - 13); 71 | packetSize++; 72 | } else if (len == 14) { 73 | *p++ = (packet.options[i].length >> 8); 74 | *p++ = (0xFF & (packet.options[i].length - 269)); 75 | packetSize+=2; 76 | } 77 | 78 | memcpy(p, packet.options[i].buffer, packet.options[i].length); 79 | p += packet.options[i].length; 80 | packetSize += packet.options[i].length + 1; 81 | running_delta = packet.options[i].number; 82 | } 83 | 84 | // make payload 85 | if (packet.payloadlen > 0) { 86 | if ((packetSize + 1 + packet.payloadlen) >= BUF_MAX_SIZE) { 87 | return 0; 88 | } 89 | *p++ = 0xFF; 90 | memcpy(p, packet.payload, packet.payloadlen); 91 | packetSize += 1 + packet.payloadlen; 92 | } 93 | 94 | _udp->beginPacket(ip, port); 95 | _udp->write(buffer, packetSize); 96 | _udp->endPacket(); 97 | 98 | return packet.messageid; 99 | } 100 | 101 | uint16_t Coap::get(IPAddress ip, int port, char *url) { 102 | return this->send(ip, port, url, COAP_CON, COAP_GET, NULL, 0, NULL, 0); 103 | } 104 | 105 | uint16_t Coap::get(char *host, int port, char *url) { 106 | return this->send(host, port, url, COAP_CON, COAP_GET, NULL, 0, NULL, 0); 107 | } 108 | 109 | uint16_t Coap::put(IPAddress ip, int port, char *url, char *payload) { 110 | return this->send(ip, port, url, COAP_CON, COAP_PUT, NULL, 0, (uint8_t *)payload, strlen(payload)); 111 | } 112 | 113 | uint16_t Coap::put(IPAddress ip, int port, char *url, char *payload, int payloadlen) { 114 | return this->send(ip, port, url, COAP_CON, COAP_PUT, NULL, 0, (uint8_t *)payload, payloadlen); 115 | } 116 | 117 | uint16_t Coap::put(char *host, int port, char *url, char *payload) { 118 | return this->send(host, port, url, COAP_CON, COAP_PUT, NULL, 0, (uint8_t *)payload, strlen(payload)); 119 | } 120 | 121 | uint16_t Coap::put(char *host, int port, char *url, char *payload, int payloadlen) { 122 | return this->send(host, port, url, COAP_CON, COAP_PUT, NULL, 0, (uint8_t *)payload, payloadlen); 123 | } 124 | 125 | uint16_t Coap::send(char *host, int port, char *url, COAP_TYPE type, COAP_METHOD method, uint8_t *token, uint8_t tokenlen, uint8_t *payload, uint32_t payloadlen) { 126 | int ret; 127 | DNSClient *dns; 128 | IPAddress remote_addr; 129 | 130 | dns = new DNSClient(); 131 | dns->begin(); 132 | ret = dns->getHostByName(host, remote_addr); 133 | delete dns; 134 | if (!ret) return 0; 135 | 136 | return this->send(remote_addr, port, url, type, method, token, tokenlen, payload, payloadlen); 137 | } 138 | 139 | uint16_t Coap::send(IPAddress ip, int port, char *url, COAP_TYPE type, COAP_METHOD method, uint8_t *token, uint8_t tokenlen, uint8_t *payload, uint32_t payloadlen) { 140 | // make packet 141 | CoapPacket packet; 142 | 143 | packet.type = type; 144 | packet.code = method; 145 | packet.token = token; 146 | packet.tokenlen = tokenlen; 147 | packet.payload = payload; 148 | packet.payloadlen = payloadlen; 149 | packet.optionnum = 0; 150 | packet.messageid = rand(); 151 | 152 | // use URI_HOST UIR_PATH 153 | String ipaddress = String(ip[0]) + String(".") + String(ip[1]) + String(".") + String(ip[2]) + String(".") + String(ip[3]); 154 | packet.options[packet.optionnum].buffer = (uint8_t *)ipaddress.c_str(); 155 | packet.options[packet.optionnum].length = ipaddress.length(); 156 | packet.options[packet.optionnum].number = COAP_URI_HOST; 157 | packet.optionnum++; 158 | 159 | // parse url 160 | int idx = 0; 161 | for (int i = 0; i < strlen(url); i++) { 162 | if (url[i] == '/') { 163 | packet.options[packet.optionnum].buffer = (uint8_t *)(url + idx); 164 | packet.options[packet.optionnum].length = i - idx; 165 | packet.options[packet.optionnum].number = COAP_URI_PATH; 166 | packet.optionnum++; 167 | idx = i + 1; 168 | } 169 | } 170 | 171 | if (idx <= strlen(url)) { 172 | packet.options[packet.optionnum].buffer = (uint8_t *)(url + idx); 173 | packet.options[packet.optionnum].length = strlen(url) - idx; 174 | packet.options[packet.optionnum].number = COAP_URI_PATH; 175 | packet.optionnum++; 176 | } 177 | 178 | // send packet 179 | return this->sendPacket(packet, ip, port); 180 | } 181 | 182 | int Coap::parseOption(CoapOption *option, uint16_t *running_delta, uint8_t **buf, size_t buflen) { 183 | uint8_t *p = *buf; 184 | uint8_t headlen = 1; 185 | uint16_t len, delta; 186 | 187 | if (buflen < headlen) return -1; 188 | 189 | delta = (p[0] & 0xF0) >> 4; 190 | len = p[0] & 0x0F; 191 | 192 | if (delta == 13) { 193 | headlen++; 194 | if (buflen < headlen) return -1; 195 | delta = p[1] + 13; 196 | p++; 197 | } else if (delta == 14) { 198 | headlen += 2; 199 | if (buflen < headlen) return -1; 200 | delta = ((p[1] << 8) | p[2]) + 269; 201 | p+=2; 202 | } else if (delta == 15) return -1; 203 | 204 | if (len == 13) { 205 | headlen++; 206 | if (buflen < headlen) return -1; 207 | len = p[1] + 13; 208 | p++; 209 | } else if (len == 14) { 210 | headlen += 2; 211 | if (buflen < headlen) return -1; 212 | len = ((p[1] << 8) | p[2]) + 269; 213 | p+=2; 214 | } else if (len == 15) 215 | return -1; 216 | 217 | if ((p + 1 + len) > (*buf + buflen)) return -1; 218 | option->number = delta + *running_delta; 219 | option->buffer = p+1; 220 | option->length = len; 221 | *buf = p + 1 + len; 222 | *running_delta += delta; 223 | 224 | return 0; 225 | } 226 | 227 | bool Coap::loop() { 228 | uint8_t buffer[BUF_MAX_SIZE]; 229 | int32_t packetlen = _udp->parsePacket(); 230 | 231 | while (packetlen > 0) { 232 | packetlen = _udp->read(buffer, packetlen >= BUF_MAX_SIZE ? BUF_MAX_SIZE : packetlen); 233 | CoapPacket packet; 234 | 235 | // parse coap packet header 236 | if (packetlen < COAP_HEADER_SIZE || (((buffer[0] & 0xC0) >> 6) != 1)) { 237 | packetlen = _udp->parsePacket(); 238 | continue; 239 | } 240 | 241 | packet.type = (buffer[0] & 0x30) >> 4; 242 | packet.tokenlen = buffer[0] & 0x0F; 243 | packet.code = buffer[1]; 244 | packet.messageid = 0xFF00 & (buffer[2] << 8); 245 | packet.messageid |= 0x00FF & buffer[3]; 246 | 247 | if (packet.tokenlen == 0) packet.token = NULL; 248 | else if (packet.tokenlen <= 8) packet.token = buffer + 4; 249 | else { 250 | packetlen = _udp->parsePacket(); 251 | continue; 252 | } 253 | 254 | // parse packet options/payload 255 | if (COAP_HEADER_SIZE + packet.tokenlen < packetlen) { 256 | int optionIndex = 0; 257 | uint16_t delta = 0; 258 | uint8_t *end = buffer + packetlen; 259 | uint8_t *p = buffer + COAP_HEADER_SIZE + packet.tokenlen; 260 | while(optionIndex < MAX_OPTION_NUM && *p != 0xFF && p < end) { 261 | packet.options[optionIndex]; 262 | if (0 != parseOption(&packet.options[optionIndex], &delta, &p, end-p)) 263 | return false; 264 | optionIndex++; 265 | } 266 | packet.optionnum = optionIndex; 267 | 268 | if (p+1 < end && *p == 0xFF) { 269 | packet.payload = p+1; 270 | packet.payloadlen = end-(p+1); 271 | } else { 272 | packet.payload = NULL; 273 | packet.payloadlen= 0; 274 | } 275 | } 276 | 277 | if (packet.type == COAP_ACK) { 278 | // call response function 279 | #if COAP_ENABLE_ACK_CALLBACK == 1 280 | if (resp != NULL) 281 | resp(packet, _udp->remoteIP(), _udp->remotePort()); 282 | #endif 283 | } else { 284 | // e.g. packet.type == COAP_CON 285 | 286 | String url = ""; 287 | // call endpoint url function 288 | for (int i = 0; i < packet.optionnum; i++) { 289 | if (packet.options[i].number == COAP_URI_PATH && packet.options[i].length > 0) { 290 | char urlname[packet.options[i].length + 1]; 291 | memcpy(urlname, packet.options[i].buffer, packet.options[i].length); 292 | urlname[packet.options[i].length] = NULL; 293 | if(url.length() > 0) 294 | url += "/"; 295 | url += urlname; 296 | } 297 | } 298 | 299 | if (!uri.find(url)) { 300 | sendResponse(_udp->remoteIP(), _udp->remotePort(), packet.messageid, NULL, 0, 301 | COAP_NOT_FOUNT, COAP_NONE, NULL, 0); 302 | } else { 303 | uri.find(url)(packet, _udp->remoteIP(), _udp->remotePort()); 304 | 305 | // ack here? 306 | // sendResponse(_udp->remoteIP(), _udp->remotePort(), packet.messageid); 307 | } 308 | } 309 | 310 | // next packet 311 | packetlen = _udp->parsePacket(); 312 | } 313 | 314 | return true; 315 | } 316 | 317 | uint16_t Coap::sendResponse(IPAddress ip, int port, uint16_t messageid) { 318 | this->sendResponse(ip, port, messageid, NULL, 0, COAP_CONTENT, COAP_TEXT_PLAIN, NULL, 0); 319 | } 320 | 321 | uint16_t Coap::sendResponse(IPAddress ip, int port, uint16_t messageid, char *payload) { 322 | this->sendResponse(ip, port, messageid, payload, strlen(payload), COAP_CONTENT, COAP_TEXT_PLAIN, NULL, 0); 323 | } 324 | 325 | uint16_t Coap::sendResponse(IPAddress ip, int port, uint16_t messageid, char *payload, int payloadlen) { 326 | this->sendResponse(ip, port, messageid, payload, payloadlen, COAP_CONTENT, COAP_TEXT_PLAIN, NULL, 0); 327 | } 328 | 329 | 330 | uint16_t Coap::sendResponse(IPAddress ip, int port, uint16_t messageid, char *payload, int payloadlen, 331 | COAP_RESPONSE_CODE code, COAP_CONTENT_TYPE type, uint8_t *token, int tokenlen) { 332 | // make packet 333 | CoapPacket packet; 334 | 335 | packet.type = COAP_ACK; 336 | packet.code = code; 337 | packet.token = token; 338 | packet.tokenlen = tokenlen; 339 | packet.payload = (uint8_t *)payload; 340 | packet.payloadlen = payloadlen; 341 | packet.optionnum = 0; 342 | packet.messageid = messageid; 343 | 344 | // if more options? 345 | char optionBuffer[2]; 346 | optionBuffer[0] = ((uint16_t)type & 0xFF00) >> 8; 347 | optionBuffer[1] = ((uint16_t)type & 0x00FF) ; 348 | packet.options[packet.optionnum].buffer = (uint8_t *)optionBuffer; 349 | packet.options[packet.optionnum].length = 2; 350 | packet.options[packet.optionnum].number = COAP_CONTENT_FORMAT; 351 | packet.optionnum++; 352 | 353 | return this->sendPacket(packet, ip, port); 354 | } 355 | -------------------------------------------------------------------------------- /CoAP.h: -------------------------------------------------------------------------------- 1 | /* 2 | CoAP library for BC95 Arduino -- modified from CoAP library for Arduino 3 | Author: Chavee Issariyapat 4 | E-mail: chavee@nexpie.com 5 | */ 6 | 7 | /* 8 | CoAP library for Arduino. 9 | This software is released under the MIT License. 10 | Copyright (c) 2014 Hirotaka Niisato 11 | Permission is hereby granted, free of charge, to any person obtaining 12 | a copy of this software and associated documentation files (the 13 | "Software"), to deal in the Software without restriction, including 14 | without limitation the rights to use, copy, modify, merge, publish, 15 | distribute, sublicense, and/or sell copies of the Software, and to 16 | permit persons to whom the Software is furnished to do so, subject to 17 | the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be 20 | included in all copies or substantial portions of the Software. 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | */ 29 | #ifndef __SIMPLE_COAP_H__ 30 | #define __SIMPLE_COAP_H__ 31 | 32 | #include "Udp.h" 33 | 34 | #define MAX_CALLBACK 10 35 | 36 | #define COAP_HEADER_SIZE 4 37 | #define COAP_OPTION_HEADER_SIZE 1 38 | #define COAP_PAYLOAD_MARKER 0xFF 39 | #define MAX_OPTION_NUM 10 40 | #define BUF_MAX_SIZE 160 41 | #define COAP_DEFAULT_PORT 5683 42 | 43 | #define RESPONSE_CODE(class, detail) ((class << 5) | (detail)) 44 | #define COAP_OPTION_DELTA(v, n) (v < 13 ? (*n = (0xFF & v)) : (v <= 0xFF + 13 ? (*n = 13) : (*n = 14))) 45 | 46 | typedef enum { 47 | COAP_CON = 0, 48 | COAP_NONCON = 1, 49 | COAP_ACK = 2, 50 | COAP_RESET = 3 51 | } COAP_TYPE; 52 | 53 | typedef enum { 54 | COAP_GET = 1, 55 | COAP_POST = 2, 56 | COAP_PUT = 3, 57 | COAP_DELETE = 4 58 | } COAP_METHOD; 59 | 60 | typedef enum { 61 | COAP_CREATED = RESPONSE_CODE(2, 1), 62 | COAP_DELETED = RESPONSE_CODE(2, 2), 63 | COAP_VALID = RESPONSE_CODE(2, 3), 64 | COAP_CHANGED = RESPONSE_CODE(2, 4), 65 | COAP_CONTENT = RESPONSE_CODE(2, 5), 66 | COAP_BAD_REQUEST = RESPONSE_CODE(4, 0), 67 | COAP_UNAUTHORIZED = RESPONSE_CODE(4, 1), 68 | COAP_BAD_OPTION = RESPONSE_CODE(4, 2), 69 | COAP_FORBIDDEN = RESPONSE_CODE(4, 3), 70 | COAP_NOT_FOUNT = RESPONSE_CODE(4, 4), 71 | COAP_METHOD_NOT_ALLOWD = RESPONSE_CODE(4, 5), 72 | COAP_NOT_ACCEPTABLE = RESPONSE_CODE(4, 6), 73 | COAP_PRECONDITION_FAILED = RESPONSE_CODE(4, 12), 74 | COAP_REQUEST_ENTITY_TOO_LARGE = RESPONSE_CODE(4, 13), 75 | COAP_UNSUPPORTED_CONTENT_FORMAT = RESPONSE_CODE(4, 15), 76 | COAP_INTERNAL_SERVER_ERROR = RESPONSE_CODE(5, 0), 77 | COAP_NOT_IMPLEMENTED = RESPONSE_CODE(5, 1), 78 | COAP_BAD_GATEWAY = RESPONSE_CODE(5, 2), 79 | COAP_SERVICE_UNAVALIABLE = RESPONSE_CODE(5, 3), 80 | COAP_GATEWAY_TIMEOUT = RESPONSE_CODE(5, 4), 81 | COAP_PROXYING_NOT_SUPPORTED = RESPONSE_CODE(5, 5) 82 | } COAP_RESPONSE_CODE; 83 | 84 | typedef enum { 85 | COAP_IF_MATCH = 1, 86 | COAP_URI_HOST = 3, 87 | COAP_E_TAG = 4, 88 | COAP_IF_NONE_MATCH = 5, 89 | COAP_URI_PORT = 7, 90 | COAP_LOCATION_PATH = 8, 91 | COAP_URI_PATH = 11, 92 | COAP_CONTENT_FORMAT = 12, 93 | COAP_MAX_AGE = 14, 94 | COAP_URI_QUERY = 15, 95 | COAP_ACCEPT = 17, 96 | COAP_LOCATION_QUERY = 20, 97 | COAP_PROXY_URI = 35, 98 | COAP_PROXY_SCHEME = 39 99 | } COAP_OPTION_NUMBER; 100 | 101 | typedef enum { 102 | COAP_NONE = -1, 103 | COAP_TEXT_PLAIN = 0, 104 | COAP_APPLICATION_LINK_FORMAT = 40, 105 | COAP_APPLICATION_XML = 41, 106 | COAP_APPLICATION_OCTET_STREAM = 42, 107 | COAP_APPLICATION_EXI = 47, 108 | COAP_APPLICATION_JSON = 50 109 | } COAP_CONTENT_TYPE; 110 | 111 | class CoapOption { 112 | public: 113 | uint8_t number; 114 | uint8_t length; 115 | uint8_t *buffer; 116 | }; 117 | 118 | class CoapPacket { 119 | public: 120 | uint8_t type; 121 | uint8_t code; 122 | uint8_t *token; 123 | uint8_t tokenlen; 124 | uint8_t *payload; 125 | uint8_t payloadlen; 126 | uint16_t messageid; 127 | 128 | uint8_t optionnum; 129 | CoapOption options[MAX_OPTION_NUM]; 130 | }; 131 | typedef void (*callback)(CoapPacket &, IPAddress, int); 132 | 133 | class CoapUri { 134 | private: 135 | String u[MAX_CALLBACK]; 136 | callback c[MAX_CALLBACK]; 137 | public: 138 | CoapUri() { 139 | for (int i = 0; i < MAX_CALLBACK; i++) { 140 | u[i] = ""; 141 | c[i] = NULL; 142 | } 143 | }; 144 | void add(callback call, String url) { 145 | for (int i = 0; i < MAX_CALLBACK; i++) 146 | if (c[i] != NULL && u[i].equals(url)) { 147 | c[i] = call; 148 | return ; 149 | } 150 | for (int i = 0; i < MAX_CALLBACK; i++) { 151 | if (c[i] == NULL) { 152 | c[i] = call; 153 | u[i] = url; 154 | return; 155 | } 156 | } 157 | }; 158 | callback find(String url) { 159 | for (int i = 0; i < MAX_CALLBACK; i++) if (c[i] != NULL && u[i].equals(url)) return c[i]; 160 | return NULL; 161 | } ; 162 | }; 163 | 164 | class Coap { 165 | private: 166 | UDP *_udp; 167 | CoapUri uri; 168 | callback resp; 169 | int _port; 170 | 171 | uint16_t sendPacket(CoapPacket &packet, IPAddress ip); 172 | uint16_t sendPacket(CoapPacket &packet, IPAddress ip, int port); 173 | int parseOption(CoapOption *option, uint16_t *running_delta, uint8_t **buf, size_t buflen); 174 | 175 | public: 176 | Coap( 177 | UDP& udp 178 | ); 179 | bool start(); 180 | bool start(int port); 181 | void response(callback c) { resp = c; } 182 | 183 | void server(callback c, String url) { uri.add(c, url); } 184 | uint16_t sendResponse(IPAddress ip, int port, uint16_t messageid); 185 | uint16_t sendResponse(IPAddress ip, int port, uint16_t messageid, char *payload); 186 | uint16_t sendResponse(IPAddress ip, int port, uint16_t messageid, char *payload, int payloadlen); 187 | uint16_t sendResponse(IPAddress ip, int port, uint16_t messageid, char *payload, int payloadlen, COAP_RESPONSE_CODE code, COAP_CONTENT_TYPE type, uint8_t *token, int tokenlen); 188 | 189 | uint16_t get(IPAddress ip, int port, char *url); 190 | uint16_t get(char *host, int port, char *url); 191 | 192 | uint16_t put(IPAddress ip, int port, char *url, char *payload); 193 | uint16_t put(IPAddress ip, int port, char *url, char *payload, int payloadlen); 194 | 195 | uint16_t put(char *host, int port, char *url, char *payload); 196 | uint16_t put(char *host, int port, char *url, char *payload, int payloadlen); 197 | 198 | uint16_t send(IPAddress ip, int port, char *url, COAP_TYPE type, COAP_METHOD method, uint8_t *token, uint8_t tokenlen, uint8_t *payload, uint32_t payloadlen); 199 | uint16_t send(char *host, int port, char *url, COAP_TYPE type, COAP_METHOD method, uint8_t *token, uint8_t tokenlen, uint8_t *payload, uint32_t payloadlen); 200 | 201 | bool loop(); 202 | }; 203 | 204 | #endif 205 | -------------------------------------------------------------------------------- /Dns.cpp: -------------------------------------------------------------------------------- 1 | // BC95 DNS client -- modified from Arduino DNS client for WizNet5100-based Ethernet shield 2 | // Author: Chavee Issariyapat 3 | // E-mail: chavee@nexpie.com 4 | 5 | // Arduino DNS client for WizNet5100-based Ethernet shield 6 | // (c) Copyright 2009-2010 MCQN Ltd. 7 | // Released under Apache License, version 2.0 8 | 9 | #include "Dns.h" 10 | #include 11 | 12 | #define SOCKET_NONE 255 13 | #define UDP_HEADER_SIZE 8 14 | #define DNS_HEADER_SIZE 12 15 | #define TTL_SIZE 4 16 | #define QUERY_FLAG (0) 17 | #define RESPONSE_FLAG (1<<15) 18 | #define QUERY_RESPONSE_MASK (1<<15) 19 | #define OPCODE_STANDARD_QUERY (0) 20 | #define OPCODE_INVERSE_QUERY (1<<11) 21 | #define OPCODE_STATUS_REQUEST (2<<11) 22 | #define OPCODE_MASK (15<<11) 23 | #define AUTHORITATIVE_FLAG (1<<10) 24 | #define TRUNCATION_FLAG (1<<9) 25 | #define RECURSION_DESIRED_FLAG (1<<8) 26 | #define RECURSION_AVAILABLE_FLAG (1<<7) 27 | #define RESP_NO_ERROR (0) 28 | #define RESP_FORMAT_ERROR (1) 29 | #define RESP_SERVER_FAILURE (2) 30 | #define RESP_NAME_ERROR (3) 31 | #define RESP_NOT_IMPLEMENTED (4) 32 | #define RESP_REFUSED (5) 33 | #define RESP_MASK (15) 34 | #define TYPE_A (0x0001) 35 | #define CLASS_IN (0x0001) 36 | #define LABEL_COMPRESSION_MASK (0xC0) 37 | // Port number that DNS servers listen on 38 | #define DNS_PORT 53 39 | 40 | // Possible return codes from ProcessResponse 41 | #define SUCCESS 1 42 | #define TIMED_OUT -1 43 | #define INVALID_SERVER -2 44 | #define TRUNCATED -3 45 | #define INVALID_RESPONSE -4 46 | 47 | #define htons(x) ( ((x)<< 8 & 0xFF00) | \ 48 | ((x)>> 8 & 0x00FF) ) 49 | #define ntohs(x) htons(x) 50 | 51 | #define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \ 52 | ((x)<< 8 & 0x00FF0000UL) | \ 53 | ((x)>> 8 & 0x0000FF00UL) | \ 54 | ((x)>>24 & 0x000000FFUL) ) 55 | #define ntohl(x) htonl(x) 56 | 57 | #if DNS_CACHE_SLOT > 0 58 | #if DNS_CACHE_EXPIRE_MS > 0 59 | unsigned long dns_expire_timestamp = 0; 60 | #endif 61 | static uint8_t current_dns_slot = 0; 62 | static dns_cache_struct dnscache[DNS_CACHE_SLOT] = {0}; 63 | #endif 64 | 65 | DNSClient::DNSClient() { 66 | } 67 | 68 | void DNSClient::begin() { 69 | begin(DNS_DEFAULT_SERVER); 70 | } 71 | 72 | void DNSClient::begin(const IPAddress& aDNSServer) { 73 | iDNSServer = aDNSServer; 74 | iRequestId = 0; 75 | } 76 | 77 | int DNSClient::inet_aton(const char* aIPAddrString, IPAddress& aResult) { 78 | // See if we've been given a valid IP address 79 | const char* p =aIPAddrString; 80 | while (*p && ( (*p == '.') || (*p >= '0') || (*p <= '9') )) { 81 | p++; 82 | } 83 | 84 | if (*p == '\0') { 85 | // It's looking promising, we haven't found any invalid characters 86 | p = aIPAddrString; 87 | int segment =0; 88 | int segmentValue =0; 89 | while (*p && (segment < 4)) { 90 | if (*p == '.') { 91 | // We've reached the end of a segment 92 | if (segmentValue > 255) { 93 | // You can't have IP address segments that don't fit in a byte 94 | return 0; 95 | } 96 | else { 97 | aResult[segment] = (byte)segmentValue; 98 | segment++; 99 | segmentValue = 0; 100 | } 101 | } 102 | else { 103 | // Next digit 104 | segmentValue = (segmentValue*10)+(*p - '0'); 105 | } 106 | p++; 107 | } 108 | // We've reached the end of address, but there'll still be the last 109 | // segment to deal with 110 | if ((segmentValue > 255) || (segment > 3)) { 111 | // You can't have IP address segments that don't fit in a byte, 112 | // or more than four segments 113 | return 0; 114 | } 115 | else { 116 | aResult[segment] = (byte)segmentValue; 117 | return 1; 118 | } 119 | } 120 | else { 121 | return 0; 122 | } 123 | } 124 | 125 | int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult) { 126 | int ret = 0; 127 | uint8_t times = 0; 128 | // See if it's a numeric IP address 129 | if (inet_aton(aHostname, aResult)) { 130 | // It is, our work here is done 131 | return 1; 132 | } 133 | 134 | #if DNS_CACHE_SLOT > 0 135 | 136 | #if DNS_CACHE_EXPIRE_MS > 0 137 | if (millis() > dns_expire_timestamp) { 138 | clearDNSCache(); 139 | } 140 | #endif 141 | 142 | for (uint8_t i=0 ; i 0 179 | if (ret) { 180 | insertDNSCache(aHostname, aResult); 181 | #if DNS_CACHE_EXPIRE_MS > 0 182 | dns_expire_timestamp = millis() + DNS_CACHE_EXPIRE_MS; 183 | #endif 184 | } 185 | #endif 186 | 187 | return ret; 188 | } 189 | 190 | uint16_t DNSClient::BuildRequest(const char* aName) { 191 | // Build header 192 | // 1 1 1 1 1 1 193 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 194 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 195 | // | ID | 196 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 197 | // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | 198 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 199 | // | QDCOUNT | 200 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 201 | // | ANCOUNT | 202 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 203 | // | NSCOUNT | 204 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 205 | // | ARCOUNT | 206 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 207 | // As we only support one request at a time at present, we can simplify 208 | // some of this header 209 | iRequestId = millis(); // generate a random ID 210 | uint16_t twoByteBuffer; 211 | 212 | // FIXME We should also check that there's enough space available to write to, rather 213 | // FIXME than assume there's enough space (as the code does at present) 214 | iUdp.write((uint8_t*)&iRequestId, sizeof(iRequestId)); 215 | 216 | twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG); 217 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 218 | 219 | twoByteBuffer = htons(1); // One question record 220 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 221 | 222 | twoByteBuffer = 0; // Zero answer records 223 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 224 | 225 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 226 | // and zero additional records 227 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 228 | 229 | // Build question 230 | const char* start =aName; 231 | const char* end =start; 232 | uint8_t len; 233 | // Run through the name being requested 234 | while (*end) { 235 | // Find out how long this section of the name is 236 | end = start; 237 | while (*end && (*end != '.') ) { 238 | end++; 239 | } 240 | 241 | if (end-start > 0) { 242 | // Write out the size of this section 243 | len = end-start; 244 | iUdp.write(&len, sizeof(len)); 245 | // And then write out the section 246 | iUdp.write((uint8_t*)start, end-start); 247 | } 248 | start = end+1; 249 | } 250 | 251 | // We've got to the end of the question name, so 252 | // terminate it with a zero-length section 253 | len = 0; 254 | iUdp.write(&len, sizeof(len)); 255 | // Finally the type and class of question 256 | twoByteBuffer = htons(TYPE_A); 257 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 258 | 259 | twoByteBuffer = htons(CLASS_IN); // Internet class of question 260 | iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); 261 | // Success! Everything buffered okay 262 | return 1; 263 | } 264 | 265 | uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress) { 266 | uint32_t startTime = millis(); 267 | // Wait for a response packet 268 | while(iUdp.parsePacket() <= 0) { 269 | if((millis() - startTime) > aTimeout) 270 | return TIMED_OUT; 271 | delay(50); 272 | } 273 | 274 | // We've had a reply! 275 | // Read the UDP header 276 | uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header 277 | // Check that it's a response from the right server and the right port 278 | if ( (iDNSServer != iUdp.remoteIP()) || 279 | (iUdp.remotePort() != DNS_PORT) ) { 280 | // It's not from who we expected 281 | return INVALID_SERVER; 282 | } 283 | 284 | // Read through the rest of the response 285 | if (iUdp.available() < DNS_HEADER_SIZE) { 286 | return TRUNCATED; 287 | } 288 | iUdp.read(header, DNS_HEADER_SIZE); 289 | 290 | uint16_t staging; // Staging used to avoid type-punning warnings 291 | memcpy(&staging, &header[2], sizeof(uint16_t)); 292 | uint16_t header_flags = htons(staging); 293 | memcpy(&staging, &header[0], sizeof(uint16_t)); 294 | // Check that it's a response to this request 295 | if ( ( iRequestId != staging ) || 296 | ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) ) { 297 | // Mark the entire packet as read 298 | iUdp.flush(); 299 | return INVALID_RESPONSE; 300 | } 301 | // Check for any errors in the response (or in our request) 302 | // although we don't do anything to get round these 303 | if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) ) { 304 | // Mark the entire packet as read 305 | iUdp.flush(); 306 | return -5; //INVALID_RESPONSE; 307 | } 308 | 309 | // And make sure we've got (at least) one answer 310 | memcpy(&staging, &header[6], sizeof(uint16_t)); 311 | uint16_t answerCount = htons(staging); 312 | if (answerCount == 0 ) { 313 | // Mark the entire packet as read 314 | iUdp.flush(); 315 | return -6; //INVALID_RESPONSE; 316 | } 317 | 318 | // Skip over any questions 319 | memcpy(&staging, &header[4], sizeof(uint16_t)); 320 | for (uint16_t i =0; i < htons(staging); i++) { 321 | // Skip over the name 322 | uint8_t len; 323 | do { 324 | iUdp.read(&len, sizeof(len)); 325 | if (len > 0) { 326 | // Don't need to actually read the data out for the string, just 327 | // advance ptr to beyond it 328 | while(len--) { 329 | iUdp.read(); // we don't care about the returned byte 330 | } 331 | } 332 | } while (len != 0); 333 | 334 | // Now jump over the type and class 335 | for (int i =0; i < 4; i++) { 336 | iUdp.read(); // we don't care about the returned byte 337 | } 338 | } 339 | 340 | // Now we're up to the bit we're interested in, the answer 341 | // There might be more than one answer (although we'll just use the first 342 | // type A answer) and some authority and additional resource records but 343 | // we're going to ignore all of them. 344 | 345 | for (uint16_t i =0; i < answerCount; i++) { 346 | // Skip the name 347 | uint8_t len; 348 | do { 349 | iUdp.read(&len, sizeof(len)); 350 | if ((len & LABEL_COMPRESSION_MASK) == 0) { 351 | // It's just a normal label 352 | if (len > 0) { 353 | // And it's got a length 354 | // Don't need to actually read the data out for the string, 355 | // just advance ptr to beyond it 356 | while(len--) { 357 | iUdp.read(); // we don't care about the returned byte 358 | } 359 | } 360 | } 361 | else { 362 | // This is a pointer to a somewhere else in the message for the 363 | // rest of the name. We don't care about the name, and RFC1035 364 | // says that a name is either a sequence of labels ended with a 365 | // 0 length octet or a pointer or a sequence of labels ending in 366 | // a pointer. Either way, when we get here we're at the end of 367 | // the name 368 | // Skip over the pointer 369 | iUdp.read(); // we don't care about the returned byte 370 | // And set len so that we drop out of the name loop 371 | len = 0; 372 | } 373 | } while (len != 0); 374 | 375 | // Check the type and class 376 | uint16_t answerType; 377 | uint16_t answerClass; 378 | iUdp.read((uint8_t*)&answerType, sizeof(answerType)); 379 | iUdp.read((uint8_t*)&answerClass, sizeof(answerClass)); 380 | 381 | // Ignore the Time-To-Live as we don't do any caching 382 | for (int i =0; i < TTL_SIZE; i++) { 383 | iUdp.read(); // we don't care about the returned byte 384 | } 385 | 386 | // And read out the length of this answer 387 | // Don't need header_flags anymore, so we can reuse it here 388 | iUdp.read((uint8_t*)&header_flags, sizeof(header_flags)); 389 | 390 | if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) ) { 391 | if (htons(header_flags) != 4) { 392 | // It's a weird size 393 | // Mark the entire packet as read 394 | iUdp.flush(); 395 | return -9;//INVALID_RESPONSE; 396 | } 397 | iUdp.read(aAddress.raw_address(), 4); 398 | return SUCCESS; 399 | } 400 | else { 401 | // This isn't an answer type we're after, move onto the next one 402 | for (uint16_t i =0; i < htons(header_flags); i++) { 403 | iUdp.read(); // we don't care about the returned byte 404 | } 405 | } 406 | } 407 | 408 | // Mark the entire packet as read 409 | iUdp.flush(); 410 | 411 | // If we get here then we haven't found an answer 412 | return -10;//INVALID_RESPONSE; 413 | } 414 | 415 | #if DNS_CACHE_SLOT > 0 416 | void DNSClient::insertDNSCache(char* domain, IPAddress ip) { 417 | if (strlen(domain) < DNS_CACHE_SIZE) { 418 | strcpy(dnscache[current_dns_slot].domain, domain); 419 | dnscache[current_dns_slot].ip = ip; 420 | current_dns_slot = (current_dns_slot+1) % DNS_CACHE_SLOT; 421 | } 422 | } 423 | 424 | void DNSClient::clearDNSCache() { 425 | for (uint8_t i; i 0 18 | struct DNS_CACHE_STRUCT { 19 | char domain[DNS_CACHE_SIZE]; 20 | IPAddress ip; 21 | }; 22 | typedef struct DNS_CACHE_STRUCT dns_cache_struct; 23 | #endif 24 | 25 | class DNSClient { 26 | public: 27 | DNSClient(); 28 | void begin (); 29 | void begin(const IPAddress& aDNSServer); 30 | 31 | /** Convert a numeric IP address string into a four-byte IP address. 32 | @param aIPAddrString IP address to convert 33 | @param aResult IPAddress structure to store the returned IP address 34 | @result 1 if aIPAddrString was successfully converted to an IP address, 35 | else error code 36 | */ 37 | int inet_aton(const char *aIPAddrString, IPAddress& aResult); 38 | 39 | /** Resolve the given hostname to an IP address. 40 | @param aHostname Name to be resolved 41 | @param aResult IPAddress structure to store the returned IP address 42 | @result 1 if aIPAddrString was successfully converted to an IP address, 43 | else error code 44 | */ 45 | int getHostByName(const char* aHostname, IPAddress& aResult); 46 | 47 | protected: 48 | uint16_t BuildRequest(const char* aName); 49 | uint16_t ProcessResponse(uint16_t aTimeout, IPAddress& aAddress); 50 | 51 | #if DNS_CACHE_SLOT > 0 52 | void insertDNSCache(char* domain, IPAddress ip); 53 | void clearDNSCache(); 54 | #endif 55 | 56 | IPAddress iDNSServer; 57 | uint16_t iRequestId; 58 | BC95UDP iUdp; 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /Examples/CoAPClient/CoAPClient.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "BC95Udp.h" 4 | #include "CoAP.h" 5 | 6 | AltSoftSerial bc95serial; 7 | 8 | BC95UDP udp; 9 | Coap coap(udp); 10 | 11 | void responseHandler(CoapPacket *packet, IPAddress remoteIP, int remotePort) { 12 | char buff[6]; 13 | 14 | Serial.print("CoAP Response Code: "); 15 | sprintf(buff, "%d.%02d\n", packet->code >> 5, packet->code & 0b00011111); 16 | Serial.print(buff); 17 | 18 | for (int i=0; i< packet->payloadlen; i++) { 19 | Serial.print((char) (packet->payload[i])); 20 | } 21 | } 22 | 23 | void setup() { 24 | bc95serial.begin(9600); 25 | BC95.begin(bc95serial); 26 | BC95.reset(); 27 | 28 | Serial.begin(9600); 29 | Serial.println(F("Starting...")); 30 | 31 | while (!BC95.attachNetwork()) { 32 | Serial.println("..."); 33 | delay(1000); 34 | } 35 | Serial.println(F("NB-IOT attached..")); 36 | 37 | coap.response(responseHandler); 38 | coap.start(); 39 | coap.get("coap.me", 5683, "/hello"); 40 | } 41 | 42 | void loop() { 43 | coap.loop(); 44 | } 45 | -------------------------------------------------------------------------------- /Examples/DNSResolve/DNSResolve.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "Dns.h" 4 | 5 | AltSoftSerial bc95serial; 6 | 7 | DNSClient dns; 8 | IPAddress remoteip; 9 | 10 | void setup() { 11 | bc95serial.begin(9600); 12 | BC95.begin(bc95serial); 13 | BC95.reset(); 14 | 15 | Serial.begin(9600); 16 | Serial.println(F("Starting...")); 17 | 18 | while (!BC95.attachNetwork()) { 19 | Serial.println("..."); 20 | delay(1000); 21 | } 22 | Serial.println(F("NB-IOT attached..")); 23 | 24 | dns.begin(); 25 | dns.getHostByName("google.com", remoteip); 26 | 27 | Serial.print("The resolved IP address is : "); 28 | Serial.println(remoteip); 29 | } 30 | 31 | void loop() { 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Examples/NTPClient/NTPClient.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "NTPClient.h" 4 | 5 | AltSoftSerial bc95serial; 6 | 7 | BC95UDP udp; 8 | NTPClient ntpclient(udp); 9 | 10 | void setup() { 11 | bc95serial.begin(9600); 12 | BC95.begin(bc95serial); 13 | BC95.reset(); 14 | 15 | Serial.begin(9600); 16 | Serial.println(F("Starting...")); 17 | 18 | while (!BC95.attachNetwork()) { 19 | Serial.println("..."); 20 | delay(1000); 21 | } 22 | Serial.println(F("NB-IOT attached..")); 23 | 24 | ntpclient.begin(); 25 | ntpclient.update(); 26 | Serial.print("The current GMT time is : "); 27 | Serial.println(ntpclient.getFormattedTime()); 28 | } 29 | 30 | void loop() { 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Examples/UDPClient/UDPClient.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "BC95Udp.h" 4 | 5 | AltSoftSerial bc95serial; 6 | 7 | // 8.8.8.8 is the Google's public DNS server. 8 | #define SERVER_IP IPAddress(8, 8, 8, 8) 9 | #define SERVER_PORT 53 10 | 11 | // This binary string represents a UDP payload of the DNS query for the domain name nexpie.com 12 | uint8_t udpdata[] = "\xC0\x5B\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06\x6E\x65\x78\x70\x69\x65\x03\x63\x6F\x6D\x00\x00\x01\x00\x01"; 13 | 14 | 15 | BC95UDP udpclient; 16 | uint8_t buff[64]; 17 | 18 | void printHEX(uint8_t *buff, size_t len) { 19 | for (int i=0; i_udp = &udp; 26 | } 27 | 28 | NTPClient::NTPClient(UDP& udp, long timeOffset) { 29 | this->_udp = &udp; 30 | this->_timeOffset = timeOffset; 31 | } 32 | 33 | NTPClient::NTPClient(UDP& udp, const char* poolServerName) { 34 | this->_udp = &udp; 35 | this->_poolServerName = poolServerName; 36 | } 37 | 38 | NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) { 39 | this->_udp = &udp; 40 | this->_timeOffset = timeOffset; 41 | this->_poolServerName = poolServerName; 42 | } 43 | 44 | NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) { 45 | this->_udp = &udp; 46 | this->_timeOffset = timeOffset; 47 | this->_poolServerName = poolServerName; 48 | this->_updateInterval = updateInterval; 49 | } 50 | 51 | void NTPClient::begin() { 52 | this->begin(NTP_DEFAULT_LOCAL_PORT); 53 | } 54 | 55 | void NTPClient::begin(int port) { 56 | this->_port = port; 57 | 58 | this->_udp->begin(this->_port); 59 | 60 | this->_udpSetup = true; 61 | } 62 | 63 | bool NTPClient::forceUpdate() { 64 | #ifdef DEBUG_NTPClient 65 | Serial.println("Update from NTP Server"); 66 | #endif 67 | 68 | this->sendNTPPacket(); 69 | 70 | // Wait till data is there or timeout... 71 | byte timeout = 0; 72 | int cb = 0; 73 | do { 74 | delay ( 10 ); 75 | cb = this->_udp->parsePacket(); 76 | if (timeout > 100) return false; // timeout after 1000 ms 77 | timeout++; 78 | } while (cb == 0); 79 | 80 | this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time 81 | 82 | this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE); 83 | 84 | unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); 85 | unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); 86 | // combine the four bytes (two words) into a long integer 87 | // this is NTP time (seconds since Jan 1 1900): 88 | unsigned long secsSince1900 = highWord << 16 | lowWord; 89 | 90 | this->_currentEpoc = secsSince1900 - SEVENZYYEARS; 91 | 92 | return true; 93 | } 94 | 95 | bool NTPClient::update() { 96 | if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval 97 | || this->_lastUpdate == 0) { // Update if there was no update yet. 98 | if (!this->_udpSetup) this->begin(); // setup the UDP client if needed 99 | return this->forceUpdate(); 100 | } 101 | return true; 102 | } 103 | 104 | unsigned long NTPClient::getEpochTime() const { 105 | return this->_timeOffset + // User offset 106 | this->_currentEpoc + // Epoc returned by the NTP server 107 | ((millis() - this->_lastUpdate) / 1000); // Time since last update 108 | } 109 | 110 | int NTPClient::getDay() const { 111 | return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday 112 | } 113 | int NTPClient::getHours() const { 114 | return ((this->getEpochTime() % 86400L) / 3600); 115 | } 116 | int NTPClient::getMinutes() const { 117 | return ((this->getEpochTime() % 3600) / 60); 118 | } 119 | int NTPClient::getSeconds() const { 120 | return (this->getEpochTime() % 60); 121 | } 122 | 123 | String NTPClient::getFormattedTime() const { 124 | unsigned long rawTime = this->getEpochTime(); 125 | unsigned long hours = (rawTime % 86400L) / 3600; 126 | String hoursStr = hours < 10 ? "0" + String(hours) : String(hours); 127 | 128 | unsigned long minutes = (rawTime % 3600) / 60; 129 | String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes); 130 | 131 | unsigned long seconds = rawTime % 60; 132 | String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds); 133 | 134 | return hoursStr + ":" + minuteStr + ":" + secondStr; 135 | } 136 | 137 | void NTPClient::end() { 138 | this->_udp->stop(); 139 | 140 | this->_udpSetup = false; 141 | } 142 | 143 | void NTPClient::setTimeOffset(int timeOffset) { 144 | this->_timeOffset = timeOffset; 145 | } 146 | 147 | void NTPClient::setUpdateInterval(unsigned long updateInterval) { 148 | this->_updateInterval = updateInterval; 149 | } 150 | 151 | void NTPClient::setPoolServerName(IPAddress poolServerName) { 152 | this->_poolServerName = poolServerName; 153 | } 154 | 155 | void NTPClient::sendNTPPacket() { 156 | // set all bytes in the buffer to 0 157 | memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); 158 | // Initialize values needed to form NTP request 159 | // (see URL above for details on the packets) 160 | this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode 161 | this->_packetBuffer[1] = 0; // Stratum, or type of clock 162 | this->_packetBuffer[2] = 6; // Polling Interval 163 | this->_packetBuffer[3] = 0xEC; // Peer Clock Precision 164 | // 8 bytes of zero for Root Delay & Root Dispersion 165 | this->_packetBuffer[12] = 49; 166 | this->_packetBuffer[13] = 0x4E; 167 | this->_packetBuffer[14] = 49; 168 | this->_packetBuffer[15] = 52; 169 | 170 | // all NTP fields have been given values, now 171 | // you can send a packet requesting a timestamp: 172 | 173 | this->_udp->beginPacket(this->_poolServerName, 123); //NTP requests are to port 123 174 | this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE); 175 | this->_udp->endPacket(); 176 | } 177 | -------------------------------------------------------------------------------- /NTPClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "BC95Udp.h" 5 | 6 | #define SEVENZYYEARS 2208988800UL 7 | #define NTP_PACKET_SIZE 48 8 | #define NTP_DEFAULT_LOCAL_PORT 1337 9 | 10 | class NTPClient { 11 | private: 12 | BC95UDP* _udp; 13 | bool _udpSetup = false; 14 | 15 | const char* _poolServerName = NTP_DEFAULT_SERVER; 16 | int _port = NTP_DEFAULT_LOCAL_PORT; 17 | long _timeOffset = 0; 18 | 19 | unsigned long _updateInterval = 60000; // In ms 20 | 21 | unsigned long _currentEpoc = 0; // In s 22 | unsigned long _lastUpdate = 0; // In ms 23 | 24 | byte _packetBuffer[NTP_PACKET_SIZE]; 25 | 26 | void sendNTPPacket(); 27 | 28 | public: 29 | NTPClient(UDP& udp); 30 | NTPClient(UDP& udp, long timeOffset); 31 | NTPClient(UDP& udp, const char* poolServerName); 32 | NTPClient(UDP& udp, const char* poolServerName, long timeOffset); 33 | NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval); 34 | 35 | /** 36 | * Set time server name 37 | * 38 | * @param poolServerName 39 | */ 40 | void setPoolServerName(IPAddress poolServerName); 41 | 42 | /** 43 | * Starts the underlying UDP client with the default local port 44 | */ 45 | void begin(); 46 | 47 | /** 48 | * Starts the underlying UDP client with the specified local port 49 | */ 50 | void begin(int port); 51 | 52 | /** 53 | * This should be called in the main loop of your application. By default an update from the NTP Server is only 54 | * made every 60 seconds. This can be configured in the NTPClient constructor. 55 | * 56 | * @return true on success, false on failure 57 | */ 58 | bool update(); 59 | 60 | /** 61 | * This will force the update from the NTP Server. 62 | * 63 | * @return true on success, false on failure 64 | */ 65 | bool forceUpdate(); 66 | 67 | int getDay() const; 68 | int getHours() const; 69 | int getMinutes() const; 70 | int getSeconds() const; 71 | 72 | /** 73 | * Changes the time offset. Useful for changing timezones dynamically 74 | */ 75 | void setTimeOffset(int timeOffset); 76 | 77 | /** 78 | * Set the update interval to another frequency. E.g. useful when the 79 | * timeOffset should not be set in the constructor 80 | */ 81 | void setUpdateInterval(unsigned long updateInterval); 82 | 83 | /** 84 | * @return time formatted like `hh:mm:ss` 85 | */ 86 | String getFormattedTime() const; 87 | 88 | /** 89 | * @return time in seconds since Jan. 1, 1970 90 | */ 91 | unsigned long getEpochTime() const; 92 | 93 | /** 94 | * Stops the underlying UDP client 95 | */ 96 | void end(); 97 | }; 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bc95-arduino 2 | 3 | UDP client library ที่พัฒนาขึ้นเพื่ออำนวยความสะดวกในการใช้งาน BC95 NB-IOT module ทำให้ไม่จำเป็นต้องเขียนโปรแกรมติดต่อ AT command เอง 4 | ในขณะเดียวกัน API สำหรับใช้งานถูกสร้างขึ้นตามแบบอย่างของ Arduino UDP class ทำให้สามารถนำไปใช้เป็น UDP stack ของ protocol library อื่นๆของ Arduino ได้เลย เช่น DNS, NTP และ CoAP 5 | 6 | ## ความเข้ากันได้ 7 | 8 | Arduino : UNO, MEGA2560 9 | 10 | NB-IOT shield/module : True NB-IOT shield, AIS NB-IOT shield, Quectel BC95-B8 11 | 12 | **คำแนะนำ** 13 | สามารถปรับแต่งค่า config ต่างๆในไฟล์ settings.h ได้ เช่น ขนาด buffer, เลือกให้ใช้ buffer จากภายนอก หรือให้ library ทำการ allocate memory ขึ้นมาเอง, การตั้งค่า time server และค่าเกี่ยวกับ DNS เป็นต้น 14 | 15 | ``` 16 | #define DATA_BUFFER_SIZE 128 17 | 18 | #define BC95_USE_EXTERNAL_BUFFER 1 19 | #define BC95_PRINT_DEBUG 0 20 | #define BC95_DEFAULT_SERIAL_TIMEOUT 500 21 | #define BC95_BUFFER_SIZE DATA_BUFFER_SIZE 22 | 23 | #define BC95UDP_USE_EXTERNAL_BUFFER 0 24 | #define BC95UDP_SHARE_GLOBAL_BUFFER 1 25 | #define BC95UDP_SERIAL_READ_CHUNK_SIZE 7 26 | #define BC95UDP_BUFFER_SIZE DATA_BUFFER_SIZE 27 | 28 | #define DNS_CACHE_SLOT 1 29 | #define DNS_CACHE_SIZE 24 30 | #define DNS_CACHE_EXPIRE_MS 0 31 | #define DNS_MAX_RETRY 5 32 | #define DNS_DEFAULT_SERVER IPAddress(8,8,8,8) 33 | 34 | #define NTP_DEFAULT_SERVER "time.nist.gov" 35 | 36 | #define COAP_ENABLE_ACK_CALLBACK 1 37 | ``` 38 | 39 | ## ตัวอย่างโค้ดบน Arduino UNO 40 | 41 | ตัวอย่างการนำ BC95UDP ไปใช้เป็น UDP stack ร่วมกับ NTP protocol ในการ sync เวลากับ time server ผ่าน NB-IOT 42 | 43 | ```C++ 44 | #include 45 | #include 46 | #include "NTPClient.h" 47 | 48 | AltSoftSerial bc95serial; 49 | 50 | BC95UDP udp; 51 | NTPClient ntpclient(udp); 52 | 53 | void setup() { 54 | bc95serial.begin(9600); 55 | BC95.begin(bc95serial); 56 | BC95.reset(); 57 | 58 | Serial.begin(9600); 59 | Serial.println(F("Starting...")); 60 | 61 | while (!BC95.attachNetwork()) { 62 | Serial.print("."); 63 | delay(1000); 64 | } 65 | Serial.println(F("\nNB-IOT attached..")); 66 | 67 | ntpclient.begin(); 68 | ntpclient.update(); 69 | Serial.print("The current GMT time is : "); 70 | Serial.println(ntpclient.getFormattedTime()); 71 | } 72 | 73 | void loop() { 74 | 75 | } 76 | 77 | ``` 78 | 79 | เรายังสามาถใช้รับส่ง UDP packet ในรูปแบบเดียวกับ UDP class มาตรฐานของ Arduino โค้ดข้างล่างเป็นตัวอย่างการส่ง UDP packet ของ DNS query ไป resolve IP address กับ domain name server ของ Google ซึ่งมี IP เป็น 8.8.8.8 80 | 81 | ```C++ 82 | #include 83 | #include 84 | #include "BC95Udp.h" 85 | 86 | AltSoftSerial bc95serial; 87 | 88 | #define SERVER_IP IPAddress(8, 8, 8, 8) 89 | #define SERVER_PORT 53 90 | 91 | // This binary string represents a UDP paylaod of the DNS query for the domain name nexpie.com 92 | uint8_t udpdata[] = "\xC0\x5B\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06\x6E\x65\x78\x70\x69\x65\x03\x63\x6F\x6D\x00\x00\x01\x00\x01"; 93 | 94 | BC95UDP udpclient; 95 | uint8_t buff[64]; 96 | 97 | void printHEX(uint8_t *buff, size_t len) { 98 | for (int i=0; i 148 | #include 149 | #include "Dns.h" 150 | 151 | AltSoftSerial bc95serial; 152 | 153 | DNSClient dns; 154 | IPAddress remoteip; 155 | 156 | void setup() { 157 | bc95serial.begin(9600); 158 | BC95.begin(bc95serial); 159 | BC95.reset(); 160 | 161 | Serial.begin(9600); 162 | Serial.println(F("Starting...")); 163 | 164 | while (!BC95.attachNetwork()) { 165 | Serial.print("."); 166 | delay(1000); 167 | } 168 | Serial.println(F("\nNB-IOT attached..")); 169 | 170 | dns.begin(); 171 | dns.getHostByName("google.com", remoteip); 172 | 173 | Serial.print("The resolved IP address is : "); 174 | Serial.println(remoteip); 175 | } 176 | 177 | void loop() { 178 | 179 | } 180 | 181 | ``` 182 | 183 | นอกจากนี้ เรายังสามารถใช้ BC95UDP ในการรับส่ง CoAP protocol กับ CoAP server ตามตัวอย่างนี้ responseHandler() เป็น callback function ที่เราต้อง register ไว้เพื่อรอรับเวลามี CoAP response เข้ามาจาก server โค้ดตัวอย่างเป็นการเรียก CoAP GET /hello ไปที่ coap.me ที่ port 5683 ซึ่ง server จะตอบคำว่า world กลับมา และมี response code เป็น 2.05 184 | 185 | ```C++ 186 | #include 187 | #include 188 | #include "BC95Udp.h" 189 | #include "CoAP.h" 190 | 191 | AltSoftSerial bc95serial; 192 | 193 | BC95UDP udp; 194 | Coap coap(udp); 195 | 196 | void responseHandler(CoapPacket *packet, IPAddress remoteIP, int remotePort) { 197 | char buff[6]; 198 | 199 | Serial.print("CoAP Response Code: "); 200 | sprintf(buff, "%d.%02d \n", packet->code >> 5, packet->code & 0b00011111); 201 | Serial.print(buff); 202 | 203 | for (int i=0; i< packet->payloadlen; i++) { 204 | Serial.print((char) (packet->payload[i])); 205 | } 206 | } 207 | 208 | void setup() { 209 | bc95serial.begin(9600); 210 | BC95.begin(bc95serial); 211 | BC95.reset(); 212 | 213 | Serial.begin(9600); 214 | Serial.println(F("Starting...")); 215 | 216 | while (!BC95.attachNetwork()) { 217 | Serial.println("..."); 218 | delay(1000); 219 | } 220 | Serial.println(F("NB-IOT attached..")); 221 | 222 | coap.response(responseHandler); 223 | coap.start(); 224 | coap.get("coap.me", 5683, "/hello"); 225 | } 226 | 227 | void loop() { 228 | coap.loop(); 229 | } 230 | ``` 231 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=BC95 for Arduino 2 | version=0.8.0 3 | author=Chavee Issariyapat 4 | maintainer=Chavee Issariyapat 5 | sentence=A network library for BC95 NB-IOT module on Arduino platform. 6 | paragraph=This library transforms AT-commands on BC95 NB-IOT module into a familiar arduino UDP functions. Application protocols based on UDP like CoAP, DNS and NTP are also included. 7 | category=Communication 8 | url=https://github.com/nexpie/bc95-arduino 9 | architectures=avr 10 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | BC95 library for Arduino. 3 | Author: Chavee Issariyapat 4 | E-mail: chavee@nexpie.com 5 | 6 | This software is released under the MIT License. 7 | */ 8 | 9 | #ifndef SETTINGS_h 10 | #define SETTINGS_h 11 | 12 | #define DATA_BUFFER_SIZE 120 13 | 14 | #define BC95_USE_EXTERNAL_BUFFER 1 15 | #define BC95_PRINT_DEBUG 0 16 | #define BC95_DEFAULT_SERIAL_TIMEOUT 500 17 | #define BC95_BUFFER_SIZE DATA_BUFFER_SIZE 18 | 19 | #define BC95UDP_USE_EXTERNAL_BUFFER 0 20 | #define BC95UDP_SHARE_GLOBAL_BUFFER 1 21 | #define BC95UDP_SERIAL_READ_CHUNK_SIZE 7 22 | #define BC95UDP_BUFFER_SIZE DATA_BUFFER_SIZE 23 | 24 | #define DNS_CACHE_SLOT 1 25 | #define DNS_CACHE_SIZE 24 26 | #define DNS_CACHE_EXPIRE_MS 0 27 | #define DNS_MAX_RETRY 5 28 | #define DNS_DEFAULT_SERVER IPAddress(8,8,8,8) 29 | 30 | #define NTP_DEFAULT_SERVER "time.nist.gov" 31 | 32 | #define COAP_ENABLE_ACK_CALLBACK 1 33 | #define COAP_CONFIRMABLE 0 34 | 35 | #endif 36 | --------------------------------------------------------------------------------