├── ESP_ATMod ├── data │ └── .gitkeep ├── LwipIntfDevPatch.h ├── asnDecode.h ├── WifiEvents.h ├── WifiEvents.cpp ├── debug.h ├── command.h ├── settings.h ├── ESP_ATMod.h ├── settings.cpp ├── asnDecode.cpp ├── ESP_ATMod.ino └── command.cpp ├── .gitignore ├── platformio.ini ├── LICENSE └── README.md /ESP_ATMod/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore all certificate files in the data folder 2 | ESP_ATMod/data/* -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | src_dir=ESP_ATMod 13 | 14 | [env:esp01_1m] 15 | platform = espressif8266 16 | board = esp01_1m 17 | framework = arduino 18 | 19 | board_build.flash_mode = dout 20 | 21 | -------------------------------------------------------------------------------- /ESP_ATMod/LwipIntfDevPatch.h: -------------------------------------------------------------------------------- 1 | #ifndef _LWIPINTFDEVPACTH_H 2 | #define _LWIPINTFDEVPATCH_H 3 | 4 | /* 5 | * This patch is required for esp8266 Arduino version 3.1.2 end older. 6 | * When a version following 3.1.2 is released, the patch can be removed. 7 | */ 8 | 9 | 10 | #include 11 | 12 | template 13 | class LwipIntfDevPatch: public LwipIntfDev { 14 | public: 15 | 16 | LwipIntfDevPatch(int8_t cs = SS, SPIClass &spi = SPI, int8_t intr = -1) : 17 | LwipIntfDev(cs, spi, intr) 18 | { 19 | 20 | } 21 | 22 | void end() 23 | { 24 | if (LwipIntfDev::_started) 25 | { 26 | netif_remove(&(LwipIntfDev::_netif)); 27 | LwipIntfDev::_started = false; 28 | RawDev::end(); 29 | } 30 | } 31 | 32 | uint8_t* macAddress(uint8_t* mac) 33 | { 34 | memcpy(mac, LwipIntfDev::_netif.hwaddr, 6); 35 | return mac; 36 | } 37 | }; 38 | 39 | 40 | #endif -------------------------------------------------------------------------------- /ESP_ATMod/asnDecode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * asnDecode.h 3 | * 4 | * Part of ESP_ATMod: modified AT command processor for ESP8266 5 | * 6 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library; if not, see . 20 | */ 21 | 22 | #ifndef ASNDECODE_H_ 23 | #define ASNDECODE_H_ 24 | 25 | #include "Arduino.h" 26 | 27 | /* 28 | * Public functions 29 | */ 30 | 31 | uint8_t *getCnFromDer(uint8_t *der, uint16_t length); 32 | 33 | #endif /* ASNDECODE_H_ */ 34 | -------------------------------------------------------------------------------- /ESP_ATMod/WifiEvents.h: -------------------------------------------------------------------------------- 1 | /* 2 | * WifiEvents.h 3 | * 4 | * Part of modified AT command processor for ESP8266 5 | * 6 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library; if not, see . 20 | */ 21 | 22 | #ifndef WIFIEVENTS_H_ 23 | #define WIFIEVENTS_H_ 24 | 25 | #include "ESP8266WiFi.h" 26 | 27 | /* 28 | * Functions 29 | */ 30 | 31 | void onStationConnected(const WiFiEventStationModeConnected &evt); 32 | void onStationGotIP(const WiFiEventStationModeGotIP &evt); 33 | void onStationDisconnected(const WiFiEventStationModeDisconnected &evt); 34 | 35 | #endif /* WIFIEVENTS_H_ */ 36 | -------------------------------------------------------------------------------- /ESP_ATMod/WifiEvents.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * WifiEvents.c 3 | * 4 | * Part of ESP_ATMod: modified AT command processor for ESP8266 5 | * 6 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library; if not, see . 20 | */ 21 | 22 | #include "Arduino.h" 23 | #include "ESP8266WiFi.h" 24 | 25 | #include "WifiEvents.h" 26 | 27 | /* 28 | * Feedback when connected to AP 29 | */ 30 | void onStationConnected(const WiFiEventStationModeConnected &evt) 31 | { 32 | (void)evt; 33 | Serial.println(F("WIFI CONNECTED")); 34 | } 35 | 36 | /* 37 | * Feedback when got IP address 38 | */ 39 | void onStationGotIP(const WiFiEventStationModeGotIP &evt) 40 | { 41 | (void)evt; 42 | Serial.println(F("WIFI GOT IP")); 43 | } 44 | 45 | /* 46 | * Feedback when disconnected 47 | * FIXME: print only reason 8 48 | */ 49 | void onStationDisconnected(const WiFiEventStationModeDisconnected &evt) 50 | { 51 | Serial.printf_P(PSTR("WIFI DISCONNECT (%d)\r\n"), evt.reason); 52 | } 53 | -------------------------------------------------------------------------------- /ESP_ATMod/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * error.h 3 | * 4 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library; if not, see . 18 | */ 19 | 20 | #ifndef DEBUG_H_ 21 | #define DEBUG_H_ 22 | 23 | /* 24 | * Debug flag 25 | */ 26 | //#define AT_DEBUG 27 | 28 | #if defined(AT_DEBUG) 29 | 30 | #define AT_DEBUG_PRINTF(format, args...) \ 31 | do \ 32 | { \ 33 | char buf[200]; \ 34 | sprintf(buf, format, args); \ 35 | Serial.print(buf); \ 36 | } while (0); 37 | 38 | #define AT_DEBUG_PRINT(string) Serial.print(string); 39 | 40 | #else 41 | 42 | #define AT_DEBUG_PRINTF(format, args...) \ 43 | do \ 44 | { \ 45 | } while (0); 46 | #define AT_DEBUG_PRINT(string) \ 47 | do \ 48 | { \ 49 | } while (0); 50 | 51 | #endif 52 | 53 | #endif /* DEBUG_H_ */ 54 | -------------------------------------------------------------------------------- /ESP_ATMod/command.h: -------------------------------------------------------------------------------- 1 | /* 2 | * command.h * 3 | * Part of ESP_ATMod: modified AT command processor for ESP8266 4 | * 5 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 6 | * 7 | * This library is free software; you can redistribute it and/or 8 | * modify it under the terms of the GNU Lesser General Public 9 | * License as published by the Free Software Foundation; either 10 | * version 2.1 of the License, or (at your option) any later version. 11 | * 12 | * This library is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | * Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this library; if not, see . 19 | */ 20 | 21 | #ifndef COMMAND_H_ 22 | #define COMMAND_H_ 23 | 24 | /* 25 | * Constants for implemented commands 26 | */ 27 | enum commands_t 28 | { 29 | CMD_ERROR = 0, 30 | // Basic AT Commands 31 | CMD_AT, 32 | CMD_AT_RST, 33 | CMD_AT_GMR, 34 | CMD_ATE, 35 | CMD_AT_RESTORE, 36 | CMD_AT_UART, 37 | CMD_AT_UART_CUR, 38 | CMD_AT_UART_DEF, 39 | CMD_AT_SYSRAM, 40 | // Wi-Fi AT Commands 41 | CMD_AT_CWMODE, 42 | CMD_AT_CWMODE_CUR, 43 | CMD_AT_CWMODE_DEF, 44 | CMD_AT_CWJAP, 45 | CMD_AT_CWJAP_CUR, 46 | CMD_AT_CWJAP_DEF, 47 | CMD_AT_CWLAPOPT, 48 | CMD_AT_CWLAP, 49 | CMD_AT_CWQAP, 50 | CMD_AT_CWSAP, 51 | CMD_AT_CWSAP_CUR, 52 | CMD_AT_CWSAP_DEF, 53 | CMD_AT_CWDHCP, 54 | CMD_AT_CWDHCP_CUR, 55 | CMD_AT_CWDHCP_DEF, 56 | CMD_AT_CWAUTOCONN, 57 | CMD_AT_CIPSTAMAC, 58 | CMD_AT_CIPSTAMAC_CUR, 59 | CMD_AT_CIPSTAMAC_DEF, 60 | CMD_AT_CIPAPMAC, 61 | CMD_AT_CIPAPMAC_CUR, 62 | CMD_AT_CIPAPMAC_DEF, 63 | CMD_AT_CIPSTA, 64 | CMD_AT_CIPSTA_CUR, 65 | CMD_AT_CIPSTA_DEF, 66 | CMD_AT_CIPAP, 67 | CMD_AT_CIPAP_CUR, 68 | CMD_AT_CIPAP_DEF, 69 | CMD_AT_CWHOSTNAME, 70 | CMD_AT_CIPETHMAC, 71 | CMD_AT_CIPETHMAC_CUR, 72 | CMD_AT_CIPETHMAC_DEF, 73 | CMD_AT_CIPETH, 74 | CMD_AT_CIPETH_CUR, 75 | CMD_AT_CIPETH_DEF, 76 | CMD_AT_CEHOSTNAME, 77 | // TCP/IP AT Commands 78 | CMD_AT_CIPSTATUS, 79 | CMD_AT_CIPDOMAIN, 80 | CMD_AT_CIPSTART, 81 | CMD_AT_CIPSSLSIZE, 82 | CMD_AT_CIPSEND, 83 | CMD_AT_CIPCLOSEMODE, 84 | CMD_AT_CIPCLOSE, 85 | CMD_AT_CIFSR, 86 | CMD_AT_CIPMUX, 87 | CMD_AT_CIPSERVER, 88 | CMD_AT_CIPSERVERMAXCONN, 89 | CMD_AT_CIPSTO, 90 | CMD_AT_CIPDINFO, 91 | CMD_AT_CIPRECVMODE, // v 1.7 92 | CMD_AT_CIPRECVDATA, // v 1.7 93 | CMD_AT_CIPRECVLEN, // v 1.7 94 | CMD_AT_CIPSNTPCFG, 95 | CMD_AT_CIPSNTPTIME, 96 | CMD_AT_CIPDNS, 97 | CMD_AT_CIPDNS_CUR, 98 | CMD_AT_CIPDNS_DEF, 99 | // New commands 100 | CMD_AT_SYSCPUFREQ, // New command 101 | CMD_AT_RFMODE, // New command 102 | CMD_AT_CIPSSLAUTH, // New command 103 | CMD_AT_CIPSSLFP, // New command 104 | CMD_AT_CIPSSLCERTMAX, // New command 105 | CMD_AT_CIPSSLCERT, // New command 106 | CMD_AT_CIPSSLMFLN, // New command 107 | CMD_AT_CIPSSLSTA, // New command 108 | CMD_AT_SNTPTIME, // New command 109 | }; 110 | 111 | /* 112 | * Public functions 113 | */ 114 | 115 | void processCommandBuffer(); 116 | 117 | #endif /* COMMAND_H_ */ 118 | -------------------------------------------------------------------------------- /ESP_ATMod/settings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * settings.h 3 | * 4 | * Part of ESP_ATMod: modified AT command processor for ESP8266 5 | * 6 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library; if not, see . 20 | */ 21 | 22 | #ifndef SETTINGS_H_ 23 | #define SETTINGS_H_ 24 | 25 | #include "Arduino.h" 26 | 27 | #include "ESP_ATMod.h" 28 | 29 | /* 30 | * Defines 31 | */ 32 | 33 | #define EEPROM_DATA_SIZE 64 34 | 35 | /* 36 | * Types 37 | */ 38 | 39 | typedef struct 40 | { 41 | uint32_t uartBaudRate; 42 | uint16_t uartConfig; 43 | uint8_t dhcpMode; 44 | ipConfig_t netConfig; 45 | dnsConfig_t dnsConfig; 46 | ipConfig_t apIpConfig; 47 | ipConfig_t ethIpConfig; 48 | int maximumCertificates; 49 | 50 | uint32_t crc32; 51 | } eepromData_t; 52 | 53 | /* 54 | * Class for settings 55 | */ 56 | 57 | class Settings 58 | { 59 | public: 60 | static uint32_t getUartBaudRate(); 61 | static SerialConfig getUartConfig(); 62 | static uint8_t getDhcpMode(); 63 | static ipConfig_t getNetConfig(); 64 | static ipConfig_t getApIpConfig(); 65 | static ipConfig_t getEthIpConfig(); 66 | static dnsConfig_t getDnsConfig(); 67 | static int getMaximumCertificates(); 68 | 69 | static void setUartBaudRate(uint32_t baudRate); 70 | static void setUartConfig(SerialConfig config); 71 | static void setDhcpMode(uint8_t mode); 72 | static void setNetConfig(ipConfig_t netCfg); 73 | static void setApIpConfig(ipConfig_t apIpCfg); 74 | static void setEthIpConfig(ipConfig_t ethIpCfg); 75 | static void setDnsConfig(dnsConfig_t dnsCfg); 76 | static void setMaximumCertificates(int maximumCertificates); 77 | 78 | static void reset(); 79 | 80 | protected: 81 | static void resetData(eepromData_t *dataPtr); 82 | 83 | protected: 84 | class EEPROMData 85 | { 86 | private: 87 | eepromData_t data; 88 | 89 | public: 90 | EEPROMData(); 91 | ~EEPROMData(); 92 | 93 | uint32_t getUartBaudRate() { return data.uartBaudRate; } 94 | SerialConfig getUartConfig() { return (SerialConfig)(data.uartConfig); } 95 | uint8_t getDhcpMode() { return data.dhcpMode; } 96 | ipConfig_t getNetConfig() { return data.netConfig; } 97 | ipConfig_t getApIpConfig() { return data.apIpConfig; } 98 | ipConfig_t getEthIpConfig() { return data.ethIpConfig; } 99 | dnsConfig_t getDnsConfig() { return data.dnsConfig; } 100 | int getMaximumCertificates() { return data.maximumCertificates; } 101 | 102 | void setUartBaudRate(uint32_t baudRate) { data.uartBaudRate = baudRate; } 103 | void setUartConfig(SerialConfig config) { data.uartConfig = config; } 104 | void setDhcpMode(uint8_t mode) { data.dhcpMode = mode; } 105 | void setNetConfig(ipConfig_t netCfg) { data.netConfig = netCfg; } 106 | void setApIpConfig(ipConfig_t apIpCfg) { data.apIpConfig = apIpCfg; } 107 | void setEthIpConfig(ipConfig_t ethIpCfg) { data.ethIpConfig = ethIpCfg; } 108 | void setDnsConfig(dnsConfig_t dnsCfg) { data.dnsConfig = dnsCfg; } 109 | void setMaximumCertificates(int maximumCertificates) { data.maximumCertificates = maximumCertificates; } 110 | 111 | eepromData_t *getDataPtr() { return &data; } 112 | }; 113 | }; 114 | 115 | #endif /* SETTINGS_H_ */ 116 | -------------------------------------------------------------------------------- /ESP_ATMod/ESP_ATMod.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ESP_ATMod.h 3 | * 4 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this library; if not, see . 18 | */ 19 | 20 | #ifndef ESP_ATMOD_H_ 21 | #define ESP_ATMOD_H_ 22 | 23 | #include 24 | 25 | //#define ETHERNET_CLASS LwipIntfDevPatch 26 | #define ETHERNET_CS 5 27 | 28 | #ifdef ETHERNET_CLASS 29 | #include "LwipIntfDevPatch.h" 30 | #include 31 | extern ETHERNET_CLASS Ethernet; 32 | #endif 33 | 34 | /* 35 | * Types 36 | */ 37 | 38 | enum clientTypes_t 39 | { 40 | TYPE_TCP = 0, 41 | TYPE_UDP, 42 | TYPE_SSL, 43 | TYPE_NONE = 99 44 | }; 45 | 46 | typedef struct 47 | { 48 | WiFiClient *client; 49 | clientTypes_t type; 50 | uint16_t sendLength; 51 | uint16_t lastAvailableBytes; 52 | uint32_t lastActivityMillis; 53 | } client_t; 54 | 55 | typedef struct 56 | { 57 | uint32_t ip; 58 | uint32_t gw; 59 | uint32_t mask; 60 | } ipConfig_t; 61 | 62 | typedef struct 63 | { 64 | uint32_t dns1; 65 | uint32_t dns2; 66 | } dnsConfig_t; 67 | 68 | const uint8_t CWDHCP_AP = 1; 69 | const uint8_t CWDHCP_STA = 2; 70 | const uint8_t CWDHCP_ETH = 4; 71 | /* 72 | * Globals 73 | */ 74 | 75 | extern client_t clients[5]; 76 | 77 | extern const uint8_t SERVERS_COUNT; 78 | extern WiFiServer servers[]; 79 | 80 | const uint16_t INPUT_BUFFER_LEN = 200; 81 | 82 | extern uint8_t inputBuffer[INPUT_BUFFER_LEN]; // Input buffer 83 | extern uint16_t inputBufferCnt; // Number of bytes in inputBuffer 84 | extern uint8_t fingerprint[20]; // SHA-1 certificate fingerprint for TLS connections 85 | extern bool fingerprintValid; 86 | extern BearSSL::X509List *CAcert; // CA certificate for TLS validation 87 | extern size_t maximumCertificates; // Maximum amount of certificates to load 88 | 89 | extern char *PemCertificate; // Buffer for loading a certificate 90 | extern uint16_t PemCertificatePos; // Position in buffer while loading 91 | extern uint16_t PemCertificateCount; // Number of chars read 92 | 93 | extern uint16_t dataRead; // Number of bytes read from the input to a send buffer 94 | 95 | /* 96 | * Global settings 97 | */ 98 | 99 | extern bool gsEchoEnabled; // command ATE 100 | extern uint8_t gsCipMux; // command AT+CIPMUX 101 | extern uint8_t gsCipdInfo; // command AT+CIPDINFO 102 | extern uint8_t gsCwDhcp; // command AT+CWDHCP 103 | extern bool gsFlag_Connecting; // Connecting in progress 104 | extern bool gsFlag_Busy; // Command is busy other commands ignored 105 | extern int8_t gsLinkIdReading; // Link id for which are the data read 106 | extern bool gsCertLoading; // AT+CIPSSLCERT in progress 107 | extern bool gsWasConnected; // Connection flag for AT+CIPSTATUS 108 | extern bool gsEthConnected; // track eth state for +ETH_ messages 109 | extern uint8_t gsCipSslAuth; // command AT+CIPSSLAUTH: 0 = none, 1 = fingerprint, 2 = certificate chain 110 | extern uint8_t gsCipRecvMode; // command AT+CIPRECVMODE 111 | extern ipConfig_t gsCipStaCfg; // command AT+CIPSTA_CUR 112 | extern dnsConfig_t gsCipDnsCfg; // command AT+CIPDNS 113 | extern ipConfig_t gsCipApCfg; // command AT+CIPAP_CUR 114 | extern ipConfig_t gsCipEthCfg; // command AT+CIPETH 115 | extern uint8_t gsCipEthMAC[6]; // command AT+CIPETHMAC 116 | extern uint16_t gsCipSslSize; // command AT+CIPSSLSIZE 117 | extern bool gsSTNPEnabled; // command AT+CIPSNTPCFG 118 | extern int8_t gsSTNPTimezone; // command AT+CIPSNTPCFG 119 | extern String gsSNTPServer[3]; // command AT+CIPSNTPCFG 120 | extern uint8_t gsServersMaxConn; // command AT+CIPSERVERMAXCONN 121 | extern uint32_t gsServerConnTimeout; // command AT+CIPSSTO 122 | 123 | extern const char APP_VERSION[]; 124 | extern const char MSG_OK[] PROGMEM; 125 | extern const char MSG_ERROR[] PROGMEM; 126 | extern const uint16_t MAX_PEM_CERT_LENGTH; 127 | 128 | /* 129 | * Public functions 130 | */ 131 | 132 | void DeleteClient(uint8_t index); 133 | void setDhcpMode(); 134 | void configureEthernet(); 135 | void setDns(); 136 | bool applyCipAp(); 137 | int SendData(int clientIndex, int maxSize); 138 | 139 | const char *nullIfEmpty(String &s); 140 | 141 | #endif /* ESP_ATMOD_H_ */ 142 | -------------------------------------------------------------------------------- /ESP_ATMod/settings.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * settings.cpp 3 | * 4 | * Part of ESP_ATMod: modified AT command processor for ESP8266 5 | * 6 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library; if not, see . 20 | */ 21 | 22 | #include "Arduino.h" 23 | #include "coredecls.h" 24 | 25 | #include 26 | 27 | #include "ESP_ATMod.h" 28 | #include "settings.h" 29 | #include "debug.h" 30 | 31 | /* 32 | * Class Settings 33 | */ 34 | 35 | /* 36 | * Getters and setters 37 | */ 38 | 39 | void Settings::setUartBaudRate(uint32_t baudRate) 40 | { 41 | EEPROMData eeprom; 42 | 43 | eeprom.setUartBaudRate(baudRate); 44 | } 45 | 46 | uint32_t Settings::getUartBaudRate() 47 | { 48 | EEPROMData eeprom; 49 | 50 | return eeprom.getUartBaudRate(); 51 | } 52 | 53 | void Settings::setUartConfig(SerialConfig config) 54 | { 55 | EEPROMData eeprom; 56 | 57 | eeprom.setUartConfig(config); 58 | } 59 | 60 | SerialConfig Settings::getUartConfig() 61 | { 62 | EEPROMData eeprom; 63 | 64 | return eeprom.getUartConfig(); 65 | } 66 | 67 | void Settings::setDhcpMode(uint8_t mode) 68 | { 69 | EEPROMData eeprom; 70 | 71 | eeprom.setDhcpMode(mode); 72 | } 73 | 74 | uint8_t Settings::getDhcpMode() 75 | { 76 | EEPROMData eeprom; 77 | 78 | return eeprom.getDhcpMode(); 79 | } 80 | 81 | void Settings::setNetConfig(ipConfig_t netCfg) 82 | { 83 | EEPROMData eeprom; 84 | 85 | eeprom.setNetConfig(netCfg); 86 | } 87 | 88 | ipConfig_t Settings::getNetConfig() 89 | { 90 | EEPROMData eeprom; 91 | 92 | return eeprom.getNetConfig(); 93 | } 94 | 95 | void Settings::setDnsConfig(dnsConfig_t dnsCfg) 96 | { 97 | EEPROMData eeprom; 98 | 99 | eeprom.setDnsConfig(dnsCfg); 100 | } 101 | 102 | dnsConfig_t Settings::getDnsConfig() 103 | { 104 | EEPROMData eeprom; 105 | 106 | return eeprom.getDnsConfig(); 107 | } 108 | 109 | void Settings::setApIpConfig(ipConfig_t apIpCfg) 110 | { 111 | EEPROMData eeprom; 112 | 113 | eeprom.setApIpConfig(apIpCfg); 114 | } 115 | 116 | void Settings::setEthIpConfig(ipConfig_t ethIpCfg) 117 | { 118 | EEPROMData eeprom; 119 | 120 | eeprom.setEthIpConfig(ethIpCfg); 121 | } 122 | 123 | ipConfig_t Settings::getApIpConfig() 124 | { 125 | EEPROMData eeprom; 126 | 127 | return eeprom.getApIpConfig(); 128 | } 129 | 130 | ipConfig_t Settings::getEthIpConfig() 131 | { 132 | EEPROMData eeprom; 133 | 134 | return eeprom.getEthIpConfig(); 135 | } 136 | 137 | void Settings::setMaximumCertificates(int maximumCertificates) 138 | { 139 | EEPROMData eeprom; 140 | 141 | eeprom.setMaximumCertificates(maximumCertificates); 142 | } 143 | 144 | int Settings::getMaximumCertificates() 145 | { 146 | EEPROMData eeprom; 147 | 148 | return eeprom.getMaximumCertificates(); 149 | } 150 | 151 | /* 152 | * Other functions 153 | */ 154 | 155 | void Settings::reset() 156 | { 157 | EEPROMData eeprom; 158 | 159 | resetData(eeprom.getDataPtr()); 160 | } 161 | 162 | void Settings::resetData(eepromData_t *dataPtr) 163 | { 164 | dataPtr->uartBaudRate = 115200; 165 | dataPtr->uartConfig = SERIAL_8N1; 166 | dataPtr->dhcpMode = 3; // for AT+CWDHCP command 167 | dataPtr->netConfig = ipConfig_t({0, 0, 0}); 168 | dataPtr->dnsConfig = dnsConfig_t({0, 0}); 169 | dataPtr->apIpConfig = ipConfig_t({0, 0, 0}); 170 | dataPtr->ethIpConfig = ipConfig_t({0, 0, 0}); 171 | dataPtr->maximumCertificates = 5; 172 | } 173 | 174 | /* 175 | * Inner Class EEPROMData 176 | */ 177 | 178 | Settings::EEPROMData::EEPROMData() 179 | { 180 | AT_DEBUG_PRINT("\r\n--- EEPROM on\r\n"); 181 | 182 | EEPROM.begin(EEPROM_DATA_SIZE); 183 | 184 | EEPROM.get(0, data); 185 | 186 | unsigned int crc = crc32(&data, sizeof(data) - 4); 187 | 188 | // Check the data is valid 189 | 190 | if (crc != data.crc32) 191 | { 192 | AT_DEBUG_PRINT("--- EEPROM reset\r\n"); 193 | 194 | Settings::resetData(&data); 195 | } 196 | } 197 | 198 | Settings::EEPROMData::~EEPROMData() 199 | { 200 | AT_DEBUG_PRINT("\r\n--- EEPROM off\r\n"); 201 | 202 | // Check for data update 203 | if (memcmp(&data, EEPROM.getConstDataPtr(), sizeof(data))) 204 | { 205 | AT_DEBUG_PRINT("--- EEPROM write\r\n"); 206 | 207 | data.crc32 = crc32(&data, sizeof(data) - 4); 208 | EEPROM.put(0, data); 209 | } 210 | 211 | EEPROM.end(); 212 | } 213 | -------------------------------------------------------------------------------- /ESP_ATMod/asnDecode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * asnDecode.cpp 3 | * 4 | * 5 | * Part of ESP_ATMod: modified AT command processor for ESP8266 6 | * 7 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 8 | * 9 | * This library is free software; you can redistribute it and/or 10 | * modify it under the terms of the GNU Lesser General Public 11 | * License as published by the Free Software Foundation; either 12 | * version 2.1 of the License, or (at your option) any later version. 13 | * 14 | * This library is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 | * Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public License 20 | * along with this library; if not, see . 21 | */ 22 | 23 | #include "Arduino.h" 24 | 25 | #include "asnDecode.h" 26 | 27 | /* 28 | * Note: this parser reads only the necessary information 29 | * to retrieve the CN (Common Name) field from the DER certificate. 30 | */ 31 | 32 | /* 33 | * X.509 certificate structure (partial): 34 | * 35 | * Certificate ::= SEQUENCE { 36 | * tbsCertificate TBSCertificate, 37 | * signatureAlgorithm AlgorithmIdentifier, 38 | * signature BIT STRING 39 | * } 40 | * 41 | * TBSCertificate ::= SEQUENCE { 42 | * version [ 0 ] Version DEFAULT v1(0), 43 | * serialNumber CertificateSerialNumber, 44 | * signature AlgorithmIdentifier, 45 | * issuer Name, 46 | * validity Validity, 47 | * subject Name, 48 | * subjectPublicKeyInfo SubjectPublicKeyInfo, 49 | * issuerUniqueID [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL, 50 | * subjectUniqueID [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL, 51 | * extensions [ 3 ] Extensions OPTIONAL 52 | * } 53 | * 54 | * Name ::= SEQUENCE OF RelativeDistinguishedName 55 | * 56 | * RelativeDistinguishedName ::= SET OF AttributeValueAssertion 57 | * 58 | * AttributeValueAssertion ::= SEQUENCE { 59 | * attributeType OBJECT IDENTIFIER, 60 | * attributeValue ANY 61 | * } 62 | */ 63 | 64 | /* 65 | * Debug 66 | */ 67 | 68 | //#define DBG(x) Serial.println(F(x)); 69 | #define DBG(x) 70 | 71 | /* 72 | * Defines 73 | */ 74 | 75 | enum asnTypes 76 | { 77 | ASN_INTEGER = 0x02, 78 | ASN_OBJECT_IDENTIFER = 0x06, 79 | ASN_SEQUENCE = 0x10, 80 | ASN_SET = 0x11, 81 | ASN_PRINTABLE_STRING = 0x13 82 | }; 83 | 84 | enum asnMethods 85 | { 86 | ASN_CONSTRUCTED = 0x20, 87 | ASN_UNIVERSAL = 0x00, 88 | ASN_APPLICATION = 0x40, 89 | ASN_CONTEXT_SPECIFIC = 0x80, 90 | ASN_PRIVATE = 0xc0 91 | }; 92 | 93 | /* 94 | * Local types 95 | */ 96 | 97 | typedef struct 98 | { 99 | uint8_t tag; 100 | uint16_t length; 101 | uint16_t dataPos; 102 | } asnHeader_t; 103 | 104 | /* 105 | * Static functions 106 | */ 107 | 108 | static asnHeader_t readHeader(uint8_t *der, uint16_t &pos, uint16_t length); 109 | 110 | /* 111 | * Public functions 112 | */ 113 | 114 | /* 115 | * Read CN field from DER certificate. The CN field should be part of the Name field. 116 | * Returns the pointer to the string value (first byte is length followed by UTF-8 characters) or nullptr 117 | */ 118 | uint8_t *getCnFromDer(uint8_t *der, uint16_t length) 119 | { 120 | // Input check 121 | if (der == nullptr) 122 | return nullptr; 123 | 124 | uint16_t pos = 0; 125 | asnHeader_t header; 126 | 127 | // 'Certificate' - sequence 128 | header = readHeader(der, pos, length); 129 | 130 | if (header.dataPos == 0 || header.tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) 131 | return nullptr; 132 | 133 | DBG("SEQUENCE Certificate"); 134 | 135 | // 'TBSCertificate' - sequence 136 | 137 | header = readHeader(der, header.dataPos, header.dataPos + header.length); 138 | 139 | if (header.dataPos == 0 || header.tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) 140 | return nullptr; 141 | 142 | DBG("--SEQUENCE TBSCertificate"); 143 | 144 | // Go inside the sequence 145 | uint16_t tbsCertPos = header.dataPos; 146 | uint16_t tbsCertEnd = tbsCertPos + header.length; 147 | 148 | // 'version' 149 | 150 | header = readHeader(der, tbsCertPos, tbsCertEnd); 151 | 152 | if (header.dataPos == 0 || header.tag != (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED)) 153 | return nullptr; 154 | 155 | DBG("----[0] version"); 156 | 157 | // 'serialNumber' - integer 158 | header = readHeader(der, tbsCertPos, tbsCertEnd); 159 | 160 | if (header.dataPos == 0 || header.tag != (ASN_INTEGER)) 161 | return nullptr; 162 | 163 | DBG("----INTEGER serialNumber"); 164 | 165 | // 'signature' - sequence 166 | header = readHeader(der, tbsCertPos, tbsCertEnd); 167 | 168 | if (header.dataPos == 0 || header.tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) 169 | return nullptr; 170 | 171 | DBG("----SEQUENCE signature"); 172 | 173 | // 'issuer' - sequence 174 | header = readHeader(der, tbsCertPos, tbsCertEnd); 175 | 176 | if (header.dataPos == 0 || header.tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) 177 | return nullptr; 178 | 179 | DBG("----SEQUENCE issuer"); 180 | 181 | // Go inside the sequence 182 | uint16_t issuerPos = header.dataPos; 183 | uint16_t issuerEnd = issuerPos + header.length; 184 | 185 | // Scan the issuer sequence 186 | while (issuerPos < issuerEnd) 187 | { 188 | // 'RelativeDistinguishedName' - SET 189 | header = readHeader(der, issuerPos, issuerEnd); 190 | 191 | if (header.dataPos == 0 || header.tag != (ASN_SET | ASN_CONSTRUCTED)) 192 | return nullptr; 193 | 194 | DBG("------SET"); 195 | 196 | // Go inside the set 197 | uint16_t setPos = header.dataPos; 198 | uint16_t setEnd = setPos + header.length; 199 | 200 | // 'AttributeValueAssertion' - SEQUENCE 201 | header = readHeader(der, setPos, setEnd); 202 | 203 | if (header.dataPos == 0 || header.tag != (ASN_SEQUENCE | ASN_CONSTRUCTED)) 204 | return nullptr; 205 | 206 | DBG("--------SEQUENCE"); 207 | 208 | // Go inside the sequence 209 | uint16_t attrPos = header.dataPos; 210 | uint16_t attrEnd = attrPos + header.length; 211 | 212 | // 'attributeType' 213 | header = readHeader(der, attrPos, attrEnd); 214 | 215 | if (header.dataPos == 0 || header.tag != ASN_OBJECT_IDENTIFER) 216 | return nullptr; 217 | 218 | DBG("--------OBJECT_IDENTIFIER"); 219 | 220 | // Check the ID 2.5.4.3 - commonName 221 | 222 | if (header.length == 3 && !memcmp(der + header.dataPos, "\x55\x04\x03", 3)) 223 | { 224 | // 'attributeValue' 225 | header = readHeader(der, attrPos, attrEnd); 226 | 227 | if (header.dataPos == 0) 228 | return nullptr; 229 | 230 | DBG("--------Value"); 231 | 232 | if (header.tag != ASN_PRINTABLE_STRING) 233 | return nullptr; 234 | 235 | return (der + header.dataPos - 1); // the last byte before the string should be the length - assume max 127 bytes 236 | } 237 | } 238 | 239 | return nullptr; 240 | } 241 | 242 | /* 243 | * Static functions 244 | */ 245 | 246 | /* 247 | * Read ASN header record 248 | */ 249 | static asnHeader_t readHeader(uint8_t *der, uint16_t &pos, uint16_t length) 250 | { 251 | asnHeader_t hdr = {0, 0, 0}; 252 | 253 | // Checking 254 | if (der == nullptr || pos >= length) 255 | return hdr; 256 | 257 | hdr.tag = der[pos]; 258 | 259 | uint8_t hdrsize; 260 | 261 | if (der[pos + 1] < 0x80) 262 | { 263 | hdr.length = der[pos + 1]; 264 | hdrsize = 2; 265 | } 266 | else if (der[pos + 1] == 0x82) // Omitting other length on purpose 267 | { 268 | hdr.length = (der[pos + 2] << 8) | der[pos + 3]; 269 | hdrsize = 4; 270 | } 271 | else 272 | return hdr; 273 | 274 | // Check the length 275 | if (pos + hdrsize > length) 276 | return hdr; // out of buffer 277 | 278 | hdr.dataPos = pos + hdrsize; 279 | pos += hdrsize + hdr.length; // advance to the next tag in the same level 280 | 281 | return hdr; 282 | } 283 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP 8266 AT Firmware - modified for TLS 1.2 2 | 3 | This firmware comes as an [Arduino esp8266](https://github.com/esp8266/Arduino#arduino-on-esp8266) sketch. 4 | 5 | This file refers to version 0.5.0 of the firmware. 6 | 7 | ## Purpose 8 | 9 | The AT firmware provided by Espressif comes with basic TLS ciphersuites only. Especially, the lack of GCM-based ciphersuites makes the SSL part of the Espressif's firmware unusable on more and more web sites. This firmware addresses this issue. 10 | 11 | **The goal was to enable all modern ciphersuites implemented in BearSSL library included in esp8266/Arduino project including with some server authentication (server certificate checking).** 12 | 13 | The firmware fits into 1024 KB flash and can be run even on ESP-01 module with 8 Mbit flash. 14 | 15 | ## Description 16 | 17 | The firmware does not (and likely will not) implement the whole set of AT commands defined in Espressif's documentation. 18 | 19 | The major differences are: 20 | 21 | 1. Only TCP mode (with or without TLS) is supported, no UDP. 22 | 23 | 2. In multiplex mode (AT+CIPMUX=1), 5 simultaneous connections are available. Due to memory constraints, there can be only one TLS (SSL) connection at a time with standard buffer size, more concurrent TLS connections can be made with a reduced buffer size (AT+CIPSSLSIZE). When the buffer size is 512 bytes, all 5 concurrent connections can be TLS. 24 | 25 | New features: 26 | 27 | 1. Implemented TLS security with state-of-the-art ciphersuites: certificate fingerprint checking or certificate chain verification. 28 | 29 | 2. Implemented TLS MFLN check (RFC 3546), setting TLS receive buffer size, checking MFLN status of a connection. 30 | 31 | ## Status 32 | 33 | The firmware is still in work-in-progress state. It has been tested and is running on my devices but there might be deviations from the expected behaviour. 34 | 35 | My testing environment uses the [WifiEsp library](https://github.com/bportaluri/WiFiEsp) and also the newer [WiFiEspAT library](https://github.com/jandrassy/WiFiEspAT). 36 | 37 | ## The Future 38 | 39 | Next development will be focused on 40 | 41 | 1. More complete AT command implementation. 42 | 43 | ## Installation 44 | 45 | There are two options for compiling and flashing this library. 46 | 47 | ### Arduino IDE 48 | 49 | First you have to install [Arduino IDE](https://www.arduino.cc/en/software) and the [core](https://github.com/esp8266/Arduino#installing-with-boards-manager) for the ESP8266 chip. Next get all source files from this repository, place them in a folder named **ESP_ATMod** and compile and upload to your ESP module. 50 | 51 | After flashing, the module will open serial connection on RX and TX pins with 115200 Bd, 8 bits, no parity. You can talk with the module using a serial terminal of your choice. 52 | 53 | **IMPORTANT NOTE:** From the firmware version 0.4.0 on, you need to use the Arduino Core for 8266 version 3.0+. 54 | 55 | ### PlatformIO 56 | 57 | An alternative to using the Arduino IDE is to use PlatformIO. 58 | 59 | 1. Install [PlatformIO](https://platformio.org/) 60 | 2. Make sure that your device is in flashing mode 61 | 3. In your favourite terminal and from the root of this repository, run 62 | the following command to build and upload the sketch to the device: 63 | ``` 64 | platformio run --target upload 65 | ``` 66 | 67 | This has been configured and tested for the ESP-01 Black. 68 | 69 | ## Add certificates 70 | 71 | Certificates are stored in the ESP's filesystem with LittleFS. To add a certificate follow the following steps. 72 | 73 | **IMPORTANT: the certifcate must be in .pem format.** 74 | 75 | 1. Copy the certificate you want to the data directory in ESP_ATMod 76 | 2. Install the [LittleFS Filesystem Uploader](https://github.com/earlephilhower/arduino-esp8266littlefs-plugin#installation) 77 | 3. Select Tools > ESP8266 LittleFS Data Upload menu item. This should start uploading the files into ESP8266 flash file system. When done, IDE status bar will display LittleFS Image Uploaded message. Might take a few minutes for large file system sizes. 78 | 4. Now upload the ESP_ATMod sketch to the ESP. 79 | 5. The certificate(s) you uploaded are now loaded and ready to use (you can check them with [AT+CIPSSLCERT](https://github.com/JiriBilek/ESP_ATMod#atcipsslcert---load-query-or-delete-tls-ca-certificate)). 80 | 6. (Optional) You may delete the .gitkeep file in the data directory. It is only there to push and pull the data directory in git. Not deleting the .gitkeep file won't do any harm. 81 | 82 | ## AT Command List 83 | 84 | In the following table, the list of supported AT commands is given. In the comment, only a difference between this implementation and the original Espressif's AT command firmware is given. The commands are implemented according to the Espressif's documentation, including the command order. Please refer to the [Espressif's documentation](https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf) for further information. 85 | 86 | AT commands with _DEF and _CUR have (as in the standard AT firmware) an undocumented version without _DEF/CUR for backward compatibility (and forward too since AT 2 doesn't use _DEF/CUR). The command without _DEF/CUR behaves as _CUR for query and as _DEF for set (stores the parameters to the flash). 87 | 88 | | Command | Description | 89 | | - | - | 90 | | [**Basic AT Commands**](https://docs.espressif.com/projects/esp-at/en/latest/AT_Command_Set/Basic_AT_Commands.html#basic-at-commands) | | 91 | | AT | Test AT startup. | 92 | | AT+RST | Restart a module. | 93 | | AT+GMR | Check version information. | 94 | | ATE | Configure AT commands echoing. | 95 | | AT+RESTORE | Restore factory default settings of the module. | 96 | | AT+UART_CUR | Current UART configuration, not saved in flash. | 97 | | AT+UART_DEF | Default UART configuration, saved in flash. | 98 | | AT+SYSRAM | Query current remaining heap size and minimum heap size. | 99 | | [**Wi-Fi AT Commands**](https://docs.espressif.com/projects/esp-at/en/latest/AT_Command_Set/Wi-Fi_AT_Commands.html#wi-fi-at-commandss) | | 100 | | AT+CWMODE | Set the Wi-Fi mode (Station/SoftAP/Station+SoftAP). | 101 | | AT+CWJAP_CUR | Connect to an AP, parameter <pci_en> not implemented | 102 | | AT+CWJAP_DEF | Connect to AP, saved to flash. Parameter <pci_en> not implemented | 103 | | AT+CWLAPOPT | Set the configuration for the command AT+CWLAP. | 104 | | AT+CWLAP | List available APs. | 105 | | AT+CWQAP | Disconnect from an AP. | 106 | | AT+CWSAP_CUR | Start SoftAP, parameter <ecn> is not used. WPA_WPA2_PSK is used, if <pwd> is not empty. | 107 | | AT+CWSAP_DEF | Connect to AP, saved to flash. Parameter <ecn> is not used. WPA_WPA2_PSK is used, if <pwd> is not empty. | 108 | | AT+CWDHCP_CUR | Enable/disable DHCP - SoftAP DHCP server enabling is not immplemented. | 109 | | AT+CWDHCP_DEF | Enable/disable DHCP saved to flash - SoftAP DHCP server enabling is not immplemented. | 110 | | AT+CWAUTOCONN | Connect to an AP automatically when powered on. | 111 | | AT+CIPSTAMAC_CUR | Sets or prints the MAC Address of the ESP8266 Station. Only query is implemented. 112 | | AT+CIPSTAMAC_DEF | Sets or prints the MAC Address of the ESP8266 Station stored in flash. Only query is implemented. 113 | | AT+CIPAPMAC_CUR | Sets or prints the MAC Address of the ESP8266 SoftAP. Only query is implemented. 114 | | AT+CIPAPMAC_DEF | Sets or prints the MAC Address of the ESP8266 SoftAP stored in flash. Only query is implemented. 115 | | AT+CIPSTA_CUR | Query/Set the IP address of an ESP station. | 116 | | AT+CIPSTA_DEF | Set and/or print current IP address, gateway and network mask, stored in flash. | 117 | | AT+CIPAP_CUR | Query/Set the current IP address of the SoftAP. | 118 | | AT+CIPAP_DEF | Set and/or print SoftAP IP address, gateway and network mask, stored in flash. | 119 | | AT+CWHOSTNAME | Query/Set the host name of an ESP Station. | 120 | | [**TCP/IP AT Commands**](https://docs.espressif.com/projects/esp-at/en/latest/AT_Command_Set/TCP-IP_AT_Commands.html) | | 121 | | AT+CIPSTATUS | Obtain the TCP/UDP/SSL connection status and information. | 122 | | AT+CIPDOMAIN | Resolve a Domain Name. | 123 | | AT+CIPSTART |Establish TCP connection, or SSL connection. Only one TLS connection at a time. | 124 | | [AT+CIPSSLSIZE](https://github.com/JiriBilek/ESP_ATMod#atcipsslsize---set-the-tls-receiver-buffer-size) | Change the size of the receiver buffer (512, 1024, 2048 or 4096 bytes) | 125 | | AT+CIPSEND | Send data in the normal transmission mode or Wi-Fi passthrough mode. | 126 | | AT+CIPCLOSEMODE | Set the Close Mode of TCP Connection. | 127 | | AT+CIPCLOSE | Close TCP/SSL connection. | 128 | | AT+CIFSR | Obtain the local IP address and MAC address. | 129 | | AT+CIPMUX | Enable/disable the multiple connections mode. Max. 5 conections, only one of them can be TLS | 130 | | AT+CIPSNTPCFG | Query/Set the time zone and SNTP server. | 131 | | AT+CIPSNTPTIME | Query the SNTP time. | 132 | | AT+CIPDINFO | Set +IPD message mode. | 133 | | [AT+CIPRECVMODE](https://github.com/JiriBilek/ESP_ATMod#atciprecvmode-atciprecvdata-atciprecvlen-in-ssl-mode) | Query/Set socket receiving mode. | 134 | | [AT+CIPRECVDATA](https://github.com/JiriBilek/ESP_ATMod#atciprecvmode-atciprecvdata-atciprecvlen-in-ssl-mode) | Obtain socket data in passive receiving mode. | 135 | | [AT+CIPRECVLEN](https://github.com/JiriBilek/ESP_ATMod#atciprecvmode-atciprecvdata-atciprecvlen-in-ssl-mode) | Obtain socket data length in passive receiving mode. | 136 | | AT+CIPDNS_CUR | Query/Set DNS server information. | 137 | | AT+CIPDNS_DEF | Default DNS setting, stored in flash | 138 | | [AT+CIPSERVER](#atcipserver-atcipservermaxconn-and-atcipsto) | Deletes/Creates TCP Server | 139 | | AT+CIPSERVERMAXCONN | Set the maximum connections allowed by server | 140 | | AT+CIPSTO | Sets the TCP Server Timeout | 141 | | **New commands** | | 142 | | [AT+SYSCPUFREQ](https://github.com/JiriBilek/ESP_ATMod#atsyscpufreq---set-or-query-the-current-cpu-frequency) | Set or query the current CPU frequency. | 143 | | [AT+RFMODE](https://github.com/JiriBilek/ESP_ATMod#atrfmode---get-and-change-the-physical-wifi-mode) | Set the physical wifi mode. | 144 | | [AT+CIPSSLAUTH](https://github.com/JiriBilek/ESP_ATMod#atcipsslauth---set-and-query-the-tls-authentication-mode) | Set and query the TLS authentication mode. | 145 | | [AT+CIPSSLFP](https://github.com/JiriBilek/ESP_ATMod#atcipsslfp---load-or-print-tls-server-certificate-sha-1-fingerprint) | Load or print the TLS server certificate fingerprint. | 146 | | [AT+CIPSSLCERTMAX](https://github.com/JiriBilek/ESP_ATMod#atcipsslcertmax---query-or-set-maximum-certificates-to-load) | Query or set the maximum amount of certificates that can be loaded. | 147 | | [AT+CIPSSLCERT](https://github.com/JiriBilek/ESP_ATMod#atcipsslcert---load-query-or-delete-tls-ca-certificate) | Load, query or delete TLS CA certificate. | 148 | | [AT+CIPSSLMFLN](https://github.com/JiriBilek/ESP_ATMod#atcipsslmfln---checks-if-the-given-site-supports-the-mfln-tls-extension) | Check if the site supports Maximum Fragment Length Negotiation (MFLN). | 149 | | [AT+CIPSSLSTA](https://github.com/JiriBilek/ESP_ATMod#atcipsslsta---checks-the-status-of-the-mfln-negotiation) | Prints the MFLN status of a connection. | 150 | | [AT+SNTPTIME](https://github.com/JiriBilek/ESP_ATMod#atsystime---returns-the-current-time-utc) | Get SNTP time. | 151 | | [**New Ethernet AT Commands**](https://docs.espressif.com/projects/esp-at/en/latest/esp32/AT_Command_Set/Ethernet_AT_Commands.html) | | 152 | | AT+CIPETHMAC_CUR | Sets or prints the MAC Address of the Ethernet interface. | 153 | | AT+CIPETHMAC_DEF | Sets or prints the MAC Address of the Ethernet interface stored in flash. Save to flash is not implemented. | 154 | | AT+CIPETH_CUR | Query/Set the IP address of the Ethernet interface. | 155 | | AT+CIPETH_DEF | Set and/or print current IP address, gateway and network mask, stored in flash. | 156 | | AT+CEHOSTNAME | Query/Set the host name of the Ethernet interface. | 157 | 158 | ## Changed Commands 159 | 160 | ### **AT+CIPSSLSIZE - Set the TLS Receiver Buffer Size** 161 | 162 | Sets the TLS receiver buffer size. The size can be 512, 1024, 2048, 4096 or 16384 (default) bytes according to [RFC3546](https://tools.ietf.org/html/rfc3546). The value is used for all subsequent TLS connections, the opened connections are not affected. 163 | 164 | *Command:* 165 | 166 | ``` 167 | AT+CIPSSLSIZE=512 168 | ``` 169 | 170 | *Answer:* 171 | ``` 172 | 173 | OK 174 | ``` 175 | 176 | ### **AT+CIPRECVMODE, AT+CIPRECVDATA, AT+CIPRECVLEN in SSL mode** 177 | 178 | Commands 179 | 180 | - AT+CIPRECVMODE (Set TCP or SSL Receive Mode) 181 | - AT+CIPRECVDATA (Get TCP or SSL Data in Passive Receive Mode) 182 | - AT+CIPRECVLEN (Get TCP or SSL Data Length in Passive Receive Mode) 183 | 184 | Works in SSL mode in the same way as in TCP mode. 185 | 186 | ### **AT+CIPSERVER, AT+CIPSERVERMAXCONN and AT+CIPSTO** 187 | 188 | The standard AT firmware supports only one server. This firmware support up to 5 server with same AT+CIPCIPSERVER command. 189 | 190 | In standard AT firmware 1.7 executing `AT+CIPSERVER=1,` again even if the port is different prints no change and OK. Here it starts a new server. "no change" is returned only if maximum count of servers is reached. 191 | 192 | In standard AT firmware 1.7 executing AT+CIPSERVER=0 stops the one server. Here it stops the first one. Executing `AT+CIPSERVER=0,` stops the server listening on ``. 193 | 194 | CIPSERVERMAXCONN and CIPSTO are global settings, They apply to all servers. 195 | 196 | ### **AT+CWDHCP** 197 | 198 | In standard AT firmware AT_CWDHCP enables/disables the DHCP client for STA (mode 0) and starts or stops the DHCP server for SoftAP (mode 1). In ESP_ATMod the SoftAP DHCP server is always enabled. The AT+CWDHCP command is not implemented for SoftAP. 199 | 200 | For Ethernet support, the AT+CWDHCP command is enhanced. The Ethernet DHCP client is enabled/disabled with mode 3. For AT+CWDHCP?, state of the Ethernet DHCP client is returned in third bit. 201 | 202 | ## New Commands 203 | 204 | ### **AT+SYSCPUFREQ - Set or query the Current CPU Frequency** 205 | 206 | Sets and queries the CPU freqency. The only valid values are 80 and 160 Mhz. 207 | 208 | **Query:** 209 | 210 | *Command:* 211 | ``` 212 | AT+SYSCPUFREQ? 213 | ``` 214 | 215 | *Answer:* 216 | ``` 217 | +SYSCPUFREQ=80 218 | 219 | OK 220 | ``` 221 | 222 | **Set:** 223 | 224 | *Command:* 225 | ``` 226 | AT+SYSCPUFREQ= 227 | ``` 228 | 229 | *Answer:* 230 | ``` 231 | 232 | OK 233 | ``` 234 | 235 | The value freq may be 80 or 160. 236 | 237 | ### **AT+RFMODE - Get and Change the Physical Wifi Mode** 238 | 239 | Sets and queries the physical wifi mode. 240 | 241 | **Query:** 242 | 243 | *Command:* 244 | ``` 245 | AT+RFMODE? 246 | ``` 247 | 248 | *Answer:* 249 | ``` 250 | +RFMODE=1 251 | 252 | OK 253 | ``` 254 | 255 | **Set:** 256 | 257 | *Command:* 258 | ``` 259 | AT+RFMODE= 260 | ``` 261 | 262 | *Answer:* 263 | ``` 264 | 265 | OK 266 | ``` 267 | 268 | The allowed values of <mode> are: 269 | 270 | | Mode | Description | 271 | | - | - | 272 | | 1 | IEEE 802.11b | 273 | | 2 | IEEE 802.11g | 274 | | 3 | IEEE 802.11n | 275 | 276 | ### **AT+CIPSSLAUTH - Set and Query the TLS Authentication Mode** 277 | 278 | Set or queries the selected TLS authentication mode. The default is no authentication. Try to avoid this because it is insecure and prone to MITM attack. 279 | 280 | **Query:** 281 | 282 | *Command:* 283 | ``` 284 | AT+CIPSSLAUTH? 285 | ``` 286 | 287 | *Answer:* 288 | ``` 289 | +CIPSSLAUTH=0 290 | 291 | OK 292 | ``` 293 | 294 | **Set:** 295 | 296 | *Command:* 297 | ``` 298 | AT+CIPSSLAUTH= 299 | ``` 300 | 301 | *Answer:* 302 | ``` 303 | 304 | OK 305 | ``` 306 | 307 | The allowed values of <mode> are: 308 | 309 | | Mode | Description | 310 | | - | - | 311 | | 0 | No authentication. Default. Insecure | 312 | | 1 | Server certificate fingerprint checking | 313 | | 2 | Certificate chain checking | 314 | 315 | Switching to mode 1 succeeds only when the certificate SHA-1 fingerprint is preloaded (see AT+CIPSSLFP). 316 | 317 | Switching to mode 2 succeeds only when the CA certificate preloaded (see AT+CIPSSLCERT). 318 | 319 | 320 | ### **AT+CIPSSLFP - Load or Print TLS Server Certificate SHA-1 Fingerprint** 321 | 322 | Load or print the saved server certificate fingerprint. The fingerprint is based on SHA-1 hash and is exactly 20 bytes long. When connecting, the TLS engine checks the fingerprint of the received certificate against the saved value. It ensures the device is connecting to the expected server. After a successful connection, the fingerprint is checked and is no longer needed for this connection. 323 | 324 | The SHA-1 certificate fingerprint for a site can be obtained e.g. in browser while examining the server certificate. 325 | 326 | **Query:** 327 | 328 | *Command:* 329 | ``` 330 | AT+CIPSSLFP? 331 | ``` 332 | 333 | *Answer:* 334 | ``` 335 | +CIPSSLFP:"4F:D5:B1:C9:B2:8C:CF:D2:D5:9C:84:5D:76:F6:F7:A1:D0:A2:FA:3D" 336 | 337 | OK 338 | ``` 339 | 340 | **Set:** 341 | 342 | *Command:* 343 | ``` 344 | AT+CIPSSLFP="4F:D5:B1:C9:B2:8C:CF:D2:D5:9C:84:5D:76:F6:F7:A1:D0:A2:FA:3D" 345 | ``` 346 | 347 | or 348 | 349 | ``` 350 | AT+CIPSSLFP="4FD5B1C9B28CCFD2D59C845D76F6F7A1D0A2FA3D" 351 | ``` 352 | 353 | *Answer:* 354 | ``` 355 | 356 | OK 357 | ``` 358 | 359 | The fingerprint consists of exactly 20 bytes. They are set as hex values and may be divided with ':'. 360 | 361 | ### **AT+CIPSSLCERTMAX - Query or set maximum certificates to load** 362 | 363 | Currently maximum 5 certificates at a time can be loaded. With this command the amount of certificates to load with LittleFS can be adjusted. 364 | 365 | **Query amount to load:** 366 | 367 | *Command:* 368 | 369 | ``` 370 | AT+CIPSSLCERTMAX? 371 | ``` 372 | 373 | *Answer:* 374 | ``` 375 | +CIPSSLCERTMAX:5 376 | OK 377 | ``` 378 | 379 | **Set amount to load:** 380 | 381 | *Command:* 382 | 383 | ``` 384 | AT+CIPSSLCERTMAX=6 385 | ``` 386 | 387 | *Answer:* 388 | ``` 389 | +CIPSSLCERTMAX:6 390 | OK 391 | ``` 392 | 393 | ### **AT+CIPSSLCERT - Load, Query or Delete TLS CA Certificate** 394 | 395 | Load, query or delete CA certificate for TLS certificate chain verification. Currently maximum 5 certificates at a time can be loaded. The certificates must be in PEM structure. After a successful connection, the certificate is checked and is no longer needed for this connection. 396 | 397 | **Query the first certificate:** 398 | 399 | *Command:* 400 | ``` 401 | AT+CIPSSLCERT? 402 | ``` 403 | 404 | *Answer:* 405 | ``` 406 | +CIPSSLCERT:no cert 407 | 408 | ERROR 409 | ``` 410 | 411 | or 412 | 413 | ``` 414 | +CIPSSLCERT,1:DST Root CA X3 415 | +CIPSSLCERT,2:DST Root CA X3 416 | 417 | OK 418 | ``` 419 | 420 | **Query specific certificate:** 421 | 422 | *Command:* 423 | ``` 424 | AT+CIPSSLCERT?2 425 | ``` 426 | 427 | *Answer:* 428 | ``` 429 | +CIPSSLCERT,2:DST Root CA X3 430 | 431 | OK 432 | ``` 433 | 434 | **Set:** 435 | 436 | *Command:* 437 | ``` 438 | AT+CIPSSLCERT 439 | ``` 440 | 441 | *Answer:* 442 | ``` 443 | 444 | OK 445 | > 446 | ``` 447 | 448 | You can now send the certificate (PEM encoding), no echo is given. After the last line (`-----END CERTIFICATE-----`), the certificate is parsed and loaded. The certificate should be sent with \n notation. For example [isrg-root-x1-cross-signed.pem](https://letsencrypt.org/certs/isrg-root-x1-cross-signed.pem): 449 | 450 | ``` 451 | -----BEGIN CERTIFICATE----- 452 | MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ 453 | MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT 454 | DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow 455 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 456 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB 457 | AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC 458 | ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL 459 | wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D 460 | LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK 461 | 4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 462 | bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y 463 | sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ 464 | Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 465 | FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc 466 | SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql 467 | PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND 468 | TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw 469 | SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 470 | c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx 471 | +tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB 472 | ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu 473 | b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E 474 | U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu 475 | MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC 476 | 5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW 477 | 9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG 478 | WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O 479 | he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC 480 | Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 481 | -----END CERTIFICATE----- 482 | ``` 483 | 484 | Should be: 485 | ``` 486 | -----BEGIN CERTIFICATE-----\nMIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/\nMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\nDkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow\nTzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\ncmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB\nAQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC\nov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL\nwYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D\nLtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK\n4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5\nbHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y\nsR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ\nXmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4\nFQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc\nSLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql\nPRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND\nTwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw\nSwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1\nc3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx\n+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB\nATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu\nb3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E\nU1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu\nMA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC\n5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW\n9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG\nWCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O\nhe8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC\nDfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5\n-----END CERTIFICATE----- 487 | ``` 488 | 489 | The application responds with 490 | 491 | ``` 492 | Read 1952 bytes 493 | 494 | OK 495 | ``` 496 | or with an error message. In case of a successful loading, the certificate is ready to use and you can turn the certificate checking on (`AT+CIPSSLAUTH=2`). 497 | 498 | The limit for the PEM certificate is 4096 characters total. 499 | 500 | **Delete a certificate:** 501 | 502 | *Command:* 503 | ``` 504 | AT+CIPSSLCERT=DELETE,1 505 | ``` 506 | 507 | *Answer:* 508 | ``` 509 | +CIPSSLCERT,1:deleted 510 | 511 | OK 512 | ``` 513 | 514 | The certificate is deleted from the memory. 515 | 516 | ### **AT+CIPSSLMFLN - Checks if the given site supports the MFLN TLS Extension** 517 | 518 | The Maximum Fragment Length Negotiation extension is useful for lowering the RAM usage by reducing receiver buffer size on TLS connections. Newer TLS implementations support this extension but it would be wise to check the capability before changing a TLS buffer size and making a connection. As the server won't change this feature on the fly, you should test the MFLN capability only once. 519 | 520 | *Command:* 521 | 522 | AT+CIPSSLMFLN="*site*",*port*,*size* 523 | 524 | The valid sizes are 512, 1024, 2048 and 4096. 525 | 526 | ``` 527 | AT+CIPSSLMFLN="www.github.com",443,512 528 | ``` 529 | 530 | *Answer:* 531 | ``` 532 | +CIPSSLMFLN:TRUE 533 | 534 | OK 535 | ``` 536 | 537 | ### **AT+CIPSSLSTA - Checks the status of the MFLN negotiation** 538 | 539 | This command checks the MFLN status on an opened TLS connection. 540 | 541 | *Command:* 542 | 543 | AT+CIPSSLSTA[=linkID] 544 | 545 | The *linkID* value is mandatory when the multiplexing is on (AT+CIPMUX=1). It should be not entered when the multiplexing is turned off. 546 | 547 | ``` 548 | AT+CIPSSLSTA=0 549 | ``` 550 | 551 | *Answer:* 552 | ``` 553 | +CIPSSLSTA:1 554 | 555 | OK 556 | ``` 557 | 558 | The returned value of 1 means there was a MFLN negotiation. It holds even with the default receiver buffer size set. 559 | 560 | ### **AT+SYSTIME - Returns the current time UTC** 561 | 562 | This command returns the current time as unix time (number of seconds since January 1st, 1970). The time zone is fixed to GMT (UTC). The time is obtained by querying NTP servers automatically, after connecting to the internet. Before connecting to the internet or in case of an error in communication with NTP servers, the time is unknown. This situation should be temporary. 563 | 564 | *Command:* 565 | ``` 566 | AT+SYSTIME? 567 | ``` 568 | 569 | *Answer:* 570 | ``` 571 | +SYSTIME:1607438042 572 | OK 573 | ``` 574 | 575 | If the current time is unknown, an error message is returned. 576 | 577 | ### **AT+CIPETH, AT+CIPETHMAC, AT+CEHOSTNAME** 578 | 579 | These commands support use of the Ethernet interface as in standard AT firmware version 2 and newer. 580 | 581 | These commands for the Ethernet interface are analogous to AT+CIPSTA, AT+CIPSTAMAC and AT+CWHOSTNAME commands. 582 | -------------------------------------------------------------------------------- /ESP_ATMod/ESP_ATMod.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * ESP_ATMod 3 | * 4 | * Modified AT command processor for ESP8266 5 | * Implements only a subset of AT commands 6 | * Main goal is to enable safe TLS 1.2 - uses all the state-of-the-art ciphersuits from BearSSL 7 | * 8 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 9 | * 10 | * This library is free software; you can redistribute it and/or 11 | * modify it under the terms of the GNU Lesser General Public 12 | * License as published by the Free Software Foundation; either 13 | * version 2.1 of the License, or (at your option) any later version. 14 | * 15 | * This library is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * Lesser General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU Lesser General Public License 21 | * along with this library; if not, see . 22 | */ 23 | 24 | /* 25 | * Version history: 26 | * 27 | * 0.1.0: First version for testing 28 | * 0.1.1: TLS fingerprint authentication (AT+CIPSSLAUTH, AT+CIPSSLFP) 29 | * 0.1.2: TLS CA certificate checking (AT+CIPSSLCERT) 30 | * 0.2.0: AT Version 1.7 - AT+CIPRECVMODE, AT+CIPRECVLEN, AT+CIPRECVDATA 31 | * 0.2.1: Fix the _CUR and _DEF command suffixes: empty suffix is equivalent to _DEF 32 | * 0.2.2: Full commands AT+CWDHCP (for station mode), AT+CIPSTA and AT+CIPDNS 33 | * 0.2.3: AT+SYSCPUFREQ (80 or 160) - inspired by LoBo AT, useful for TLS connections 34 | * Resize the input buffer (AT+CIPSSLSIZE) - supported values: 512, 1024, 2048, 4096, 16384 (RFC 3546) 35 | * Check the site MFLN capability (AT+CIPSSLMFLN), check the connection MFLN status (AT+CIPSSLSTA) 36 | * 0.2.4: AT+SYSTIME? for returning unixtime 37 | * 0.2.5: revert AT+SYSTIME, implement AT+CIPSNTPCFG, AT+CIPSNTPTIME, AT+SNTPTIME 38 | * 0.2.6: add space to the response to command cmd_AT_CIPSEND (for compatibility with AT 1.x original firmware) 39 | * 0.2.7: fix 'busy p...' text sending while connecting - send on every received char 40 | * 0.2.8: add AT+CIPCLOSEMODE 41 | * 0.3.0: Stored certificates in filesystem with LittleFS 42 | * 0.3.1: AT+CWLAP, AT+CWLAPOPT and 'busy p...' fix 43 | * 0.3.2: AT+CWHOSTNAME 44 | * 0.3.3: AT+CIPSERVER, AT+CIPSTO, AT+CIPSERVERMAXCONN [J.A] 45 | * 0.3.4: SoftAP mode AT+CWMODE, AT+CWSAP, AT+CIPAP [J.A] 46 | * 0.3.6: AT+CIPSTAMAC and AT+CIPAPMAC query only [J.A] 47 | * 0.3.6a: Fixed long hostname in AT+CIPSTART, input buffer and string search increased to 200 characters 48 | * 0.3.6b: Checking the appropriate mode for some commands [J.A] 49 | * 0.4.0: Arduino ESP8266 Core 3.1.1 50 | * 0.4.0a: AT+CIPSTATUS lists SoftAP TCP connections [J.A] 51 | * 0.4.0b: AT+CIPDOMAIN implementation [J.A] 52 | * 0.5.0: support Ethernet AT+CIPETH, AT+CIPETHMAC, AT+CEHOSTNAME and modified AT+CWDHCP [J.A] 53 | * 54 | * TODO: 55 | * - Implement AP mode DHCP settings and AT+CWLIF 56 | * - Implement UDP 57 | * - TLS Security - persistent fingerprint and single certificate, AT+CIPSSLAUTH_DEF 58 | */ 59 | 60 | #include "Arduino.h" 61 | #include "LittleFS.h" 62 | #include "string.h" 63 | 64 | #include 65 | #include 66 | 67 | extern "C" 68 | { 69 | #include "user_interface.h" 70 | 71 | #include "lwip/dns.h" 72 | } 73 | 74 | #include "ESP_ATMod.h" 75 | #include "WifiEvents.h" 76 | #include "command.h" 77 | #include "settings.h" 78 | #include "debug.h" 79 | #include "asnDecode.h" 80 | 81 | #ifdef ETHERNET_CLASS 82 | ETHERNET_CLASS Ethernet(ETHERNET_CS); 83 | #endif 84 | 85 | /* 86 | * Defines 87 | */ 88 | 89 | const char APP_VERSION[] = "0.5.0"; 90 | 91 | /* 92 | * Constants 93 | */ 94 | 95 | const char MSG_OK[] PROGMEM = "\r\nOK\r\n"; 96 | const char MSG_ERROR[] PROGMEM = "\r\nERROR\r\n"; 97 | 98 | const uint16_t MAX_PEM_CERT_LENGTH = 4096; // Maximum size of a certificate loaded by AT+CIPSSLCERT 99 | 100 | /* 101 | * Global variables 102 | */ 103 | 104 | uint8_t inputBuffer[INPUT_BUFFER_LEN]; // Input buffer 105 | uint16_t inputBufferCnt; // Number of bytes in inputBuffer 106 | 107 | WiFiEventHandler onConnectedHandler; 108 | WiFiEventHandler onGotIPHandler; 109 | WiFiEventHandler onDisconnectedHandler; 110 | 111 | client_t clients[5] = {{nullptr, TYPE_NONE, 0, 0, 0}, 112 | {nullptr, TYPE_NONE, 0, 0, 0}, 113 | {nullptr, TYPE_NONE, 0, 0, 0}, 114 | {nullptr, TYPE_NONE, 0, 0, 0}, 115 | {nullptr, TYPE_NONE, 0, 0, 0}}; 116 | 117 | WiFiServer servers[] = {WiFiServer(0), WiFiServer(0), WiFiServer(0), WiFiServer(0), WiFiServer(0)}; 118 | const uint8_t SERVERS_COUNT = sizeof(servers) / sizeof(WiFiServer); 119 | 120 | uint8_t sendBuffer[2048]; 121 | uint16_t dataRead = 0; // Number of bytes read from the input to a send buffer 122 | 123 | // TLS specific variables 124 | 125 | uint8_t fingerprint[20]; // SHA-1 certificate fingerprint for TLS connections 126 | bool fingerprintValid; 127 | BearSSL::X509List *CAcert; // CA certificates for TLS validation 128 | size_t maximumCertificates; 129 | 130 | char *PemCertificate = nullptr; // Buffer for loading a certificate 131 | uint16_t PemCertificatePos; // Position in buffer while loading 132 | uint16_t PemCertificateCount; // Number of chars read 133 | 134 | /* 135 | * Global settings 136 | */ 137 | bool gsEchoEnabled = true; // command ATE 138 | uint8_t gsCipMux = 0; // command AT+CIPMUX 139 | uint8_t gsCipdInfo = 0; // command AT+CIPDINFO 140 | uint8_t gsCwDhcp = CWDHCP_AP | CWDHCP_STA; // command AT+CWDHCP 141 | bool gsFlag_Connecting = false; // Connecting in progress 142 | bool gsFlag_Busy = false; // Command is busy other commands will be ignored 143 | int8_t gsLinkIdReading = -1; // Link id where the data is read 144 | bool gsCertLoading = false; // AT+CIPSSLCERT in progress 145 | bool gsWasConnected = false; // Connection flag for AT+CIPSTATUS 146 | bool gsEthConnected = false; // track eth state for +ETH_ messages 147 | IPAddress gsEthLastIP; // for +ETH_GOT_IP message 148 | uint8_t gsCipSslAuth = 0; // command AT+CIPSSLAUTH: 0 = none, 1 = fingerprint, 2 = certificate chain 149 | uint8_t gsCipRecvMode = 0; // command AT+CIPRECVMODE 150 | ipConfig_t gsCipStaCfg = {0, 0, 0}; // command AT+CIPSTA 151 | dnsConfig_t gsCipDnsCfg = {0, 0}; // command AT+CIPDNS 152 | ipConfig_t gsCipApCfg = {0, 0, 0}; // command AT+CIPAP 153 | ipConfig_t gsCipEthCfg = {0, 0, 0}; // command AT+CIPETH 154 | uint8_t gsCipEthMAC[6] = {0, 0, 0, 0, 0, 0}; // command AT+CIPETHMAC 155 | uint16_t gsCipSslSize = 16384; // command AT+CIPSSLSIZE 156 | bool gsSTNPEnabled = true; // command AT+CIPSNTPCFG 157 | int8_t gsSTNPTimezone = 0; // command AT+CIPSNTPCFG 158 | String gsSNTPServer[3]; // command AT+CIPSNTPCFG 159 | uint8_t gsServersMaxConn = 5; // command AT+CIPSERVERMAXCONN 160 | uint32_t gsServerConnTimeout = 180000; // command AT+CIPSSTO 161 | 162 | /* 163 | * Local prototypes 164 | */ 165 | static bool checkCertificateDuplicatesAndLoad(BearSSL::X509List &importCertList); 166 | 167 | /* 168 | * The setup function is called once at startup of the sketch 169 | */ 170 | void setup() 171 | { 172 | // Fixes the problem with the core 3.x and autoconnect 173 | // For core pre 3.x please comment the next line out otherwise the compilation will fail 174 | enableWiFiAtBootTime(); 175 | 176 | // Default static net configuration 177 | gsCipStaCfg = Settings::getNetConfig(); 178 | 179 | // Default DNS configuration 180 | gsCipDnsCfg = Settings::getDnsConfig(); 181 | 182 | // Default DHCP configuration 183 | gsCwDhcp = Settings::getDhcpMode(); 184 | 185 | // Default SoftAP configuration 186 | gsCipApCfg = Settings::getApIpConfig(); 187 | 188 | // apply CIPSTA_DEF values if STA started automatically 189 | if (WiFi.getMode() != WIFI_AP) 190 | { 191 | setDns(); 192 | setDhcpMode(); 193 | } 194 | 195 | // apply CIPAP_DEF values if SoftAP started automatically 196 | if (WiFi.getMode() != WIFI_STA) 197 | { 198 | applyCipAp(); 199 | } 200 | 201 | // Default UART configuration 202 | uint32_t baudrate = Settings::getUartBaudRate(); 203 | SerialConfig config = Settings::getUartConfig(); 204 | Serial.begin(baudrate, config); 205 | 206 | #ifdef ETHERNET_CLASS 207 | SPI.begin(); 208 | SPI.setClockDivider(SPI_CLOCK_DIV4); 209 | SPI.setBitOrder(MSBFIRST); 210 | SPI.setDataMode(SPI_MODE0); 211 | 212 | gsCipEthCfg = Settings::getEthIpConfig(); 213 | configureEthernet(); 214 | #endif 215 | 216 | // Initialization of variables 217 | inputBufferCnt = 0; 218 | memset(fingerprint, 0, sizeof(fingerprint)); 219 | fingerprintValid = false; 220 | 221 | // Register event handlers. 222 | // Call "onStationConnected" each time a station connects 223 | onConnectedHandler = WiFi.onStationModeConnected(&onStationConnected); 224 | // Call "onStationModeGotIP" each time a station fully connects 225 | onGotIPHandler = WiFi.onStationModeGotIP(&onStationGotIP); 226 | // Call "onStationDisconnected" each time a station disconnects 227 | onDisconnectedHandler = WiFi.onStationModeDisconnected(&onStationDisconnected); 228 | 229 | // Set the WiFi defaults 230 | WiFi.persistent(false); 231 | WiFi.setAutoReconnect(true); 232 | 233 | // Set the SNTP defaults 234 | gsSNTPServer[0] = "pool.ntp.org"; 235 | gsSNTPServer[1] = "time.nist.gov"; 236 | gsSNTPServer[2] = ""; 237 | 238 | // Default maximum certificates 239 | maximumCertificates = Settings::getMaximumCertificates(); 240 | 241 | // Initialize certificate store 242 | CAcert = new BearSSL::X509List(); 243 | 244 | // Load certificates from LittleFS 245 | if (LittleFS.begin()) 246 | { 247 | // Open dir folder 248 | Dir dir = LittleFS.openDir("/"); 249 | 250 | // Cycle all the content 251 | while (dir.next()) 252 | { 253 | // Get filename 254 | String filename = dir.fileName(); 255 | 256 | size_t originalCertCount = CAcert->getCount(); 257 | 258 | // Check if maximum certificates has not been reached yet 259 | if (originalCertCount >= maximumCertificates) 260 | { 261 | Serial.printf_P(PSTR("\nCould not load %s. Reached the maximum of %d certificates"), filename.c_str(), maximumCertificates); 262 | Serial.printf_P(MSG_ERROR); 263 | } 264 | else 265 | { 266 | // Check if the file is in PEM format 267 | if (filename.endsWith(".pem")) 268 | { 269 | // Check if file has content 270 | if (dir.fileSize()) 271 | { 272 | File file = LittleFS.open(filename, "r"); 273 | 274 | if (!file) 275 | { 276 | Serial.printf_P("\nFailed to open file for reading"); 277 | Serial.printf_P(MSG_ERROR); 278 | return; 279 | } 280 | 281 | // Read file content 282 | String fileContent = ""; 283 | while (file.available()) 284 | { 285 | fileContent += (char)file.read(); 286 | } 287 | 288 | file.close(); 289 | 290 | // Append certificate to seperate X509List 291 | BearSSL::X509List importCertList; 292 | importCertList.append(fileContent.c_str()); 293 | 294 | if (importCertList.getCount() != 1) 295 | { 296 | Serial.printf_P(PSTR("\nFailed to add %s to the certificates list"), filename.c_str()); 297 | Serial.printf_P(MSG_ERROR); 298 | return; 299 | } 300 | 301 | if (checkCertificateDuplicatesAndLoad(importCertList)) 302 | { 303 | Serial.println(F("\nTried to load already existing certificate")); 304 | Serial.printf_P(MSG_ERROR); 305 | } 306 | else if (CAcert->getCount() == originalCertCount) 307 | { 308 | Serial.printf_P(PSTR("\nFailed to add %s to the certificates list"), filename.c_str()); 309 | Serial.printf_P(MSG_ERROR); 310 | } 311 | } 312 | else 313 | { 314 | Serial.printf_P(PSTR("\n%s is empty"), filename.c_str()); 315 | Serial.printf_P(MSG_ERROR); 316 | } 317 | } 318 | else 319 | { 320 | if (strcmp(filename.c_str(), ".gitkeep")) 321 | { 322 | Serial.printf_P(PSTR("\n%s is not a .pem file"), filename.c_str()); 323 | Serial.printf_P(MSG_ERROR); 324 | } 325 | } 326 | } 327 | } 328 | } 329 | else 330 | { 331 | Serial.printf_P("\nInizializing FS failed."); 332 | Serial.printf_P(MSG_ERROR); 333 | } 334 | 335 | Serial.println(F("\r\nready")); 336 | } 337 | 338 | /* 339 | * The loop function is called in an endless loop 340 | */ 341 | void loop() 342 | { 343 | bool lineCompleted = false; 344 | 345 | // Check for data and closed connections - only when we can transmit data 346 | 347 | if (Serial.availableForWrite()) 348 | { 349 | uint8_t maxCli = 0; // Maximum client number 350 | if (gsCipMux == 1) 351 | maxCli = 4; 352 | 353 | uint8_t freeLinkId = 255; 354 | uint8_t serversConnCount = 0; 355 | 356 | for (uint8_t i = 0; i <= maxCli; ++i) 357 | { 358 | WiFiClient *cli = clients[i].client; 359 | 360 | if (cli != nullptr) 361 | { 362 | int avail = cli->available(); 363 | 364 | if (avail > clients[i].lastAvailableBytes) // For RECVMODE it is every non zero avail 365 | { 366 | clients[i].lastActivityMillis = millis(); 367 | 368 | if (gsCipRecvMode == 0) 369 | { 370 | SendData(i, 0); 371 | } 372 | else // CIPRECVMODE = 1 373 | { 374 | clients[i].lastAvailableBytes = avail; 375 | 376 | Serial.print(F("\r\n+IPD,")); 377 | 378 | if (gsCipMux == 1) 379 | { 380 | Serial.print(i); 381 | Serial.print(','); 382 | } 383 | 384 | Serial.println(avail); 385 | } 386 | } 387 | 388 | if (cli->available() == 0 && !cli->connected()) 389 | { 390 | if (gsCipMux == 1) 391 | Serial.printf_P(PSTR("%d,"), i); 392 | 393 | Serial.println(F("CLOSED")); 394 | 395 | DeleteClient(i); 396 | cli = nullptr; 397 | } 398 | } 399 | 400 | if (cli != nullptr) 401 | { 402 | boolean isServer = false; 403 | for (uint8_t j = 0; j < SERVERS_COUNT; ++j) 404 | { 405 | if (servers[j].status() == CLOSED) 406 | continue; 407 | if (cli->localPort() == servers[j].port()) 408 | { 409 | isServer = true; 410 | break; 411 | } 412 | } 413 | if (isServer) 414 | { 415 | if (gsServerConnTimeout != 0 && cli->available() == 0 416 | && millis() - clients[i].lastActivityMillis > gsServerConnTimeout) 417 | { 418 | if (gsCipMux == 1) 419 | Serial.printf_P(PSTR("%d,"), i); 420 | Serial.println(F("CLOSED")); 421 | DeleteClient(i); 422 | } 423 | else 424 | { 425 | serversConnCount++; 426 | } 427 | } 428 | } 429 | else if (freeLinkId == 255) 430 | { 431 | freeLinkId = i; 432 | } 433 | } 434 | 435 | // handle server clients. check for a new connection only if we can add it 436 | for (uint8_t i = 0; i < SERVERS_COUNT; ++i) 437 | { 438 | if (freeLinkId == 255 || serversConnCount >= gsServersMaxConn) 439 | break; 440 | if (servers[i].status() == CLOSED) 441 | continue; 442 | // WiFiClient cli = servers[i].available(); // Use for older cores where the function accept() doesn't exist 443 | WiFiClient cli = servers[i].accept(); 444 | if (!cli) 445 | continue; 446 | clients[freeLinkId].client = new WiFiClient(cli); 447 | clients[freeLinkId].type = TYPE_TCP; 448 | clients[freeLinkId].lastAvailableBytes = 0; 449 | clients[freeLinkId].lastActivityMillis = millis(); 450 | Serial.printf_P(PSTR("%d,CONNECT\r\n"), freeLinkId); 451 | gsWasConnected = true; // Flag for CIPSTATUS command 452 | 453 | serversConnCount++; 454 | freeLinkId = 255; 455 | for (uint8_t j = 0; j <= maxCli; ++j) 456 | { 457 | if (clients[j].client == nullptr) 458 | { 459 | freeLinkId = j; 460 | break; 461 | } 462 | } 463 | } 464 | #ifdef ETHERNET_CLASS 465 | if (gsEthConnected != netif_is_up(Ethernet.getNetIf())) 466 | { 467 | gsEthConnected = netif_is_up(Ethernet.getNetIf()); 468 | if (gsEthConnected) 469 | { 470 | Serial.println(F("+ETH_CONNECTED")); 471 | } 472 | else 473 | { 474 | Serial.println(F("+ETH_DISCONNECTED")); 475 | } 476 | } 477 | if (Ethernet.localIP().isSet() && gsEthLastIP != Ethernet.localIP()) 478 | { 479 | gsEthLastIP = Ethernet.localIP(); 480 | if (gsEthLastIP.isSet()) 481 | { 482 | Serial.print(F("+ETH_GOT_IP=\"")); 483 | Serial.print(gsEthLastIP); 484 | Serial.println('"'); 485 | } 486 | } 487 | #endif 488 | } 489 | 490 | // Read the serial port into the input or send buffer 491 | int avail = Serial.available(); 492 | while (avail > 0) 493 | { 494 | // Check for EOF and errors 495 | int c = Serial.peek(); 496 | if (c < 0) 497 | break; 498 | 499 | c = Serial.read() & 0xff; 500 | 501 | if (gsLinkIdReading >= 0) 502 | { 503 | sendBuffer[dataRead] = c; 504 | 505 | if (++dataRead >= clients[gsLinkIdReading].sendLength) 506 | { 507 | Serial.printf_P(PSTR("\r\nRecv %d bytes\r\n"), clients[gsLinkIdReading].sendLength); 508 | 509 | // Send the data to the client 510 | size_t s = clients[gsLinkIdReading].client->write(sendBuffer, clients[gsLinkIdReading].sendLength); 511 | 512 | if (s == clients[gsLinkIdReading].sendLength) 513 | { 514 | Serial.println(F("\r\nSEND OK")); 515 | clients[gsLinkIdReading].lastActivityMillis = millis(); 516 | } 517 | else 518 | { 519 | Serial.println(F("\r\nSEND FAIL")); 520 | if (clients[gsLinkIdReading].client->connected()) 521 | clients[gsLinkIdReading].client->stop(); 522 | } 523 | 524 | // Stop data reading 525 | gsLinkIdReading = -1; 526 | dataRead = 0; 527 | } 528 | } 529 | else if (gsCertLoading) 530 | { 531 | ++PemCertificateCount; 532 | 533 | if (isAlphaNumeric(c) || strchr("/+= -\\\r\n", c) != nullptr) 534 | { 535 | // Check newlines - the header and footer must be separated by at least one '\n' from the base64 certificate data 536 | if (c == '\r') 537 | c = '\n'; 538 | else if (c == 'n' && PemCertificatePos > 0 && PemCertificate[PemCertificatePos - 1] == '\\') 539 | { 540 | // Create real backslash from separate characters '\' and 'n' 541 | c = '\n'; 542 | --PemCertificatePos; 543 | } 544 | 545 | // Ignore multiple newlines, save space in the buffer 546 | if (c != '\n' || (PemCertificatePos > 0 && PemCertificate[PemCertificatePos - 1] != '\n')) 547 | { 548 | PemCertificate[PemCertificatePos++] = c; 549 | 550 | // Check the end 551 | if (c == '-' && PemCertificatePos > 100) 552 | { 553 | if (!memcmp_P(PemCertificate + PemCertificatePos - 25, PSTR("-----END CERTIFICATE-----"), 25)) 554 | { 555 | Serial.printf_P(PSTR("\r\nRead %d bytes\r\n"), PemCertificateCount); 556 | 557 | // Process the certificate 558 | PemCertificate[PemCertificatePos] = '\0'; 559 | 560 | size_t originalCertCount = CAcert->getCount(); 561 | 562 | // Append certificate to seperate X509List 563 | BearSSL::X509List importCertList; 564 | importCertList.append(PemCertificate); 565 | 566 | if (importCertList.getCount() != 1) 567 | { 568 | Serial.println(F("Loading certificate failed")); 569 | Serial.printf_P(MSG_ERROR); 570 | return; 571 | } 572 | 573 | delete PemCertificate; 574 | PemCertificate = nullptr; 575 | PemCertificatePos = 0; 576 | 577 | gsCertLoading = false; 578 | 579 | if (checkCertificateDuplicatesAndLoad(importCertList)) 580 | { 581 | Serial.println(F("Tried to load already existing certificate")); 582 | Serial.printf_P(MSG_ERROR); 583 | } 584 | else if (CAcert->getCount() == (originalCertCount + 1)) 585 | { 586 | Serial.printf_P(MSG_OK); 587 | } 588 | else 589 | { 590 | Serial.println(F("no certificate")); 591 | Serial.printf_P(MSG_ERROR); 592 | } 593 | } 594 | } 595 | if (PemCertificatePos > MAX_PEM_CERT_LENGTH - 1) 596 | { 597 | gsCertLoading = false; 598 | Serial.printf_P(MSG_ERROR); // Invalid data 599 | } 600 | } 601 | } 602 | else if (c < ' ') 603 | { 604 | } 605 | else // illegal character in certificate 606 | { 607 | gsCertLoading = false; 608 | Serial.printf_P(MSG_ERROR); // Invalid data 609 | } 610 | 611 | if (!gsCertLoading) 612 | { 613 | // Read everything left before continuing 614 | while (Serial.available() > 0) 615 | { 616 | // Read the incoming byte 617 | Serial.read(); 618 | } 619 | } 620 | } 621 | else if (inputBufferCnt < INPUT_BUFFER_LEN) 622 | { 623 | if (gsEchoEnabled) 624 | Serial.write(c); 625 | 626 | /* if (inputBufferCnt == 0 && c != 'A') // Wait for 'A' as the start of the command 627 | {} 628 | else*/ 629 | // FIXME: problematic, some libraries send garbage to check if the module is alive 630 | { 631 | inputBuffer[inputBufferCnt++] = c; 632 | 633 | if (c == '\n') // LF (0x0a) 634 | { 635 | lineCompleted = true; 636 | break; 637 | } 638 | } 639 | } 640 | else 641 | { 642 | inputBufferCnt = 0; 643 | Serial.printf_P(MSG_ERROR); // Buffer overflow 644 | } 645 | 646 | yield(); 647 | } 648 | 649 | // Are we connecting now? 650 | if (gsFlag_Connecting) 651 | { 652 | station_status_t status = wifi_station_get_connect_status(); 653 | 654 | #if defined(AT_DEBUG) 655 | static station_status_t s = (station_status_t)100; 656 | 657 | if (s != status) 658 | { 659 | AT_DEBUG_PRINTF("--- status: %d\r\n", status); 660 | s = status; 661 | } 662 | #endif 663 | 664 | switch (status) 665 | { 666 | case STATION_GOT_IP: 667 | Serial.println(F("\r\nOK")); 668 | gsFlag_Connecting = false; 669 | gsFlag_Busy = false; 670 | 671 | // Set up time to allow for certificate validation 672 | if (gsSTNPEnabled && time(nullptr) < 8 * 3600 * 2) 673 | { 674 | // FIXME: Note: the time zone is not used here, it didn't work for me 675 | configTime(0, 0, nullIfEmpty(gsSNTPServer[0]), nullIfEmpty(gsSNTPServer[1]), nullIfEmpty(gsSNTPServer[2])); 676 | } 677 | 678 | // Redefine dns to the static ones 679 | if (gsCipDnsCfg.dns1 != 0) 680 | setDns(); 681 | 682 | break; 683 | 684 | case STATION_NO_AP_FOUND: 685 | Serial.println(F("\r\n+CWJAP:3\r\nFAIL")); 686 | gsFlag_Connecting = false; 687 | gsFlag_Busy = false; 688 | break; 689 | 690 | case STATION_CONNECT_FAIL: 691 | Serial.println(F("\r\n+CWJAP:4\r\nFAIL")); 692 | gsFlag_Connecting = false; 693 | gsFlag_Busy = false; 694 | break; 695 | 696 | case STATION_WRONG_PASSWORD: 697 | Serial.println(F("\r\n+CWJAP:2\r\nFAIL")); 698 | gsFlag_Connecting = false; 699 | gsFlag_Busy = false; 700 | break; 701 | 702 | // case STATION_IDLE: 703 | //return WL_IDLE_STATUS; 704 | default: 705 | break; // return WL_DISCONNECTED; 706 | } 707 | 708 | // Hack: while connecting we need the autoconnect feature to be switched on 709 | if (!gsFlag_Connecting) 710 | WiFi.setAutoReconnect(false); 711 | } 712 | 713 | // Check for a new command while connecting 714 | if (gsFlag_Busy) 715 | { 716 | // Check for busy condition 717 | if (inputBufferCnt != 0) 718 | { 719 | Serial.println(F("\r\nbusy p...")); 720 | 721 | // Discard the input buffer 722 | inputBufferCnt = 0; 723 | } 724 | } 725 | else if (lineCompleted) // Check for a new command 726 | { 727 | processCommandBuffer(); 728 | 729 | // Discard the garbage that may have come during the processing of the command 730 | while (Serial.available()) 731 | { 732 | int c = Serial.peek(); 733 | if (c < 0 || c == 'A') // we are waiting for empty serial or 'A' in AT command 734 | break; 735 | 736 | /* Note: There is a potential risk of discarding input data when the data 737 | * correctly starts with a character other than 'A'. This is the case 738 | * of AT+CIPSEND, e.g. 739 | * The risky time window opens after sending the last character of the 740 | * processed command response and lasts until the USART is empty 741 | */ 742 | Serial.read(); 743 | } 744 | } 745 | } 746 | 747 | /* 748 | * Deleted the client objects and resets the client structure 749 | */ 750 | void DeleteClient(uint8_t index) 751 | { 752 | // Check the input 753 | if (index > 4) 754 | return; 755 | 756 | client_t *cli = &(clients[index]); 757 | 758 | if (cli->client != nullptr) 759 | { 760 | delete cli->client; 761 | cli->client = nullptr; 762 | AT_DEBUG_PRINTF("--- client deleted: %d\r\n", index); 763 | } 764 | 765 | if (index == gsLinkIdReading) 766 | gsLinkIdReading = -1; 767 | 768 | cli->sendLength = 0; 769 | cli->type = TYPE_NONE; 770 | } 771 | 772 | /* 773 | * Set DHCP Mode 774 | * On stopping client DHCP, sets the network configuration 775 | */ 776 | void setDhcpMode() 777 | { 778 | if (gsCwDhcp & CWDHCP_STA) 779 | { 780 | WiFi.config(0, 0, 0); // Enable Station DHCP 781 | } 782 | else 783 | { 784 | if (gsCipStaCfg.ip != 0 && gsCipStaCfg.gw != 0 && gsCipStaCfg.mask != 0) 785 | { 786 | // Configure the network 787 | WiFi.config(gsCipStaCfg.ip, gsCipStaCfg.gw, gsCipStaCfg.mask); 788 | setDns(); 789 | } 790 | else 791 | { 792 | // Manually stop as WiFi.config performs tests on ip addresses 793 | wifi_station_dhcpc_stop(); 794 | } 795 | } 796 | } 797 | 798 | void configureEthernet() 799 | { 800 | #ifdef ETHERNET_CLASS 801 | bool begin = false; 802 | 803 | if (gsCwDhcp & CWDHCP_ETH) 804 | { 805 | Ethernet.end(); 806 | Ethernet.config(0, 0, 0); // Enable Ethernet DHCP 807 | begin = true; 808 | } 809 | else 810 | { 811 | if (gsCipEthCfg.ip != 0 && gsCipEthCfg.gw != 0 && gsCipEthCfg.mask != 0) 812 | { 813 | // Configure the network 814 | Ethernet.end(); 815 | Ethernet.config(gsCipEthCfg.ip, gsCipEthCfg.gw, gsCipEthCfg.mask); 816 | setDns(); 817 | begin = true; 818 | } 819 | else 820 | { 821 | dhcp_stop((netif*) Ethernet.getNetIf()); 822 | } 823 | } 824 | if (begin) { 825 | uint8_t* mac = nullptr; 826 | if (gsCipEthMAC[0] != 0 || memcmp(gsCipEthMAC, gsCipEthMAC + 1, 5)) { // all zeros test 827 | mac = gsCipEthMAC; 828 | } 829 | if (Ethernet.begin(mac)) { 830 | if (mac == nullptr) { 831 | Ethernet.macAddress(gsCipEthMAC); 832 | } 833 | } 834 | } 835 | #endif 836 | } 837 | 838 | /* 839 | * Set DNS servers 840 | */ 841 | void setDns() 842 | { 843 | if (gsCipDnsCfg.dns1 != 0) 844 | { 845 | dns_setserver(0, IPAddress(gsCipDnsCfg.dns1)); 846 | 847 | if (gsCipDnsCfg.dns2 != 0) 848 | dns_setserver(1, IPAddress(gsCipDnsCfg.dns2)); 849 | else 850 | dns_setserver(1, nullptr); 851 | } 852 | else 853 | { 854 | // Default DNS server 64.6.64.6 (Verisign Free DNS) 855 | dns_setserver(0, IPAddress(64, 6, 64, 6)); 856 | dns_setserver(1, nullptr); 857 | } 858 | } 859 | 860 | /* 861 | * wraps WiFi.softAPConfig, because it does a restart of the DHCP server 862 | * even if the settings didn't change. 863 | */ 864 | bool applyCipAp() 865 | { 866 | if (!gsCipApCfg.ip) 867 | return true; 868 | ip_info apInfo; 869 | wifi_get_ip_info(SOFTAP_IF, &apInfo); 870 | if (gsCipApCfg.ip == apInfo.ip.addr && gsCipApCfg.gw == apInfo.gw.addr && gsCipApCfg.mask == apInfo.netmask.addr) 871 | return true; 872 | return WiFi.softAPConfig(gsCipApCfg.ip, gsCipApCfg.gw, gsCipApCfg.mask); 873 | } 874 | 875 | /* 876 | * Send data in a +IPD message (for AT+CIPRECVMODE=0) or +CIPRECVDATA (for AT+CIPRECVMODE=1) 877 | * Returns number of bytes sent or 0 (error) 878 | */ 879 | int SendData(int clientIndex, int maxSize) 880 | { 881 | const char *respText[2] = {"+IPD", "+CIPRECVDATA"}; 882 | 883 | WiFiClient *cli = clients[clientIndex].client; 884 | int bytes; 885 | 886 | if (cli == nullptr) 887 | return 0; 888 | 889 | int avail = cli->available(); 890 | 891 | if (avail == 0) 892 | return 0; 893 | 894 | if (maxSize > 0 && maxSize < avail) 895 | avail = maxSize; 896 | 897 | uint8_t *buf = new uint8_t[avail]; 898 | 899 | if (buf != nullptr) 900 | { 901 | Serial.println(); 902 | Serial.print(respText[gsCipRecvMode]); 903 | 904 | /* FIXME: Weird behaviour of the original firmware when CIPRECVMODE=1: 905 | * It responds +CIPRECVDATA, regardless of CIPMUX setting. It doesn't 906 | * return the link id. 907 | */ 908 | if (gsCipMux == 1 && gsCipRecvMode == 0) 909 | { 910 | Serial.printf_P(PSTR(",%d"), clientIndex); 911 | } 912 | 913 | Serial.printf_P(PSTR(",%d"), avail); 914 | 915 | if (gsCipdInfo == 1 && gsCipRecvMode == 0) // No CIPDINFO for CIPRECVDATA 916 | { 917 | IPAddress ip = cli->remoteIP(); 918 | 919 | Serial.printf_P(PSTR(",%s,%d"), ip.toString().c_str(), cli->remotePort()); 920 | } 921 | 922 | Serial.print(':'); 923 | 924 | bytes = cli->readBytes(buf, avail); 925 | int bytesToSend = bytes; 926 | uint8_t *bufPtr = buf; 927 | 928 | while (bytesToSend > 0) 929 | { 930 | // Send data in chunks of 100 bytes to avoid wdt reset 931 | int txBytes = bytesToSend; 932 | if (bytesToSend > 100) 933 | txBytes = 100; 934 | 935 | // Wait for tx empty 936 | esp8266::polledTimeout::oneShot waitForTxReadyTimeout(500); 937 | 938 | while (!Serial.availableForWrite() && !waitForTxReadyTimeout) 939 | { 940 | } 941 | 942 | // In case of a timeout stop the transmission with an error 943 | if (waitForTxReadyTimeout) 944 | break; 945 | 946 | Serial.write(bufPtr, txBytes); 947 | 948 | bufPtr += txBytes; 949 | bytesToSend -= txBytes; 950 | 951 | yield(); 952 | } 953 | 954 | delete buf; 955 | 956 | if (bytes < avail) 957 | Serial.printf_P(MSG_ERROR); 958 | } 959 | else 960 | { 961 | // Out of memory 962 | if (gsCipMux == 0 || gsCipRecvMode == 1) 963 | Serial.printf_P(PSTR("\r\n%s,out of mem"), respText[gsCipRecvMode]); 964 | else 965 | Serial.printf_P(PSTR("\r\n%s,%d,out of mem"), respText[gsCipRecvMode], clientIndex); 966 | 967 | return 0; 968 | } 969 | 970 | return bytes; 971 | } 972 | 973 | /* 974 | * Returns the internal char* of the input string 975 | * In case of empty string returns nullptr 976 | */ 977 | const char *nullIfEmpty(String &s) 978 | { 979 | if (s.isEmpty()) 980 | return nullptr; 981 | 982 | return s.c_str(); 983 | } 984 | 985 | /* 986 | * Checks if the newly added certificate is a duplicate 987 | */ 988 | bool checkCertificateDuplicatesAndLoad(BearSSL::X509List &importCertList) 989 | { 990 | const br_x509_certificate *importedCert = &(importCertList.getX509Certs()[0]); 991 | 992 | for (size_t i = 0; i < CAcert->getCount(); i++) 993 | { 994 | const br_x509_certificate *cert = &(CAcert->getX509Certs()[i]); 995 | if (!memcmp(importedCert->data, cert->data, importedCert->data_len)) 996 | { 997 | return true; 998 | } 999 | } 1000 | 1001 | // Certificate is not a duplicate 1002 | CAcert->append(importedCert->data, importedCert->data_len); 1003 | 1004 | return false; 1005 | } 1006 | -------------------------------------------------------------------------------- /ESP_ATMod/command.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * command.cpp 3 | * 4 | * Part of ESP_ATMod: modified AT command processor for ESP8266 5 | * 6 | * Copyright 2020, Jiri Bilek, https://github.com/JiriBilek 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this library; if not, see . 20 | */ 21 | 22 | #include "Arduino.h" 23 | #include "ESP8266WiFi.h" 24 | #include 25 | 26 | #include "sntp.h" 27 | #include 28 | 29 | #include "ESP_ATMod.h" 30 | #include "command.h" 31 | #include "settings.h" 32 | #include "asnDecode.h" 33 | #include "debug.h" 34 | 35 | /* 36 | * Constants 37 | */ 38 | 39 | const char *suffix_CUR = "_CUR"; 40 | const char *suffix_DEF = "_DEF"; 41 | 42 | /* 43 | * Command list 44 | */ 45 | 46 | enum cmdMode_t 47 | { 48 | MODE_NO_CHECKING, // no checking 49 | MODE_EXACT_MATCH, // exact match 50 | MODE_QUERY_SET // '?' or '=' follows 51 | }; 52 | 53 | typedef struct 54 | { 55 | const char *text; 56 | const cmdMode_t mode; 57 | const commands_t cmd; 58 | } commandDef_t; 59 | 60 | static const commandDef_t commandList[] = { 61 | {"+RST", MODE_EXACT_MATCH, CMD_AT_RST}, 62 | {"+GMR", MODE_EXACT_MATCH, CMD_AT_GMR}, 63 | {"E", MODE_NO_CHECKING, CMD_ATE}, 64 | {"+RESTORE", MODE_EXACT_MATCH, CMD_AT_RESTORE}, 65 | {"+UART", MODE_QUERY_SET, CMD_AT_UART}, 66 | {"+UART_CUR", MODE_QUERY_SET, CMD_AT_UART_CUR}, 67 | {"+UART_DEF", MODE_QUERY_SET, CMD_AT_UART_DEF}, 68 | {"+SYSRAM?", MODE_EXACT_MATCH, CMD_AT_SYSRAM}, 69 | 70 | {"+CWMODE", MODE_QUERY_SET, CMD_AT_CWMODE}, 71 | {"+CWMODE_CUR", MODE_QUERY_SET, CMD_AT_CWMODE_CUR}, 72 | {"+CWMODE_DEF", MODE_QUERY_SET, CMD_AT_CWMODE_DEF}, 73 | {"+CWJAP", MODE_QUERY_SET, CMD_AT_CWJAP}, 74 | {"+CWJAP_CUR", MODE_QUERY_SET, CMD_AT_CWJAP_CUR}, 75 | {"+CWJAP_DEF", MODE_QUERY_SET, CMD_AT_CWJAP_DEF}, 76 | {"+CWLAPOPT", MODE_QUERY_SET, CMD_AT_CWLAPOPT}, 77 | {"+CWLAP", MODE_EXACT_MATCH, CMD_AT_CWLAP}, 78 | {"+CWQAP", MODE_EXACT_MATCH, CMD_AT_CWQAP}, 79 | {"+CWSAP", MODE_QUERY_SET, CMD_AT_CWSAP}, 80 | {"+CWSAP_CUR", MODE_QUERY_SET, CMD_AT_CWSAP_CUR}, 81 | {"+CWSAP_DEF", MODE_QUERY_SET, CMD_AT_CWSAP_DEF}, 82 | {"+CWDHCP", MODE_QUERY_SET, CMD_AT_CWDHCP}, 83 | {"+CWDHCP_CUR", MODE_QUERY_SET, CMD_AT_CWDHCP_CUR}, 84 | {"+CWDHCP_DEF", MODE_QUERY_SET, CMD_AT_CWDHCP_DEF}, 85 | {"+CWAUTOCONN", MODE_QUERY_SET, CMD_AT_CWAUTOCONN}, 86 | {"+CIPSTAMAC", MODE_QUERY_SET, CMD_AT_CIPSTAMAC}, 87 | {"+CIPSTAMAC_CUR", MODE_QUERY_SET, CMD_AT_CIPSTAMAC_CUR}, 88 | {"+CIPSTAMAC_DEF", MODE_QUERY_SET, CMD_AT_CIPSTAMAC_DEF}, 89 | {"+CIPAPMAC", MODE_QUERY_SET, CMD_AT_CIPAPMAC}, 90 | {"+CIPAPMAC_CUR", MODE_QUERY_SET, CMD_AT_CIPAPMAC_CUR}, 91 | {"+CIPAPMAC_DEF", MODE_QUERY_SET, CMD_AT_CIPAPMAC_DEF}, 92 | {"+CIPSTA", MODE_QUERY_SET, CMD_AT_CIPSTA}, 93 | {"+CIPSTA_CUR", MODE_QUERY_SET, CMD_AT_CIPSTA_CUR}, 94 | {"+CIPSTA_DEF", MODE_QUERY_SET, CMD_AT_CIPSTA_DEF}, 95 | {"+CIPAP", MODE_QUERY_SET, CMD_AT_CIPAP}, 96 | {"+CIPAP_CUR", MODE_QUERY_SET, CMD_AT_CIPAP_CUR}, 97 | {"+CIPAP_DEF", MODE_QUERY_SET, CMD_AT_CIPAP_DEF}, 98 | {"+CWHOSTNAME", MODE_QUERY_SET, CMD_AT_CWHOSTNAME}, 99 | #ifdef ETHERNET_CLASS 100 | {"+CIPETHMAC", MODE_QUERY_SET, CMD_AT_CIPETHMAC}, 101 | {"+CIPETHMAC_CUR", MODE_QUERY_SET, CMD_AT_CIPETHMAC_CUR}, 102 | {"+CIPETHMAC_DEF", MODE_QUERY_SET, CMD_AT_CIPETHMAC_DEF}, 103 | {"+CIPETH", MODE_QUERY_SET, CMD_AT_CIPETH}, 104 | {"+CIPETH_CUR", MODE_QUERY_SET, CMD_AT_CIPETH_CUR}, 105 | {"+CIPETH_DEF", MODE_QUERY_SET, CMD_AT_CIPETH_DEF}, 106 | {"+CEHOSTNAME", MODE_QUERY_SET, CMD_AT_CEHOSTNAME}, 107 | #endif 108 | 109 | {"+CIPSTATUS", MODE_EXACT_MATCH, CMD_AT_CIPSTATUS}, 110 | {"+CIPDOMAIN", MODE_NO_CHECKING, CMD_AT_CIPDOMAIN}, 111 | {"+CIPSTART", MODE_NO_CHECKING, CMD_AT_CIPSTART}, 112 | {"+CIPSSLSIZE", MODE_QUERY_SET, CMD_AT_CIPSSLSIZE}, 113 | {"+CIPSEND", MODE_NO_CHECKING, CMD_AT_CIPSEND}, 114 | {"+CIPCLOSEMODE", MODE_NO_CHECKING, CMD_AT_CIPCLOSEMODE}, 115 | {"+CIPCLOSE", MODE_NO_CHECKING, CMD_AT_CIPCLOSE}, 116 | {"+CIFSR", MODE_EXACT_MATCH, CMD_AT_CIFSR}, 117 | {"+CIPMUX", MODE_QUERY_SET, CMD_AT_CIPMUX}, 118 | {"+CIPDINFO", MODE_QUERY_SET, CMD_AT_CIPDINFO}, 119 | {"+CIPSERVER", MODE_NO_CHECKING, CMD_AT_CIPSERVER}, 120 | {"+CIPSERVERMAXCONN", MODE_QUERY_SET, CMD_AT_CIPSERVERMAXCONN}, 121 | {"+CIPSTO", MODE_QUERY_SET, CMD_AT_CIPSTO}, 122 | {"+CIPRECVMODE", MODE_QUERY_SET, CMD_AT_CIPRECVMODE}, 123 | {"+CIPRECVDATA", MODE_QUERY_SET, CMD_AT_CIPRECVDATA}, 124 | {"+CIPRECVLEN", MODE_QUERY_SET, CMD_AT_CIPRECVLEN}, 125 | {"+CIPSNTPCFG", MODE_QUERY_SET, CMD_AT_CIPSNTPCFG}, 126 | {"+CIPSNTPTIME?", MODE_EXACT_MATCH, CMD_AT_CIPSNTPTIME}, 127 | {"+CIPDNS", MODE_QUERY_SET, CMD_AT_CIPDNS}, 128 | {"+CIPDNS_CUR", MODE_QUERY_SET, CMD_AT_CIPDNS_CUR}, 129 | {"+CIPDNS_DEF", MODE_QUERY_SET, CMD_AT_CIPDNS_DEF}, 130 | 131 | {"+SYSCPUFREQ", MODE_QUERY_SET, CMD_AT_SYSCPUFREQ}, 132 | {"+RFMODE", MODE_QUERY_SET, CMD_AT_RFMODE}, 133 | {"+CIPSSLAUTH", MODE_QUERY_SET, CMD_AT_CIPSSLAUTH}, 134 | {"+CIPSSLFP", MODE_QUERY_SET, CMD_AT_CIPSSLFP}, 135 | {"+CIPSSLCERTMAX", MODE_QUERY_SET, CMD_AT_CIPSSLCERTMAX}, 136 | {"+CIPSSLCERT", MODE_NO_CHECKING, CMD_AT_CIPSSLCERT}, 137 | {"+CIPSSLMFLN", MODE_QUERY_SET, CMD_AT_CIPSSLMFLN}, 138 | {"+CIPSSLSTA", MODE_NO_CHECKING, CMD_AT_CIPSSLSTA}, 139 | {"+SNTPTIME?", MODE_EXACT_MATCH, CMD_AT_SNTPTIME}}; 140 | 141 | /* 142 | * Static functions 143 | */ 144 | 145 | commands_t findCommand(uint8_t *input, uint16_t inpLen); 146 | String readStringFromBuffer(unsigned char *inpBuf, uint16_t &offset, bool escape, bool allowEmpty = false); 147 | bool readNumber(unsigned char *inpBuf, uint16_t &offset, uint32_t &output); 148 | bool readIpAddress(unsigned char *inpBuf, uint16_t &offset, uint32_t &output); 149 | uint8_t readHex(char c); 150 | void printCertificateName(uint8_t certNumber); 151 | int compWifiRssi(const void *elem1, const void *elem2); 152 | void printCWLAP(int networksFound); 153 | void printScanResult(int networksFound); 154 | 155 | /* 156 | * Variables 157 | */ 158 | uint32_t sort_enable = 0; 159 | uint32_t printMask = 0x7FF; 160 | int rssiFilter = -100; 161 | uint32_t authmodeMask = 0xFFFF; 162 | 163 | /* 164 | * Commands 165 | */ 166 | 167 | static void cmd_AT(); 168 | static void cmd_AT_RST(); 169 | static void cmd_AT_GMR(); 170 | static void cmd_ATE(); 171 | static void cmd_AT_RESTORE(); 172 | static void cmd_AT_UART(commands_t cmd); 173 | static void cmd_AT_SYSRAM(); 174 | 175 | static void cmd_AT_CWMODE(commands_t cmd); 176 | static void cmd_AT_CWJAP(commands_t cmd); 177 | static void cmd_AT_CWLAPOPT(); 178 | static void cmd_AT_CWLAP(); 179 | static void cmd_AT_CWQAP(); 180 | static void cmd_AT_CWSAP(commands_t cmd); 181 | static void cmd_AT_CWDHCP(commands_t cmd); 182 | static void cmd_AT_CWAUTOCONN(); 183 | static void cmd_AT_CIPXXMAC(commands_t cmd); 184 | static void cmd_AT_CIPSTA(commands_t cmd); 185 | static void cmd_AT_CIPAP(commands_t cmd); 186 | static void cmd_AT_CWHOSTNAME(); 187 | #ifdef ETHERNET_CLASS 188 | static void cmd_AT_CIPETHMAC(commands_t cmd); 189 | static void cmd_AT_CIPETH(commands_t cmd); 190 | static void cmd_AT_CEHOSTNAME(); 191 | #endif 192 | 193 | static void cmd_AT_CIPSTATUS(); 194 | static void cmd_AT_CIPDOMAIN(); 195 | static void cmd_AT_CIPSTART(); 196 | static void cmd_AT_CIPSSLSIZE(); 197 | static void cmd_AT_CIPSEND(); 198 | static void cmd_AT_CIPCLOSEMODE(); 199 | static void cmd_AT_CIPCLOSE(); 200 | static void cmd_AT_CIFSR(); 201 | static void cmd_AT_CIPMUX(); 202 | static void cmd_AT_CIPSERVER(); 203 | static void cmd_AT_CIPSERVERMAXCONN(); 204 | static void cmd_AT_CIPSTO(); 205 | static void cmd_AT_CIPDINFO(); 206 | static void cmd_AT_CIPRECVMODE(); 207 | static void cmd_AT_CIPRECVDATA(); 208 | static void cmd_AT_CIPRECVLEN(); 209 | static void cmd_AT_CIPSNTPCFG(); 210 | static void cmd_AT_CIPSNTPTIME(); 211 | static void cmd_AT_CIPDNS(commands_t cmd); 212 | 213 | static void cmd_AT_SYSCPUFREQ(); 214 | static void cmd_AT_RFMODE(); 215 | static void cmd_AT_CIPSSLAUTH(); 216 | static void cmd_AT_CIPSSLFP(); 217 | static void cmd_AT_CIPSSLCERTMAX(); 218 | static void cmd_AT_CIPSSLCERT(); 219 | static void cmd_AT_CIPSSLMFLN(); 220 | static void cmd_AT_CIPSSLSTA(); 221 | static void cmd_AT_SNTPTIME(); 222 | 223 | /* 224 | * Processes the command buffer 225 | */ 226 | void processCommandBuffer(void) 227 | { 228 | commands_t cmd = findCommand(inputBuffer, inputBufferCnt); 229 | 230 | // ------------------------------------------------------------------------------------ AT 231 | if (cmd == CMD_AT) 232 | cmd_AT(); 233 | 234 | // ------------------------------------------------------------------------------------ AT+RST 235 | else if (cmd == CMD_AT_RST) // AT+RST - soft reset 236 | cmd_AT_RST(); 237 | 238 | // ------------------------------------------------------------------------------------ AT+GMR 239 | else if (cmd == CMD_AT_GMR) // AT+GMR - firmware version 240 | cmd_AT_GMR(); 241 | 242 | // ------------------------------------------------------------------------------------ ATE 243 | else if (cmd == CMD_ATE) // ATE0, ATE1 - echo enabled / disabled 244 | cmd_ATE(); 245 | 246 | // ------------------------------------------------------------------------------------ AT+RESTORE 247 | else if (cmd == CMD_AT_RESTORE) // AT+RESTORE - Restores the Factory Default Settings 248 | cmd_AT_RESTORE(); 249 | 250 | // ------------------------------------------------------------------------------------ AT+UART 251 | else if (cmd == CMD_AT_UART || cmd == CMD_AT_UART_CUR || cmd == CMD_AT_UART_DEF) 252 | // AT+UART=baudrate,databits,stopbits,parity,flow - UART Configuration 253 | cmd_AT_UART(cmd); 254 | 255 | // ------------------------------------------------------------------------------------ AT+SYSRAM 256 | else if (cmd == CMD_AT_SYSRAM) // AT+SYSRAM? - Checks the Remaining Space of RAM 257 | cmd_AT_SYSRAM(); 258 | 259 | // ------------------------------------------------------------------------------------ AT+CWMODE 260 | else if (cmd == CMD_AT_CWMODE || cmd == CMD_AT_CWMODE_CUR || cmd == CMD_AT_CWMODE_DEF) 261 | // AT+CWMODE - Sets the Current Wi-Fi mode 262 | cmd_AT_CWMODE(cmd); 263 | 264 | // ------------------------------------------------------------------------------------ AT+CWJAP 265 | else if (cmd == CMD_AT_CWJAP || cmd == CMD_AT_CWJAP_CUR || cmd == CMD_AT_CWJAP_DEF) 266 | // AT+CWJAP="ssid","pwd" [,"bssid"] - Connects to an AP, only ssid, pwd and bssid supported 267 | cmd_AT_CWJAP(cmd); 268 | 269 | // ------------------------------------------------------------------------------------ AT+CWLAPOPT 270 | else if (cmd == CMD_AT_CWLAPOPT) // AT+CWLAPOPT - Set the configuration for the command AT+CWLAP. 271 | cmd_AT_CWLAPOPT(); 272 | 273 | // ------------------------------------------------------------------------------------ AT+CWLAP 274 | else if (cmd == CMD_AT_CWLAP) // AT+CWLAP - List available APs. 275 | cmd_AT_CWLAP(); 276 | 277 | // ------------------------------------------------------------------------------------ AT+CWQAP 278 | else if (cmd == CMD_AT_CWQAP) // AT+CWQAP - Disconnects from the AP 279 | cmd_AT_CWQAP(); 280 | 281 | // ------------------------------------------------------------------------------------ AT+CWSAP 282 | else if (cmd == CMD_AT_CWSAP || cmd == CMD_AT_CWSAP_CUR || cmd == CMD_AT_CWSAP_DEF) 283 | // AT+CWSAP="ssid","pwd",chl,ecn [,max conm, ssid hidden] - Function: to configure the SoftA 284 | cmd_AT_CWSAP(cmd); 285 | 286 | // ------------------------------------------------------------------------------------ AT+CWDHCP 287 | else if (cmd == CMD_AT_CWDHCP || cmd == CMD_AT_CWDHCP_CUR || cmd == CMD_AT_CWDHCP_DEF) 288 | // AT+CWDHCP=x,y - Enables/Disables DHCP 289 | cmd_AT_CWDHCP(cmd); 290 | 291 | // ------------------------------------------------------------------------------------ AT+CWAUTOCONN 292 | else if (cmd == CMD_AT_CWAUTOCONN) // AT+CWAUTOCONN - auto connect to AP 293 | cmd_AT_CWAUTOCONN(); 294 | 295 | // ------------------------------------------------------------------------------------ AT+CIPSTAMAC 296 | else if (cmd == CMD_AT_CIPSTAMAC || cmd == CMD_AT_CIPSTAMAC_CUR || cmd == CMD_AT_CIPSTAMAC_DEF) 297 | // AT+CIPSTAMAC - Sets or prints the MAC Address of the ESP8266 Station 298 | cmd_AT_CIPXXMAC(cmd); 299 | 300 | // ------------------------------------------------------------------------------------ AT+CIPAPMAC 301 | else if (cmd == CMD_AT_CIPAPMAC || cmd == CMD_AT_CIPAPMAC_CUR || cmd == CMD_AT_CIPAPMAC_DEF) 302 | // AT+CIPAPMAC - Sets or prints the MAC Address of the ESP8266 SoftAP 303 | cmd_AT_CIPXXMAC(cmd); 304 | 305 | // ------------------------------------------------------------------------------------ AT+CIPSTA 306 | else if (cmd == CMD_AT_CIPSTA || cmd == CMD_AT_CIPSTA_CUR || cmd == CMD_AT_CIPSTA_DEF) 307 | // AT+CIPSTA - Sets or prints the network configuration 308 | cmd_AT_CIPSTA(cmd); 309 | 310 | // ------------------------------------------------------------------------------------ AT+CIPAP 311 | else if (cmd == CMD_AT_CIPAP || cmd == CMD_AT_CIPAP_CUR || cmd == CMD_AT_CIPAP_DEF) 312 | // AT+CIPAP - Sets or prints the SoftAP configuration 313 | cmd_AT_CIPAP(cmd); 314 | 315 | // ------------------------------------------------------------------------------------ AT+CWHOSTNAME 316 | else if (cmd == CMD_AT_CWHOSTNAME) 317 | // AT+CWHOSTNAME - Query/Set the host name of an ESP station 318 | cmd_AT_CWHOSTNAME(); 319 | 320 | // ------------------------------------------------------------------------------------ AT+CIPSTATUS 321 | else if (cmd == CMD_AT_CIPSTATUS) // AT+CIPSTATUS - Gets the Connection Status 322 | cmd_AT_CIPSTATUS(); 323 | 324 | // ------------------------------------------------------------------------------------ AT+CIPDOMAIN 325 | else if (cmd == CMD_AT_CIPDOMAIN) // AT+CIPDOMAIN - resolves a hostname to IP address with DNS 326 | cmd_AT_CIPDOMAIN(); 327 | 328 | #ifdef ETHERNET_CLASS 329 | // ------------------------------------------------------------------------------------ AT+CIPETHMAC 330 | else if (cmd == CMD_AT_CIPETHMAC || cmd == CMD_AT_CIPETHMAC_CUR || cmd == CMD_AT_CIPETHMAC_DEF) 331 | // AT+CIPETHMAC - Sets or prints the MAC Address of the Ethernet interace 332 | cmd_AT_CIPETHMAC(cmd); 333 | 334 | // ------------------------------------------------------------------------------------ AT+CIPETH 335 | else if (cmd == CMD_AT_CIPETH || cmd == CMD_AT_CIPETH_CUR || cmd == CMD_AT_CIPETH_DEF) 336 | // AT+CIPETH - Sets or prints the Ethernet configuration 337 | cmd_AT_CIPETH(cmd); 338 | 339 | // ------------------------------------------------------------------------------------ AT+CEHOSTNAME 340 | else if (cmd == CMD_AT_CEHOSTNAME) 341 | // AT+CEHOSTNAME - Query/Set the host name of the Ethernet interface 342 | cmd_AT_CEHOSTNAME(); 343 | #endif 344 | 345 | // ------------------------------------------------------------------------------------ AT+CIPSTART 346 | else if (cmd == CMD_AT_CIPSTART) 347 | // AT+CIPSTART - Establishes TCP Connection, UDP Transmission or SSL Connection 348 | cmd_AT_CIPSTART(); 349 | 350 | // ------------------------------------------------------------------------------------ AT+CIPSSLSIZE 351 | else if (cmd == CMD_AT_CIPSSLSIZE) 352 | // AT+CIPSSLSIZE - Sets the Size of SSL Buffer - the command is parsed but ignored 353 | cmd_AT_CIPSSLSIZE(); 354 | 355 | // ------------------------------------------------------------------------------------ AT+CIPSEND 356 | else if (cmd == CMD_AT_CIPSEND) // AT+CIPSEND - Sends Data 357 | cmd_AT_CIPSEND(); 358 | 359 | // ------------------------------------------------------------------------------------ AT+CIPCLOSE 360 | else if (cmd == CMD_AT_CIPCLOSEMODE) // AT+CIPCLOSEMODE - Defines the closing mode - parsed but ignored for now 361 | cmd_AT_CIPCLOSEMODE(); 362 | 363 | // ------------------------------------------------------------------------------------ AT+CIPCLOSE 364 | else if (cmd == CMD_AT_CIPCLOSE) // AT+CIPCLOSE - Closes the TCP/UDP/SSL Connection 365 | cmd_AT_CIPCLOSE(); 366 | 367 | // ------------------------------------------------------------------------------------ AT+CIFSR 368 | else if (cmd == CMD_AT_CIFSR) // AT+CIFSR - Gets the Local IP Address 369 | cmd_AT_CIFSR(); 370 | 371 | // ------------------------------------------------------------------------------------ AT+CIPMUX 372 | else if (cmd == CMD_AT_CIPMUX) // AT+CIPMUX - Enable or Disable Multiple Connections 373 | cmd_AT_CIPMUX(); 374 | 375 | // ------------------------------------------------------------------------------------ AT+CIPDINFO 376 | else if (cmd == CMD_AT_CIPDINFO) // AT+CIPDINFO - Shows the Remote IP and Port with +IPD 377 | cmd_AT_CIPDINFO(); 378 | 379 | // ------------------------------------------------------------------------------------ AT+CIPSERVER 380 | else if (cmd == CMD_AT_CIPSERVER) // AT+CIPCIPSERVER - Deletes/Creates TCP Server 381 | cmd_AT_CIPSERVER(); 382 | 383 | // ------------------------------------------------------------------------------------ AT+CIPSERVERMAXCONN 384 | else if (cmd == CMD_AT_CIPSERVERMAXCONN) // AT+CIPSERVERMAXCONN - Set the Maximum Connections Allowed by Server 385 | cmd_AT_CIPSERVERMAXCONN(); 386 | 387 | // ------------------------------------------------------------------------------------ AT+CIPSTO 388 | else if (cmd == CMD_AT_CIPSTO) // AT+CIPSTO - Sets the TCP Server Timeout 389 | cmd_AT_CIPSTO(); 390 | 391 | // ------------------------------------------------------------------------------------ AT+CIPRECVMODE 392 | else if (cmd == CMD_AT_CIPRECVMODE) // AT+CIPRECVMODE - Set TCP Receive Mode 393 | cmd_AT_CIPRECVMODE(); 394 | 395 | // ------------------------------------------------------------------------------------ AT+CIPRECVDATA 396 | else if (cmd == CMD_AT_CIPRECVDATA) // AT+CIPRECVDATA - Get TCP Data in Passive Receive Mode 397 | cmd_AT_CIPRECVDATA(); 398 | 399 | // ------------------------------------------------------------------------------------ AT+CIPRECVLEN 400 | else if (cmd == CMD_AT_CIPRECVLEN) // AT+CIPRECVLEN - Get TCP Data Length in Passive Receive Mode 401 | cmd_AT_CIPRECVLEN(); 402 | 403 | // ------------------------------------------------------------------------------------ AT+CIPSNTPCFG 404 | else if (cmd == CMD_AT_CIPSNTPCFG) // AT+CIPSNTPCFG - configure SNTP time 405 | cmd_AT_CIPSNTPCFG(); 406 | 407 | // ------------------------------------------------------------------------------------ AT+CIPSNTPTIME? 408 | else if (cmd == CMD_AT_CIPSNTPTIME) // AT+CIPSNTPTIME? - get time in asctime format 409 | cmd_AT_CIPSNTPTIME(); 410 | 411 | // ------------------------------------------------------------------------------------ AT+CIPDNS 412 | else if (cmd == CMD_AT_CIPDNS || cmd == CMD_AT_CIPDNS_CUR || cmd == CMD_AT_CIPDNS_DEF) 413 | // AT+CIPDNS - Sets User-defined DNS Servers 414 | cmd_AT_CIPDNS(cmd); 415 | 416 | // ------------------------------------------------------------------------------------ AT+SYSCPUFREQ 417 | else if (cmd == CMD_AT_SYSCPUFREQ) // AT+SYSCPUFREQ - Set or Get the Current CPU Frequency 418 | cmd_AT_SYSCPUFREQ(); 419 | 420 | // ------------------------------------------------------------------------------------ AT+RFMODE 421 | else if (cmd == CMD_AT_RFMODE) // AT+RFMODE - Sets or queries current RF mode (custom command) 422 | cmd_AT_RFMODE(); 423 | 424 | // ------------------------------------------------------------------------------------ AT+CIPSSLAUTH 425 | else if (cmd == CMD_AT_CIPSSLAUTH) // AT+CIPSSLAUTH - Authentication type 426 | cmd_AT_CIPSSLAUTH(); 427 | 428 | // ------------------------------------------------------------------------------------ AT+CIPSSLFP 429 | else if (cmd == CMD_AT_CIPSSLFP) // AT+CIPSSLFP - Shows or stores certificate fingerprint 430 | cmd_AT_CIPSSLFP(); 431 | 432 | // ------------------------------------------------------------------------------------ AT+CIPSSLCERTMAX 433 | else if (cmd == CMD_AT_CIPSSLCERTMAX) // AT+CIPSSLCERTMAX - Get or set the maximum certificate amount 434 | cmd_AT_CIPSSLCERTMAX(); 435 | 436 | // ------------------------------------------------------------------------------------ AT+CIPSSLCERT 437 | else if (cmd == CMD_AT_CIPSSLCERT) // AT+CIPSSLCERT - Load CA certificate in PEM format 438 | cmd_AT_CIPSSLCERT(); 439 | 440 | // ------------------------------------------------------------------------------------ AT+CIPSSLMFLN 441 | else if (cmd == CMD_AT_CIPSSLMFLN) // AT+CIPSSLMFLN - Check the capability of MFLN for a site 442 | cmd_AT_CIPSSLMFLN(); 443 | 444 | // ------------------------------------------------------------------------------------ AT+CIPSSLSTA 445 | else if (cmd == CMD_AT_CIPSSLSTA) // AT+CIPSSLSTA - Check the MFLN status for a connection 446 | cmd_AT_CIPSSLSTA(); 447 | 448 | // ------------------------------------------------------------------------------------ AT+SNTPTIME? 449 | else if (cmd == CMD_AT_SNTPTIME) // AT+SNTPTIME? - get time 450 | cmd_AT_SNTPTIME(); 451 | 452 | else 453 | { 454 | Serial.printf_P(MSG_ERROR); 455 | } 456 | 457 | // Clear the buffer 458 | inputBufferCnt = 0; 459 | } 460 | 461 | /**************************************************************************************** 462 | * Commands 463 | */ 464 | 465 | /* 466 | * AT 467 | */ 468 | void cmd_AT() 469 | { 470 | Serial.printf_P(MSG_OK); 471 | } 472 | 473 | /* 474 | * AT+RST - soft reset 475 | */ 476 | void cmd_AT_RST() 477 | { 478 | Serial.printf_P(MSG_OK); 479 | Serial.flush(); 480 | 481 | ESP.reset(); 482 | } 483 | 484 | /* 485 | * AT+GMR - firmware version 486 | */ 487 | void cmd_AT_GMR() 488 | { 489 | Serial.println(F("AT version:1.7.0.0 (partial)")); 490 | Serial.printf_P(PSTR("SDK version:%s\r\n"), system_get_sdk_version()); 491 | Serial.printf_P(PSTR("Compile time:%s %s\r\n"), __DATE__, __TIME__); 492 | Serial.printf_P(PSTR("Version ESP_ATMod:%s\r\n"), APP_VERSION); 493 | 494 | /* The Arduino code version is based on file core_version.h 495 | * This file is by default unusable (version number 0) but can be (and maybe is) populated 496 | * while installing the git version (file build_boards_manager_package.sh) 497 | * 498 | * If the file has the default contents, you can recreate it by git command. The idea is as follows, 499 | * for windows you will have to create the file in a text editor and insert the git results manually: 500 | * 501 | * echo #define ARDUINO_ESP8266_GIT_VER 0x`git rev-parse --short=8 HEAD` > core_version.h 502 | * echo #define ARDUINO_ESP8266_GIT_VER `git describe --tags` >> core_version.h 503 | */ 504 | #if (ARDUINO_ESP8266_GIT_VER != 0) 505 | { 506 | Serial.printf_P(PSTR("Arduino core version:%s\r\n"), __STR(ARDUINO_ESP8266_GIT_DESC)); 507 | } 508 | #endif 509 | 510 | Serial.println(F("OK")); 511 | } 512 | 513 | /* 514 | * ATE0, ATE1 - echo enabled / disabled 515 | */ 516 | void cmd_ATE() 517 | { 518 | uint32_t echo; 519 | uint16_t offset = 3; 520 | 521 | if (!readNumber(inputBuffer, offset, echo) || echo > 1 || inputBufferCnt != offset + 2) 522 | Serial.printf_P(MSG_ERROR); 523 | else 524 | { 525 | gsEchoEnabled = echo; 526 | Serial.printf_P(MSG_OK); 527 | } 528 | } 529 | 530 | /* 531 | * AT+RESTORE - Restores the Factory Default Settings 532 | */ 533 | void cmd_AT_RESTORE() 534 | { 535 | Serial.printf_P(MSG_OK); 536 | 537 | // Reset the EEPROM configuration 538 | Settings::reset(); 539 | 540 | ESP.reset(); 541 | } 542 | 543 | /* 544 | * AT+UART=baudrate,databits,stopbits,parity,flow - UART Configuration 545 | */ 546 | void cmd_AT_UART(commands_t cmd) 547 | { 548 | uint16_t offset = 7; 549 | if (cmd == CMD_AT_UART_CUR || cmd == CMD_AT_UART_DEF) 550 | offset += 4; 551 | 552 | if (inputBuffer[offset] == '?' && inputBufferCnt == offset + 3) 553 | { 554 | const char *cmdSuffix = ""; 555 | if (cmd == CMD_AT_UART_CUR) 556 | cmdSuffix = suffix_CUR; 557 | else if (cmd == CMD_AT_UART_DEF) 558 | cmdSuffix = suffix_DEF; 559 | 560 | Serial.printf_P(PSTR("+UART%s:"), cmdSuffix); 561 | 562 | /* 563 | * UART Register USC0: 564 | * UCSBN 4 //StopBits Count (2bit) 0:disable, 1:1bit, 2:1.5bit, 3:2bit 565 | * UCBN 2 //DataBits Count (2bin) 0:5bit, 1:6bit, 2:7bit, 3:8bit 566 | * UCPAE 1 //Parity Enable 567 | * UCPA 0 //Parity 0:even, 1:odd 568 | */ 569 | 570 | uint32_t uartConfig; 571 | uint32_t baudRate; 572 | 573 | if (cmd == CMD_AT_UART_DEF) 574 | { 575 | uartConfig = Settings::getUartConfig(); 576 | baudRate = Settings::getUartBaudRate(); 577 | } 578 | else 579 | { 580 | uartConfig = USC0(0); 581 | baudRate = Serial.baudRate(); 582 | } 583 | 584 | uint8_t databits = 5 + ((uartConfig >> UCBN) & 3); 585 | uint8_t stopbits = (uartConfig >> UCSBN) & 3; 586 | uint8_t parity = uartConfig & 3; 587 | 588 | Serial.printf("%d,%d,%d,%d,0\r\nOK\r\n", baudRate, databits, stopbits, parity); 589 | } 590 | else if (inputBuffer[offset] == '=') 591 | { 592 | uint8_t error = 1; 593 | 594 | ++offset; 595 | 596 | do 597 | { 598 | uint32_t baudRate; 599 | uint32_t dataBits; 600 | uint32_t stopBits; 601 | uint32_t parity; 602 | uint32_t flow; 603 | SerialConfig uartConfig; 604 | 605 | if (!readNumber(inputBuffer, offset, baudRate) || baudRate < 110 || baudRate > 921600 || inputBuffer[offset] != ',') 606 | break; 607 | 608 | ++offset; 609 | 610 | if (!readNumber(inputBuffer, offset, dataBits) || dataBits < 5 || dataBits > 8 || inputBuffer[offset] != ',') 611 | break; 612 | 613 | ++offset; 614 | 615 | if (!readNumber(inputBuffer, offset, stopBits) || stopBits < 1 || stopBits > 3 || inputBuffer[offset] != ',') 616 | break; 617 | 618 | ++offset; 619 | 620 | if (!readNumber(inputBuffer, offset, parity) || parity > 2 || inputBuffer[offset] != ',') 621 | break; 622 | 623 | ++offset; 624 | 625 | if (!readNumber(inputBuffer, offset, flow) || flow > 3 || inputBufferCnt != offset + 2) 626 | break; 627 | 628 | if (flow != 0) 629 | { 630 | Serial.println(F("NOT IMPLEMENTED")); 631 | break; 632 | } 633 | 634 | uartConfig = (SerialConfig)(((dataBits - 5) << UCBN) | (stopBits << UCSBN) | parity); 635 | 636 | AT_DEBUG_PRINTF("--- %d,%02x\r\n", baudRate, uartConfig); 637 | 638 | error = 0; 639 | 640 | // Last message at the original speed 641 | Serial.printf_P(MSG_OK); 642 | 643 | // Restart the serial interface 644 | 645 | Serial.flush(); 646 | Serial.end(); 647 | Serial.begin(baudRate, uartConfig); 648 | delay(250); // To let the line settle 649 | 650 | if (cmd != CMD_AT_UART_CUR) 651 | { 652 | Settings::setUartBaudRate(baudRate); 653 | Settings::setUartConfig(uartConfig); 654 | } 655 | 656 | } while (0); 657 | 658 | if (error == 1) 659 | Serial.printf_P(MSG_ERROR); 660 | } 661 | else 662 | { 663 | Serial.printf_P(MSG_ERROR); 664 | } 665 | } 666 | 667 | /* 668 | * AT+SYSRAM? - Checks the Remaining Space of RAM 669 | */ 670 | void cmd_AT_SYSRAM() 671 | { 672 | Serial.printf_P(PSTR("+SYSRAM:%d\r\nOK\r\n"), ESP.getFreeHeap()); 673 | } 674 | 675 | /* 676 | * AT+CWMODE - Sets the Current Wi-Fi mode (only mode 1 implemented) 677 | */ 678 | void cmd_AT_CWMODE(commands_t cmd) 679 | { 680 | uint16_t offset = 9; // offset to ? or = 681 | if (cmd != CMD_AT_CWMODE) 682 | offset += 4; 683 | 684 | if (inputBuffer[offset] == '?' && inputBufferCnt == offset + 3) 685 | { 686 | const char *cmdSuffix = ""; 687 | if (cmd == CMD_AT_CWMODE_CUR) 688 | cmdSuffix = suffix_CUR; 689 | else if (cmd == CMD_AT_CWMODE_DEF) 690 | cmdSuffix = suffix_DEF; 691 | 692 | Serial.printf_P(PSTR("+CWMODE%s:%d\r\n"), cmdSuffix, WiFi.getMode()); 693 | Serial.printf_P(MSG_OK); 694 | } 695 | else if (inputBuffer[offset] == '=') 696 | { 697 | uint32_t mode; 698 | 699 | ++offset; 700 | 701 | if (readNumber(inputBuffer, offset, mode) && mode <= 3 && inputBufferCnt == offset + 2) 702 | { 703 | if (cmd != CMD_AT_CWMODE_CUR) 704 | WiFi.persistent(true); 705 | 706 | if (WiFi.mode((WiFiMode) mode)) 707 | Serial.printf_P(MSG_OK); 708 | else 709 | Serial.printf_P(MSG_ERROR); 710 | 711 | WiFi.persistent(false); 712 | 713 | if (mode != WIFI_AP) 714 | { 715 | setDns(); 716 | setDhcpMode(); 717 | } 718 | if (mode != WIFI_STA) 719 | { 720 | applyCipAp(); 721 | } 722 | } 723 | else 724 | Serial.printf_P(MSG_ERROR); 725 | } 726 | else 727 | { 728 | Serial.printf_P(MSG_ERROR); 729 | } 730 | } 731 | 732 | /* 733 | * AT+CWJAP="ssid","pwd" [,"bssid"] - Connects to an AP, only ssid, pwd and bssid supported 734 | */ 735 | void cmd_AT_CWJAP(commands_t cmd) 736 | { 737 | if (WiFi.getMode() == WIFI_AP) 738 | { 739 | Serial.printf_P(MSG_ERROR); 740 | return; 741 | } 742 | 743 | uint16_t offset = 8; // offset to ? or = 744 | if (cmd != CMD_AT_CWJAP) 745 | offset += 4; 746 | 747 | if (inputBuffer[offset] == '?' && inputBufferCnt == offset + 3) 748 | { 749 | if (WiFi.status() != WL_CONNECTED) 750 | { 751 | Serial.println(F("No AP")); 752 | } 753 | else 754 | { 755 | struct station_config conf; 756 | 757 | if (cmd == CMD_AT_CWJAP_DEF) 758 | wifi_station_get_config_default(&conf); 759 | else 760 | wifi_station_get_config(&conf); 761 | 762 | char ssid[33]; 763 | 764 | memcpy(ssid, conf.ssid, sizeof(conf.ssid)); 765 | ssid[32] = 0; // Nullterm in case of 32 char ssid 766 | 767 | const char *cmdSuffix = ""; 768 | if (cmd == CMD_AT_CWJAP_CUR) 769 | cmdSuffix = suffix_CUR; 770 | else if (cmd == CMD_AT_CWJAP_DEF) 771 | cmdSuffix = suffix_DEF; 772 | 773 | Serial.printf_P(PSTR("+CWJAP%s:"), cmdSuffix); 774 | 775 | // +CWJAP_CUR:,,, 776 | Serial.printf_P(PSTR("\"%s\",\"%02x:%02x:%02x:%02x:%02x:%02x\",%d,%d\r\n"), ssid, 777 | conf.bssid[0], conf.bssid[1], conf.bssid[2], conf.bssid[3], conf.bssid[4], conf.bssid[5], 778 | WiFi.channel(), WiFi.RSSI()); 779 | } 780 | Serial.printf_P(MSG_OK); 781 | } 782 | else if (inputBuffer[offset] == '=') 783 | { 784 | bool error = true; 785 | 786 | do 787 | { 788 | String ssid; 789 | String pwd; 790 | String bssid; 791 | uint8_t uBssid[6]; 792 | 793 | ++offset; 794 | 795 | ssid = readStringFromBuffer(inputBuffer, offset, true); 796 | if (ssid.isEmpty() || (inputBuffer[offset] != ',')) 797 | break; 798 | 799 | ++offset; 800 | 801 | pwd = readStringFromBuffer(inputBuffer, offset, true, true); 802 | 803 | if (inputBuffer[offset] == ',') 804 | { 805 | ++offset; 806 | 807 | bssid = readStringFromBuffer(inputBuffer, offset, false); 808 | 809 | if (bssid.isEmpty() || bssid.length() != 17) 810 | break; 811 | 812 | char fmt[40]; 813 | strcpy_P(fmt, PSTR("%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx")); 814 | if (sscanf(bssid.c_str(), fmt, &uBssid[0], &uBssid[1], &uBssid[2], &uBssid[3], &uBssid[4], &uBssid[5]) != 6) 815 | break; 816 | } 817 | 818 | if (inputBufferCnt != offset + 2) 819 | break; 820 | 821 | if (cmd != CMD_AT_CWJAP_CUR) 822 | WiFi.persistent(true); 823 | 824 | // If connected, disconnect first 825 | if (WiFi.status() == WL_CONNECTED) 826 | { 827 | WiFi.disconnect(); 828 | 829 | esp8266::polledTimeout::oneShot disconnectTimeout(5000); 830 | while (WiFi.status() == WL_CONNECTED && !disconnectTimeout) 831 | { 832 | delay(50); 833 | } 834 | 835 | if (WiFi.status() == WL_CONNECTED) 836 | break; // Still connected 837 | } 838 | 839 | uint8_t *pBssid = nullptr; 840 | if (!bssid.isEmpty()) 841 | pBssid = uBssid; 842 | 843 | WiFi.begin(ssid, pwd, 0, pBssid); 844 | 845 | WiFi.persistent(false); 846 | 847 | gsFlag_Connecting = true; 848 | gsFlag_Busy = true; 849 | 850 | // Hack: while connecting we need the autoreconnect feature to be switched on 851 | // otherwise the connection fails (?) 852 | WiFi.setAutoReconnect(true); 853 | 854 | error = false; 855 | 856 | } while (0); 857 | 858 | if (error) 859 | { 860 | Serial.printf_P(MSG_ERROR); 861 | } 862 | } 863 | else 864 | { 865 | Serial.printf_P(MSG_ERROR); 866 | } 867 | } 868 | 869 | /* 870 | * AT+CWLAPOPT - Set the configuration for the command AT+CWLAP. 871 | */ 872 | void cmd_AT_CWLAPOPT() 873 | { 874 | bool error = false; 875 | 876 | if (inputBuffer[11] == '=') 877 | { 878 | uint16_t offset = 12; 879 | 880 | if (!readNumber(inputBuffer, offset, sort_enable)) 881 | { 882 | error = true; 883 | } 884 | 885 | offset++; 886 | 887 | if (!readNumber(inputBuffer, offset, printMask)) 888 | { 889 | error = true; 890 | } 891 | 892 | offset++; 893 | 894 | int signNumber = 1; 895 | if (inputBuffer[offset] == '-') 896 | { 897 | signNumber = -1; 898 | offset++; 899 | } 900 | 901 | uint32_t readRssiFilter; 902 | if (readNumber(inputBuffer, offset, readRssiFilter)) 903 | { 904 | rssiFilter = readRssiFilter * signNumber; 905 | } 906 | 907 | offset++; 908 | 909 | readNumber(inputBuffer, offset, authmodeMask); 910 | 911 | if (error) 912 | { 913 | Serial.printf_P(MSG_ERROR); 914 | } 915 | else 916 | { 917 | Serial.printf_P(MSG_OK); 918 | } 919 | } 920 | else 921 | { 922 | Serial.printf_P(MSG_ERROR); 923 | } 924 | } 925 | 926 | /* 927 | * AT+CWLAP - List available APs. 928 | */ 929 | void cmd_AT_CWLAP() 930 | { 931 | if (WiFi.getMode() == WIFI_AP) 932 | { 933 | Serial.printf_P(MSG_ERROR); 934 | return; 935 | } 936 | 937 | // Print found networks with printScanResult once the scan has finished 938 | WiFi.scanNetworksAsync(printScanResult); 939 | gsFlag_Busy = true; 940 | } 941 | 942 | /* 943 | * AT+CWQAP - Disconnects from the AP 944 | */ 945 | void cmd_AT_CWQAP() 946 | { 947 | if (WiFi.status() == WL_CONNECTED) 948 | { 949 | WiFi.disconnect(); 950 | } 951 | Serial.printf_P(MSG_OK); 952 | } 953 | 954 | /* 955 | * AT+CWSAP="ssid","pwd",chl,ecn [,max conm, ssid hidden] - Function: to configure the SoftAP 956 | */ 957 | void cmd_AT_CWSAP(commands_t cmd) 958 | { 959 | if (WiFi.getMode() == WIFI_STA) 960 | { 961 | Serial.printf_P(MSG_ERROR); 962 | return; 963 | } 964 | 965 | uint16_t offset = 8; // offset to ? or = 966 | if (cmd != CMD_AT_CWSAP) 967 | offset += 4; 968 | 969 | if (inputBuffer[offset] == '?' && inputBufferCnt == offset + 3) 970 | { 971 | 972 | struct softap_config conf; 973 | 974 | if (cmd != CMD_AT_CWSAP_CUR) 975 | wifi_softap_get_config_default(&conf); 976 | else 977 | wifi_softap_get_config(&conf); 978 | 979 | char ssid[33]; 980 | 981 | memcpy(ssid, conf.ssid, conf.ssid_len); 982 | ssid[conf.ssid_len] = 0; // Nullterm in case of 32 char ssid 983 | 984 | const char *cmdSuffix = ""; 985 | if (cmd == CMD_AT_CWSAP_CUR) 986 | cmdSuffix = suffix_CUR; 987 | else if (cmd == CMD_AT_CWSAP_DEF) 988 | cmdSuffix = suffix_DEF; 989 | 990 | Serial.printf_P(PSTR("+CWSAP%s:"), cmdSuffix); 991 | 992 | // +CWSAP_CUR:,,,,,