mqttRSSI.ino 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. ESP8266/ESP32 publish the RSSI as the WiFi signal strength to ThingSpeak channel.
  3. This example is for explaining how to use the AutoConnect library.
  4. In order to execute this example, the ThingSpeak account is needed. Sing up
  5. for New User Account and create a New Channel via My Channels.
  6. For details, please refer to the project page.
  7. https://hieromon.github.io/AutoConnect/howtoembed.html#used-with-mqtt-as-a-client-application
  8. This example is based on the environment as of March 20, 2018.
  9. Copyright (c) 2020 Hieromon Ikasamo.
  10. This software is released under the MIT License.
  11. https://opensource.org/licenses/MIT
  12. */
  13. #if defined(ARDUINO_ARCH_ESP8266)
  14. #include <ESP8266WiFi.h>
  15. #include <ESP8266HTTPClient.h>
  16. #define GET_CHIPID() (ESP.getChipId())
  17. #elif defined(ARDUINO_ARCH_ESP32)
  18. #include <WiFi.h>
  19. #include <SPIFFS.h>
  20. #include <HTTPClient.h>
  21. #define GET_CHIPID() ((uint16_t)(ESP.getEfuseMac()>>32))
  22. #endif
  23. #include <PubSubClient.h>
  24. #include <AutoConnect.h>
  25. /*
  26. AC_USE_SPIFFS indicates SPIFFS or LittleFS as available file systems that
  27. will become the AUTOCONNECT_USE_SPIFFS identifier and is exported as showng
  28. the valid file system. After including AutoConnect.h, the Sketch can determine
  29. whether to use FS.h or LittleFS.h by AUTOCONNECT_USE_SPIFFS definition.
  30. */
  31. #include <FS.h>
  32. #if defined(ARDUINO_ARCH_ESP8266)
  33. #ifdef AUTOCONNECT_USE_SPIFFS
  34. FS& FlashFS = SPIFFS;
  35. #else
  36. #include <LittleFS.h>
  37. FS& FlashFS = LittleFS;
  38. #endif
  39. #elif defined(ARDUINO_ARCH_ESP32)
  40. #include <SPIFFS.h>
  41. fs::SPIFFSFS& FlashFS = SPIFFS;
  42. #endif
  43. #define PARAM_FILE "/param.json"
  44. #define AUX_SETTING_URI "/mqtt_setting"
  45. #define AUX_SAVE_URI "/mqtt_save"
  46. #define AUX_CLEAR_URI "/mqtt_clear"
  47. // JSON definition of AutoConnectAux.
  48. // Multiple AutoConnectAux can be defined in the JSON array.
  49. // In this example, JSON is hard-coded to make it easier to understand
  50. // the AutoConnectAux API. In practice, it will be an external content
  51. // which separated from the sketch, as the mqtt_RSSI_FS example shows.
  52. static const char AUX_mqtt_setting[] PROGMEM = R"raw(
  53. [
  54. {
  55. "title": "MQTT Setting",
  56. "uri": "/mqtt_setting",
  57. "menu": true,
  58. "element": [
  59. {
  60. "name": "style",
  61. "type": "ACStyle",
  62. "value": "label+input,label+select{position:sticky;left:120px;width:230px!important;box-sizing:border-box;}"
  63. },
  64. {
  65. "name": "header",
  66. "type": "ACText",
  67. "value": "<h2>MQTT broker settings</h2>",
  68. "style": "text-align:center;color:#2f4f4f;padding:10px;"
  69. },
  70. {
  71. "name": "caption",
  72. "type": "ACText",
  73. "value": "Publishing the WiFi signal strength to MQTT channel. RSSI value of ESP8266 to the channel created on ThingSpeak",
  74. "style": "font-family:serif;color:#4682b4;"
  75. },
  76. {
  77. "name": "mqttserver",
  78. "type": "ACInput",
  79. "value": "",
  80. "label": "Server",
  81. "pattern": "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$",
  82. "placeholder": "MQTT broker server"
  83. },
  84. {
  85. "name": "channelid",
  86. "type": "ACInput",
  87. "label": "Channel ID",
  88. "pattern": "^[0-9]{6}$"
  89. },
  90. {
  91. "name": "userkey",
  92. "type": "ACInput",
  93. "label": "User Key"
  94. },
  95. {
  96. "name": "apikey",
  97. "type": "ACInput",
  98. "label": "API Key"
  99. },
  100. {
  101. "name": "newline",
  102. "type": "ACElement",
  103. "value": "<hr>"
  104. },
  105. {
  106. "name": "uniqueid",
  107. "type": "ACCheckbox",
  108. "value": "unique",
  109. "label": "Use APID unique",
  110. "checked": false
  111. },
  112. {
  113. "name": "period",
  114. "type": "ACRadio",
  115. "value": [
  116. "30 sec.",
  117. "60 sec.",
  118. "180 sec."
  119. ],
  120. "label": "Update period",
  121. "arrange": "vertical",
  122. "checked": 1
  123. },
  124. {
  125. "name": "hostname",
  126. "type": "ACInput",
  127. "value": "",
  128. "label": "ESP host name",
  129. "pattern": "^([a-zA-Z0-9]([a-zA-Z0-9-])*[a-zA-Z0-9]){1,24}$"
  130. },
  131. {
  132. "name": "save",
  133. "type": "ACSubmit",
  134. "value": "Save&amp;Start",
  135. "uri": "/mqtt_save"
  136. },
  137. {
  138. "name": "discard",
  139. "type": "ACSubmit",
  140. "value": "Discard",
  141. "uri": "/"
  142. }
  143. ]
  144. },
  145. {
  146. "title": "MQTT Setting",
  147. "uri": "/mqtt_save",
  148. "menu": false,
  149. "element": [
  150. {
  151. "name": "caption",
  152. "type": "ACText",
  153. "value": "<h4>Parameters saved as:</h4>",
  154. "style": "text-align:center;color:#2f4f4f;padding:10px;"
  155. },
  156. {
  157. "name": "parameters",
  158. "type": "ACText"
  159. },
  160. {
  161. "name": "clear",
  162. "type": "ACSubmit",
  163. "value": "Clear channel",
  164. "uri": "/mqtt_clear"
  165. }
  166. ]
  167. }
  168. ]
  169. )raw";
  170. // Adjusting WebServer class with between ESP8266 and ESP32.
  171. #if defined(ARDUINO_ARCH_ESP8266)
  172. typedef ESP8266WebServer WiFiWebServer;
  173. #elif defined(ARDUINO_ARCH_ESP32)
  174. typedef WebServer WiFiWebServer;
  175. #endif
  176. AutoConnect portal;
  177. AutoConnectConfig config;
  178. WiFiClient wifiClient;
  179. PubSubClient mqttClient(wifiClient);
  180. String serverName;
  181. String channelId;
  182. String userKey;
  183. String apiKey;
  184. String apid;
  185. String hostName;
  186. bool uniqueid;
  187. unsigned int updateInterval = 0;
  188. unsigned long lastPub = 0;
  189. #define MQTT_USER_ID "anyone"
  190. bool mqttConnect() {
  191. static const char alphanum[] = "0123456789"
  192. "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  193. "abcdefghijklmnopqrstuvwxyz"; // For random generation of client ID.
  194. char clientId[9];
  195. uint8_t retry = 3;
  196. while (!mqttClient.connected()) {
  197. if (serverName.length() <= 0)
  198. break;
  199. mqttClient.setServer(serverName.c_str(), 1883);
  200. Serial.println(String("Attempting MQTT broker:") + serverName);
  201. for (uint8_t i = 0; i < 8; i++) {
  202. clientId[i] = alphanum[random(62)];
  203. }
  204. clientId[8] = '\0';
  205. if (mqttClient.connect(clientId, MQTT_USER_ID, userKey.c_str())) {
  206. Serial.println("Established:" + String(clientId));
  207. return true;
  208. }
  209. else {
  210. Serial.println("Connection failed:" + String(mqttClient.state()));
  211. if (!--retry)
  212. break;
  213. delay(3000);
  214. }
  215. }
  216. return false;
  217. }
  218. void mqttPublish(String msg) {
  219. String path = String("channels/") + channelId + String("/publish/") + apiKey;
  220. mqttClient.publish(path.c_str(), msg.c_str());
  221. }
  222. int getStrength(uint8_t points) {
  223. uint8_t sc = points;
  224. long rssi = 0;
  225. while (sc--) {
  226. rssi += WiFi.RSSI();
  227. delay(20);
  228. }
  229. return points ? static_cast<int>(rssi / points) : 0;
  230. }
  231. void getParams(AutoConnectAux& aux) {
  232. serverName = aux["mqttserver"].value;
  233. serverName.trim();
  234. channelId = aux["channelid"].value;
  235. channelId.trim();
  236. userKey = aux["userkey"].value;
  237. userKey.trim();
  238. apiKey = aux["apikey"].value;
  239. apiKey.trim();
  240. AutoConnectRadio& period = aux["period"].as<AutoConnectRadio>();
  241. updateInterval = period.value().substring(0, 2).toInt() * 1000;
  242. uniqueid = aux["uniqueid"].as<AutoConnectCheckbox>().checked;
  243. hostName = aux["hostname"].value;
  244. hostName.trim();
  245. }
  246. // Load parameters saved with saveParams from SPIFFS into the
  247. // elements defined in /mqtt_setting JSON.
  248. String loadParams(AutoConnectAux& aux, PageArgument& args) {
  249. (void)(args);
  250. File param = FlashFS.open(PARAM_FILE, "r");
  251. if (param) {
  252. if (aux.loadElement(param)) {
  253. getParams(aux);
  254. Serial.println(PARAM_FILE " loaded");
  255. }
  256. else
  257. Serial.println(PARAM_FILE " failed to load");
  258. param.close();
  259. }
  260. else
  261. Serial.println(PARAM_FILE " open failed");
  262. return String("");
  263. }
  264. // Save the value of each element entered by '/mqtt_setting' to the
  265. // parameter file. The saveParams as below is a callback function of
  266. // /mqtt_save. When invoking this handler, the input value of each
  267. // element is already stored in '/mqtt_setting'.
  268. // In Sketch, you can output to stream its elements specified by name.
  269. String saveParams(AutoConnectAux& aux, PageArgument& args) {
  270. // The 'where()' function returns the AutoConnectAux that caused
  271. // the transition to this page.
  272. AutoConnectAux& mqtt_setting = *portal.aux(portal.where());
  273. getParams(mqtt_setting);
  274. AutoConnectInput& mqttserver = mqtt_setting["mqttserver"].as<AutoConnectInput>();
  275. // The entered value is owned by AutoConnectAux of /mqtt_setting.
  276. // To retrieve the elements of /mqtt_setting, it is necessary to get
  277. // the AutoConnectAux object of /mqtt_setting.
  278. File param = FlashFS.open(PARAM_FILE, "w");
  279. mqtt_setting.saveElement(param, { "mqttserver", "channelid", "userkey", "apikey", "uniqueid", "period", "hostname" });
  280. param.close();
  281. // Echo back saved parameters to AutoConnectAux page.
  282. AutoConnectText& echo = aux["parameters"].as<AutoConnectText>();
  283. echo.value = "Server: " + serverName;
  284. echo.value += mqttserver.isValid() ? String(" (OK)") : String(" (ERR)");
  285. echo.value += "<br>Channel ID: " + channelId + "<br>";
  286. echo.value += "User Key: " + userKey + "<br>";
  287. echo.value += "API Key: " + apiKey + "<br>";
  288. echo.value += "Update period: " + String(updateInterval / 1000) + " sec.<br>";
  289. echo.value += "Use APID unique: " + String(uniqueid == true ? "true" : "false") + "<br>";
  290. echo.value += "ESP host name: " + hostName + "<br>";
  291. return String("");
  292. }
  293. void handleRoot() {
  294. String content =
  295. "<html>"
  296. "<head>"
  297. "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
  298. "</head>"
  299. "<body>"
  300. "<iframe width=\"450\" height=\"260\" style=\"transform:scale(0.79);-o-transform:scale(0.79);-webkit-transform:scale(0.79);-moz-transform:scale(0.79);-ms-transform:scale(0.79);transform-origin:0 0;-o-transform-origin:0 0;-webkit-transform-origin:0 0;-moz-transform-origin:0 0;-ms-transform-origin:0 0;border: 1px solid #cccccc;\" src=\"https://thingspeak.com/channels/{{CHANNEL}}/charts/1?bgcolor=%23ffffff&color=%23d62020&dynamic=true&type=line\"></iframe>"
  301. "<p style=\"padding-top:5px;text-align:center\">" AUTOCONNECT_LINK(COG_24) "</p>"
  302. "</body>"
  303. "</html>";
  304. content.replace("{{CHANNEL}}", channelId);
  305. WiFiWebServer& webServer = portal.host();
  306. webServer.send(200, "text/html", content);
  307. }
  308. // Clear channel using ThingSpeak's API.
  309. void handleClearChannel() {
  310. HTTPClient httpClient;
  311. String endpoint = serverName;
  312. endpoint.replace("mqtt", "api");
  313. String delUrl = "http://" + endpoint + "/channels/" + channelId + "/feeds.json?api_key=" + userKey;
  314. Serial.print("DELETE " + delUrl);
  315. if (httpClient.begin(wifiClient, delUrl)) {
  316. Serial.print(":");
  317. int resCode = httpClient.sendRequest("DELETE");
  318. const String& res = httpClient.getString();
  319. Serial.println(String(resCode) + String(",") + res);
  320. httpClient.end();
  321. }
  322. else
  323. Serial.println(" failed");
  324. // Returns the redirect response. The page is reloaded and its contents
  325. // are updated to the state after deletion.
  326. WiFiWebServer& webServer = portal.host();
  327. webServer.sendHeader("Location", String("http://") + webServer.client().localIP().toString() + String("/"));
  328. webServer.send(302, "text/plain", "");
  329. webServer.client().flush();
  330. webServer.client().stop();
  331. }
  332. void setup() {
  333. delay(1000);
  334. Serial.begin(115200);
  335. Serial.println();
  336. #if defined(ARDUINO_ARCH_ESP8266)
  337. FlashFS.begin();
  338. #elif defined(ARDUINO_ARCH_ESP32)
  339. FlashFS.begin(true);
  340. #endif
  341. if (portal.load(FPSTR(AUX_mqtt_setting))) {
  342. AutoConnectAux& mqtt_setting = *portal.aux(AUX_SETTING_URI);
  343. PageArgument args;
  344. loadParams(mqtt_setting, args);
  345. if (uniqueid) {
  346. config.apid = String("ESP") + "-" + String(GET_CHIPID(), HEX);
  347. Serial.println("apid set to " + config.apid);
  348. }
  349. if (hostName.length()) {
  350. config.hostName = hostName;
  351. Serial.println("hostname set to " + config.hostName);
  352. }
  353. config.homeUri = "/";
  354. portal.on(AUX_SETTING_URI, loadParams);
  355. portal.on(AUX_SAVE_URI, saveParams);
  356. }
  357. else
  358. Serial.println("load error");
  359. // Reconnect and continue publishing even if WiFi is disconnected.
  360. config.autoReconnect = true;
  361. config.reconnectInterval = 1;
  362. portal.config(config);
  363. Serial.print("WiFi ");
  364. if (portal.begin()) {
  365. config.bootUri = AC_ONBOOTURI_HOME;
  366. Serial.println("connected:" + WiFi.SSID());
  367. Serial.println("IP:" + WiFi.localIP().toString());
  368. }
  369. else {
  370. Serial.println("connection failed:" + String(WiFi.status()));
  371. Serial.println("Needs WiFi connection to start publishing messages");
  372. }
  373. WiFiWebServer& webServer = portal.host();
  374. webServer.on("/", handleRoot);
  375. webServer.on(AUX_CLEAR_URI, handleClearChannel);
  376. }
  377. void loop() {
  378. if (WiFi.status() == WL_CONNECTED) {
  379. // MQTT publish control
  380. if (updateInterval > 0) {
  381. if (millis() - lastPub > updateInterval) {
  382. if (!mqttClient.connected()) {
  383. mqttConnect();
  384. }
  385. String item = String("field1=") + String(getStrength(7));
  386. mqttPublish(item);
  387. mqttClient.loop();
  388. lastPub = millis();
  389. }
  390. }
  391. }
  392. portal.handleClient();
  393. }