|  | 
|  | 1 | +/**************************************************************************************************************************** | 
|  | 2 | +  Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino - Dead simple AsyncWebServer for STM32 LAN8720 or built-in LAN8742A Ethernet | 
|  | 3 | +   | 
|  | 4 | +  For STM32 with LAN8720 (STM32F4/F7) or built-in LAN8742A Ethernet (Nucleo-144, DISCOVERY, etc) | 
|  | 5 | +   | 
|  | 6 | +  AsyncWebServer_STM32 is a library for the STM32 with LAN8720 or built-in LAN8742A Ethernet WebServer | 
|  | 7 | +   | 
|  | 8 | +  Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) | 
|  | 9 | +  Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_STM32 | 
|  | 10 | +  Licensed under MIT license | 
|  | 11 | +   | 
|  | 12 | +  Copyright (c) 2015, Majenko Technologies | 
|  | 13 | +  All rights reserved. | 
|  | 14 | +   | 
|  | 15 | +  Redistribution and use in source and binary forms, with or without modification, | 
|  | 16 | +  are permitted provided that the following conditions are met: | 
|  | 17 | +   | 
|  | 18 | +  Redistributions of source code must retain the above copyright notice, this | 
|  | 19 | +  list of conditions and the following disclaimer. | 
|  | 20 | +   | 
|  | 21 | +  Redistributions in binary form must reproduce the above copyright notice, this | 
|  | 22 | +  list of conditions and the following disclaimer in the documentation and/or | 
|  | 23 | +  other materials provided with the distribution. | 
|  | 24 | +   | 
|  | 25 | +  Neither the name of Majenko Technologies nor the names of its | 
|  | 26 | +  contributors may be used to endorse or promote products derived from | 
|  | 27 | +  this software without specific prior written permission. | 
|  | 28 | +   | 
|  | 29 | +  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | 
|  | 30 | +  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 
|  | 31 | +  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | 32 | +  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | 
|  | 33 | +  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 
|  | 34 | +  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 
|  | 35 | +  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | 
|  | 36 | +  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
|  | 37 | +  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 
|  | 38 | +  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  | 39 | + *****************************************************************************************************************************/ | 
|  | 40 | +/* | 
|  | 41 | +   Currently support | 
|  | 42 | +   1) STM32 boards with built-in Ethernet (to use USE_BUILTIN_ETHERNET = true) such as : | 
|  | 43 | +      - Nucleo-144 (F429ZI, F767ZI) | 
|  | 44 | +      - Discovery (STM32F746G-DISCOVERY) | 
|  | 45 | +      - STM32 boards (STM32F/L/H/G/WB/MP1) with 32K+ Flash, with Built-in Ethernet,  | 
|  | 46 | +      - See How To Use Built-in Ethernet at (https://github.com/khoih-prog/EthernetWebServer_STM32/issues/1) | 
|  | 47 | +   2) STM32F/L/H/G/WB/MP1 boards (with 64+K Flash) running ENC28J60 shields (to use USE_BUILTIN_ETHERNET = false) | 
|  | 48 | +   3) STM32F/L/H/G/WB/MP1 boards (with 64+K Flash) running W5x00 shields | 
|  | 49 | +   4) STM32F4 and STM32F7 boards (with 64+K Flash) running LAN8720 shields | 
|  | 50 | +*/ | 
|  | 51 | + | 
|  | 52 | +#if !( defined(STM32F0) || defined(STM32F1) || defined(STM32F2) || defined(STM32F3)  ||defined(STM32F4) || defined(STM32F7) || \ | 
|  | 53 | +       defined(STM32L0) || defined(STM32L1) || defined(STM32L4) || defined(STM32H7)  ||defined(STM32G0) || defined(STM32G4) || \ | 
|  | 54 | +       defined(STM32WB) || defined(STM32MP1) ) | 
|  | 55 | +  #error This code is designed to run on STM32F/L/H/G/WB/MP1 platform! Please check your Tools->Board setting. | 
|  | 56 | +#endif | 
|  | 57 | + | 
|  | 58 | +#define _ASYNCWEBSERVER_STM32_LOGLEVEL_       4 | 
|  | 59 | + | 
|  | 60 | +#if defined(STM32F0) | 
|  | 61 | +  #warning STM32F0 board selected | 
|  | 62 | +  #define BOARD_TYPE  "STM32F0" | 
|  | 63 | +#elif defined(STM32F1) | 
|  | 64 | +  #warning STM32F1 board selected | 
|  | 65 | +  #define BOARD_TYPE  "STM32F1" | 
|  | 66 | +#elif defined(STM32F2) | 
|  | 67 | +  #warning STM32F2 board selected | 
|  | 68 | +  #define BOARD_TYPE  "STM32F2" | 
|  | 69 | +#elif defined(STM32F3) | 
|  | 70 | +  #warning STM32F3 board selected | 
|  | 71 | +  #define BOARD_TYPE  "STM32F3" | 
|  | 72 | +#elif defined(STM32F4) | 
|  | 73 | +  #warning STM32F4 board selected | 
|  | 74 | +  #define BOARD_TYPE  "STM32F4" | 
|  | 75 | +#elif defined(STM32F7) | 
|  | 76 | +  #warning STM32F7 board selected | 
|  | 77 | +  #define BOARD_TYPE  "STM32F7" | 
|  | 78 | +#elif defined(STM32L0) | 
|  | 79 | +  #warning STM32L0 board selected | 
|  | 80 | +  #define BOARD_TYPE  "STM32L0" | 
|  | 81 | +#elif defined(STM32L1) | 
|  | 82 | +  #warning STM32L1 board selected | 
|  | 83 | +  #define BOARD_TYPE  "STM32L1" | 
|  | 84 | +#elif defined(STM32L4) | 
|  | 85 | +  #warning STM32L4 board selected | 
|  | 86 | +  #define BOARD_TYPE  "STM32L4" | 
|  | 87 | +#elif defined(STM32H7) | 
|  | 88 | +  #warning STM32H7 board selected | 
|  | 89 | +  #define BOARD_TYPE  "STM32H7" | 
|  | 90 | +#elif defined(STM32G0) | 
|  | 91 | +  #warning STM32G0 board selected | 
|  | 92 | +  #define BOARD_TYPE  "STM32G0" | 
|  | 93 | +#elif defined(STM32G4) | 
|  | 94 | +  #warning STM32G4 board selected | 
|  | 95 | +  #define BOARD_TYPE  "STM32G4" | 
|  | 96 | +#elif defined(STM32WB) | 
|  | 97 | +  #warning STM32WB board selected | 
|  | 98 | +  #define BOARD_TYPE  "STM32WB" | 
|  | 99 | +#elif defined(STM32MP1) | 
|  | 100 | +  #warning STM32MP1 board selected | 
|  | 101 | +  #define BOARD_TYPE  "STM32MP1" | 
|  | 102 | +#else | 
|  | 103 | +  #warning STM32 unknown board selected | 
|  | 104 | +  #define BOARD_TYPE  "STM32 Unknown" | 
|  | 105 | +#endif | 
|  | 106 | + | 
|  | 107 | +#ifndef BOARD_NAME | 
|  | 108 | +  #define BOARD_NAME    BOARD_TYPE | 
|  | 109 | +#endif | 
|  | 110 | + | 
|  | 111 | +#define SHIELD_TYPE     "LAN8742A built-in Ethernet" | 
|  | 112 | + | 
|  | 113 | +#include <LwIP.h> | 
|  | 114 | +#include <STM32Ethernet.h> | 
|  | 115 | +#include <AsyncWebServer_STM32.h> | 
|  | 116 | + | 
|  | 117 | +// Enter a MAC address and IP address for your controller below. | 
|  | 118 | +#define NUMBER_OF_MAC      20 | 
|  | 119 | + | 
|  | 120 | +byte mac[][NUMBER_OF_MAC] = | 
|  | 121 | +{ | 
|  | 122 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x01 }, | 
|  | 123 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x02 }, | 
|  | 124 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x03 }, | 
|  | 125 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x04 }, | 
|  | 126 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x05 }, | 
|  | 127 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x06 }, | 
|  | 128 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x07 }, | 
|  | 129 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x08 }, | 
|  | 130 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x09 }, | 
|  | 131 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0A }, | 
|  | 132 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0B }, | 
|  | 133 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0C }, | 
|  | 134 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0D }, | 
|  | 135 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0E }, | 
|  | 136 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x0F }, | 
|  | 137 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x10 }, | 
|  | 138 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x11 }, | 
|  | 139 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x12 }, | 
|  | 140 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x13 }, | 
|  | 141 | +  { 0xDE, 0xAD, 0xBE, 0xEF, 0x32, 0x14 }, | 
|  | 142 | +}; | 
|  | 143 | +// Select the IP address according to your local network | 
|  | 144 | +IPAddress ip(192, 168, 2, 232); | 
|  | 145 | + | 
|  | 146 | +// In bytes | 
|  | 147 | +#define STRING_SIZE                    40000 | 
|  | 148 | +//#define STRING_SIZE                    12000 | 
|  | 149 | + | 
|  | 150 | +AsyncWebServer    server(80); | 
|  | 151 | + | 
|  | 152 | +int reqCount = 0;                // number of requests received | 
|  | 153 | + | 
|  | 154 | +const int led = 13; | 
|  | 155 | + | 
|  | 156 | +//////////////////////////////////////////////////// | 
|  | 157 | + | 
|  | 158 | +#include <malloc.h> | 
|  | 159 | + | 
|  | 160 | +extern "C" char *sbrk(int i); | 
|  | 161 | +/* Use linker definition */ | 
|  | 162 | +extern char _estack; | 
|  | 163 | +extern char _Min_Stack_Size; | 
|  | 164 | + | 
|  | 165 | +void PrintHeapData(String hIn) | 
|  | 166 | +{ | 
|  | 167 | +  static char *ramend   = &_estack; | 
|  | 168 | +  static char *minSP    = (char*)(ramend - &_Min_Stack_Size); | 
|  | 169 | + | 
|  | 170 | +  static uint32_t maxUsedHeap = 0; | 
|  | 171 | + | 
|  | 172 | +  char *heapend = (char*)sbrk(0); | 
|  | 173 | +  char * stack_ptr = (char*)__get_MSP(); | 
|  | 174 | + | 
|  | 175 | +  struct mallinfo memoryInfoCurrent = mallinfo(); | 
|  | 176 | + | 
|  | 177 | +  uint32_t usedHeap = memoryInfoCurrent.uordblks; | 
|  | 178 | +   | 
|  | 179 | +  // Print and update only when larger heap | 
|  | 180 | +  if (usedHeap > maxUsedHeap) | 
|  | 181 | +  { | 
|  | 182 | +    maxUsedHeap = usedHeap; | 
|  | 183 | +   | 
|  | 184 | +    Serial.print("\nHEAP DATA - "); | 
|  | 185 | +    Serial.print(hIn); | 
|  | 186 | +     | 
|  | 187 | +    Serial.print("  Free RAM: "); | 
|  | 188 | +    Serial.print(((stack_ptr < minSP) ? stack_ptr : minSP) - heapend + memoryInfoCurrent.fordblks); | 
|  | 189 | +    Serial.print("  Used heap: "); | 
|  | 190 | +    Serial.println(usedHeap); | 
|  | 191 | +  } | 
|  | 192 | +} | 
|  | 193 | + | 
|  | 194 | +//////////////////////////////////////////////////// | 
|  | 195 | + | 
|  | 196 | +#define BUFFER_SIZE         768 | 
|  | 197 | +char temp[BUFFER_SIZE]; | 
|  | 198 | + | 
|  | 199 | +void handleRoot(AsyncWebServerRequest *request) | 
|  | 200 | +{ | 
|  | 201 | +  digitalWrite(led, 1); | 
|  | 202 | + | 
|  | 203 | +  int sec = millis() / 1000; | 
|  | 204 | +  int min = sec / 60; | 
|  | 205 | +  int hr = min / 60; | 
|  | 206 | +  int day = hr / 24; | 
|  | 207 | + | 
|  | 208 | +  snprintf(temp, BUFFER_SIZE - 1, | 
|  | 209 | +           "<html>\ | 
|  | 210 | +<head>\ | 
|  | 211 | +<meta http-equiv='refresh' content='5'/>\ | 
|  | 212 | +<title>AsyncWebServer-%s</title>\ | 
|  | 213 | +<style>\ | 
|  | 214 | +body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\ | 
|  | 215 | +</style>\ | 
|  | 216 | +</head>\ | 
|  | 217 | +<body>\ | 
|  | 218 | +<h2>AsyncWebServer_STM32!</h2>\ | 
|  | 219 | +<h3>running on %s</h3>\ | 
|  | 220 | +<p>Uptime: %d d %02d:%02d:%02d</p>\ | 
|  | 221 | +<img src=\"/test.svg\" />\ | 
|  | 222 | +</body>\ | 
|  | 223 | +</html>", BOARD_NAME, BOARD_NAME, day, hr % 24, min % 60, sec % 60); | 
|  | 224 | + | 
|  | 225 | +  request->send(200, "text/html", temp); | 
|  | 226 | + | 
|  | 227 | +  digitalWrite(led, 0); | 
|  | 228 | +} | 
|  | 229 | + | 
|  | 230 | +void handleNotFound(AsyncWebServerRequest *request) | 
|  | 231 | +{ | 
|  | 232 | +  digitalWrite(led, 1); | 
|  | 233 | +  String message = "File Not Found\n\n"; | 
|  | 234 | + | 
|  | 235 | +  message += "URI: "; | 
|  | 236 | +  message += request->url(); | 
|  | 237 | +  message += "\nMethod: "; | 
|  | 238 | +  message += (request->method() == HTTP_GET) ? "GET" : "POST"; | 
|  | 239 | +  message += "\nArguments: "; | 
|  | 240 | +  message += request->args(); | 
|  | 241 | +  message += "\n"; | 
|  | 242 | + | 
|  | 243 | +  for (uint8_t i = 0; i < request->args(); i++) | 
|  | 244 | +  { | 
|  | 245 | +    message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; | 
|  | 246 | +  } | 
|  | 247 | + | 
|  | 248 | +  request->send(404, "text/plain", message); | 
|  | 249 | +  digitalWrite(led, 0); | 
|  | 250 | +} | 
|  | 251 | + | 
|  | 252 | +void PrintStringSize(String & out) | 
|  | 253 | +{  | 
|  | 254 | +  static uint32_t count = 0; | 
|  | 255 | + | 
|  | 256 | +  // Print only when cStr length too large and corrupting memory or every (12 * 5) s | 
|  | 257 | +  if ( (out.length() >= STRING_SIZE) || (++count > 12) ) | 
|  | 258 | +  { | 
|  | 259 | +    Serial.print("\nOut String Length="); | 
|  | 260 | +    Serial.println(out.length()); | 
|  | 261 | + | 
|  | 262 | +    count = 0; | 
|  | 263 | +  } | 
|  | 264 | +} | 
|  | 265 | + | 
|  | 266 | +void drawGraph(AsyncWebServerRequest *request) | 
|  | 267 | +{ | 
|  | 268 | +  String out; | 
|  | 269 | + | 
|  | 270 | +  out.reserve(STRING_SIZE); | 
|  | 271 | +  char temp[80]; | 
|  | 272 | + | 
|  | 273 | +  out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"910\" height=\"150\">\n"; | 
|  | 274 | +  out += "<rect width=\"910\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"2\" stroke=\"rgb(0, 0, 0)\" />\n"; | 
|  | 275 | +  out += "<g stroke=\"blue\">\n"; | 
|  | 276 | +  int y = rand() % 130; | 
|  | 277 | + | 
|  | 278 | +  // Won't work if using 5000 because of heap memory | 
|  | 279 | +  //for (int x = 10; x < 5000; x += 10) | 
|  | 280 | +  for (int x = 10; x < 900; x += 10) | 
|  | 281 | +  { | 
|  | 282 | +    int y2 = rand() % 130; | 
|  | 283 | +    sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"2\" />\n", x, 140 - y, x + 10, 140 - y2); | 
|  | 284 | +    out += temp; | 
|  | 285 | +    y = y2; | 
|  | 286 | +  } | 
|  | 287 | +   | 
|  | 288 | +  out += "</g>\n</svg>\n"; | 
|  | 289 | + | 
|  | 290 | +  PrintHeapData("Pre Send"); | 
|  | 291 | + | 
|  | 292 | +  PrintStringSize(out); | 
|  | 293 | + | 
|  | 294 | +  request->send(200, "image/svg+xml", out); | 
|  | 295 | + | 
|  | 296 | +  PrintHeapData("Post Send"); | 
|  | 297 | +} | 
|  | 298 | + | 
|  | 299 | +void setup() | 
|  | 300 | +{ | 
|  | 301 | +  pinMode(led, OUTPUT); | 
|  | 302 | +  digitalWrite(led, 0); | 
|  | 303 | + | 
|  | 304 | +  Serial.begin(115200); | 
|  | 305 | +  while (!Serial && millis() < 5000); | 
|  | 306 | + | 
|  | 307 | +  delay(200); | 
|  | 308 | + | 
|  | 309 | +  Serial.print("\nStart Async_AdvancedWebServer_MemoryIssues_SendArduinoString on "); Serial.print(BOARD_NAME); | 
|  | 310 | +  Serial.print(" with "); Serial.println(SHIELD_TYPE); | 
|  | 311 | +  Serial.println(ASYNC_WEBSERVER_STM32_VERSION); | 
|  | 312 | + | 
|  | 313 | +  PrintHeapData("Start =>"); | 
|  | 314 | + | 
|  | 315 | +  /////////////////////////////////// | 
|  | 316 | + | 
|  | 317 | +#if (_ASYNCWEBSERVER_STM32_LOGLEVEL_ > 2) | 
|  | 318 | +  Serial.print("STM32 Core version v"); Serial.print(STM32_CORE_VERSION_MAJOR); | 
|  | 319 | +  Serial.print("."); Serial.print(STM32_CORE_VERSION_MINOR);  | 
|  | 320 | +  Serial.print("."); Serial.println(STM32_CORE_VERSION_PATCH); | 
|  | 321 | +#endif | 
|  | 322 | + | 
|  | 323 | +  // start the ethernet connection and the server | 
|  | 324 | +  // Use random mac | 
|  | 325 | +  uint16_t index = millis() % NUMBER_OF_MAC; | 
|  | 326 | + | 
|  | 327 | +  // Use Static IP | 
|  | 328 | +  //Ethernet.begin(mac[index], ip); | 
|  | 329 | +  // Use DHCP dynamic IP and random mac | 
|  | 330 | +  Ethernet.begin(mac[index]); | 
|  | 331 | + | 
|  | 332 | +  /////////////////////////////////// | 
|  | 333 | +  | 
|  | 334 | +  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) | 
|  | 335 | +  { | 
|  | 336 | +    handleRoot(request); | 
|  | 337 | +  }); | 
|  | 338 | + | 
|  | 339 | +  server.on("/test.svg", HTTP_GET, [](AsyncWebServerRequest * request) | 
|  | 340 | +  { | 
|  | 341 | +    drawGraph(request); | 
|  | 342 | +  }); | 
|  | 343 | + | 
|  | 344 | +  server.on("/inline", [](AsyncWebServerRequest * request) | 
|  | 345 | +  { | 
|  | 346 | +    request->send(200, "text/plain", "This works as well"); | 
|  | 347 | +  }); | 
|  | 348 | + | 
|  | 349 | +  server.onNotFound(handleNotFound); | 
|  | 350 | + | 
|  | 351 | +  server.begin(); | 
|  | 352 | + | 
|  | 353 | +  Serial.print(F("HTTP EthernetWebServer is @ IP : ")); | 
|  | 354 | +  Serial.println(Ethernet.localIP()); | 
|  | 355 | + | 
|  | 356 | +  PrintHeapData("Pre Create Arduino String"); | 
|  | 357 | +} | 
|  | 358 | + | 
|  | 359 | +void heartBeatPrint() | 
|  | 360 | +{ | 
|  | 361 | +  static int num = 1; | 
|  | 362 | + | 
|  | 363 | +  Serial.print(F(".")); | 
|  | 364 | + | 
|  | 365 | +  if (num == 80) | 
|  | 366 | +  { | 
|  | 367 | +    Serial.println(); | 
|  | 368 | +    num = 1; | 
|  | 369 | +  } | 
|  | 370 | +  else if (num++ % 10 == 0) | 
|  | 371 | +  { | 
|  | 372 | +    Serial.print(F(" ")); | 
|  | 373 | +  } | 
|  | 374 | +} | 
|  | 375 | + | 
|  | 376 | +void check_status() | 
|  | 377 | +{ | 
|  | 378 | +  static unsigned long checkstatus_timeout = 0; | 
|  | 379 | + | 
|  | 380 | +#define STATUS_CHECK_INTERVAL     10000L | 
|  | 381 | + | 
|  | 382 | +  // Send status report every STATUS_REPORT_INTERVAL (60) seconds: we don't need to send updates frequently if there is no status change. | 
|  | 383 | +  if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0)) | 
|  | 384 | +  { | 
|  | 385 | +    heartBeatPrint(); | 
|  | 386 | +    checkstatus_timeout = millis() + STATUS_CHECK_INTERVAL; | 
|  | 387 | +  } | 
|  | 388 | +} | 
|  | 389 | + | 
|  | 390 | +void loop() | 
|  | 391 | +{ | 
|  | 392 | +  check_status(); | 
|  | 393 | +} | 
0 commit comments