nephalim

Esp_radio

Oct 23rd, 2021 (edited)
238
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 146.00 KB | None | 0 0
  1. //******************************************************************************************
  2. //*  Esp_radio -- Webradio receiver for ESP8266, (color) display and VS1053 MP3 module,    *
  3. //*               by Ed Smallenburg (ed@smallenburg.nl)                                    *
  4. //*  With ESP8266 running at 80 MHz, it is capable of handling up to 256 kb bitrate.       *
  5. //*  With ESP8266 running at 160 MHz, it is capable of handling up to 320 kb bitrate.      *
  6. //******************************************************************************************
  7. // ESP8266 libraries used:
  8. //  - ESP8266WiFi       - Part of ESP8266 Arduino default libraries.
  9. //  - SPI               - Part of Arduino default libraries.
  10. //  - Adafruit_GFX      - https://github.com/adafruit/Adafruit-GFX-Library
  11. //  - TFT_ILI9163C      - https://github.com/sumotoy/TFT_ILI9163C
  12. //  - ESPAsyncTCP       - https://github.com/me-no-dev/ESPAsyncTCP
  13. //  - ESPAsyncWebServer - https://github.com/me-no-dev/ESPAsyncWebServer
  14. //  - FS - https://github.com/esp8266/arduino-esp8266fs-plugin/releases/download/0.2.0/ESP8266FS-0.2.0.zip
  15. //  - ArduinoOTA        - Part of ESP8266 Arduino default libraries.
  16. //  - AsyncMqttClient   - https://github.com/marvinroger/async-mqtt-client
  17. //  - TinyXML           - Fork https://github.com/adafruit/TinyXML
  18. //
  19. // A library for the VS1053 (for ESP8266) is not available (or not easy to find).  Therefore
  20. // a class for this module is derived from the maniacbug library and integrated in this sketch.
  21. //
  22. // Compiling: Set SPIFS to 3 MB.  Set IwIP variant to "V1.4 Higher Bandwidth".
  23. // See http://www.internet-radio.com for suitable stations.  Add the stations of your choice
  24. // to the .ini-file.
  25. //
  26. // Brief description of the program:
  27. // First a suitable WiFi network is found and a connection is made.
  28. // Then a connection will be made to a shoutcast server.  The server starts with some
  29. // info in the header in readable ascii, ending with a double CRLF, like:
  30. //  icy-name:Classic Rock Florida - SHE Radio
  31. //  icy-genre:Classic Rock 60s 70s 80s Oldies Miami South Florida
  32. //  icy-url:http://www.ClassicRockFLorida.com
  33. //  content-type:audio/mpeg
  34. //  icy-pub:1
  35. //  icy-metaint:32768          - Metadata after 32768 bytes of MP3-data
  36. //  icy-br:128                 - in kb/sec (for Ogg this is like "icy-br=Quality 2"
  37. //
  38. // After de double CRLF is received, the server starts sending mp3- or Ogg-data.  For mp3, this
  39. // data may contain metadata (non mp3) after every "metaint" mp3 bytes.
  40. // The metadata is empty in most cases, but if any is available the content will be presented on the TFT.
  41. // Pushing the input button causes the player to select the next preset station present in the .ini file.
  42. //
  43. // The display used is a Chinese 1.8 color TFT module 128 x 160 pixels.  The TFT_ILI9163C.h
  44. // file has been changed to reflect this particular module.  TFT_ILI9163C.cpp has been
  45. // changed to use the full screenwidth if rotated to mode "3".  Now there is room for 26
  46. // characters per line and 16 lines.  Software will work without installing the display.
  47. // If no TFT is used, you may use GPIO2 and GPIO15 as control buttons.  See definition of "USETFT" below.
  48. // Switches are than programmed as:
  49. // GPIO2 : "Goto station 1"
  50. // GPIO0 : "Next station"
  51. // GPIO15: "Previous station".  Note that GPIO15 has to be LOW when starting the ESP8266.
  52. //         The button for GPIO15 must therefore be connected to VCC (3.3V) instead of GND.
  53.  
  54. //
  55. // For configuration of the WiFi network(s): see the global data section further on.
  56. //
  57. // The SPI interface for VS1053 and TFT uses hardware SPI.
  58. //
  59. // Wiring:
  60. // NodeMCU  GPIO    Pin to program  Wired to LCD        Wired to VS1053      Wired to rest
  61. // -------  ------  --------------  ---------------     -------------------  ---------------------
  62. // D0       GPIO16  16              -                   pin 1 DCS            -
  63. // D1       GPIO5    5              -                   pin 2 CS             LED on nodeMCU
  64. // D2       GPIO4    4              -                   pin 4 DREQ           -
  65. // D3       GPIO0    0 FLASH        -                   -                    Control button "Next station"
  66. // D4       GPIO2    2              pin 3 (D/C)         -                    (OR)Control button "Station 1"
  67. // D5       GPIO14  14 SCLK         pin 5 (CLK)         pin 5 SCK            -
  68. // D6       GPIO12  12 MISO         -                   pin 7 MISO           -
  69. // D7       GPIO13  13 MOSI         pin 4 (DIN)         pin 6 MOSI           -
  70. // D8       GPIO15  15              pin 2 (CS)          -                    (OR)Control button "Previous station"
  71. // D9       GPI03    3 RXD0         -                   -                    Reserved serial input
  72. // D10      GPIO1    1 TXD0         -                   -                    Reserved serial output
  73. // -------  ------  --------------  ---------------     -------------------  ---------------------
  74. // GND      -        -              pin 8 (GND)         pin 8 GND            Power supply
  75. // VCC 3.3  -        -              pin 6 (VCC)         -                    LDO 3.3 Volt
  76. // VCC 5 V  -        -              pin 7 (BL)          pin 9 5V             Power supply
  77. // RST      -        -              pin 1 (RST)         pin 3 RESET          Reset circuit
  78. //
  79. // The reset circuit is a circuit with 2 diodes to GPIO5 and GPIO16 and a resistor to ground
  80. // (wired OR gate) because there was not a free GPIO output available for this function.
  81. // This circuit is included in the documentation.
  82. // Issues:
  83. // Webserver produces error "LmacRxBlk:1" after some time.  After that it will work very slow.
  84. // The program will reset the ESP8266 in such a case.  Now we have switched to async webserver,
  85. // the problem still exists, but the program will not crash anymore.
  86. // Upload to ESP8266 not reliable.
  87. // Define the version number, also used for webserver as Last-Modified header:
  88. #define VERSION "Tue, 23 Apr 2019 09:10:00 GMT"
  89. // TFT.  Define USETFT if required.
  90. #define USETFT
  91. #include <ESP8266WiFi.h>
  92. //#include <ESP8266FtpServer.h>
  93. #include <ESPAsyncTCP.h>
  94. #include <ESPAsyncWebServer.h>
  95. #include <AsyncMqttClient.h>
  96. #include <SPI.h>
  97. #if defined ( USETFT )
  98. #include <Adafruit_GFX.h>
  99. #include <TFT_ILI9163C.h>
  100. #endif
  101. #include <Ticker.h>
  102. #include <stdio.h>
  103. #include <string.h>
  104. #include <FS.h>
  105. #include <ArduinoOTA.h>
  106. #include <TinyXML.h>
  107.  
  108. extern "C"
  109. {
  110. #include "user_interface.h"
  111. }
  112.  
  113. // Definitions for 3 control switches on analog input
  114. // You can test the analog input values by holding down the switch and select /?analog=1
  115. // in the web interface. See schematics in the documentation.
  116. // Switches are programmed as "Goto station 1", "Next station" and "Previous station" respectively.
  117. // Set these values to 2000 if not used or tie analog input to ground.
  118. #define NUMANA  3
  119. #define asw1    2000
  120. #define asw2    2000
  121. #define asw3    2000
  122. //
  123. // Color definitions for the TFT screen (if used)
  124. #define BLACK   0x0000
  125. #define BLUE    0xF800
  126. #define RED     0x001F
  127. #define GREEN   0x07E0
  128. #define CYAN    GREEN | BLUE
  129. #define MAGENTA RED | BLUE
  130. #define YELLOW  RED | GREEN
  131. // Digital I/O used
  132. // Pins for VS1053 module
  133. #define VS1053_CS     5
  134. #define VS1053_DCS    16
  135. #define VS1053_DREQ   4
  136. // Pins CS and DC for TFT module (if used, see definition of "USETFT")
  137. #define TFT_CS 2
  138. #define TFT_DC 10
  139. // Control button (GPIO) for controlling station
  140. #define BUTTON1 2
  141. #define BUTTON2 0
  142. #define BUTTON3 15
  143. // Ringbuffer for smooth playing. 20000 bytes is 160 Kbits, about 1.5 seconds at 128kb bitrate.
  144. #define RINGBFSIZ 20000 //20000
  145. // Debug buffer size
  146. #define DEBUG_BUFFER_SIZE 100
  147. // Name of the ini file
  148. #define INIFILENAME "/radio.ini"
  149. // Access point name if connection to WiFi network fails.  Also the hostname for WiFi and OTA.
  150. // Not that the password of an AP must be at least as long as 8 characters.
  151. // Also used for other naming.
  152. #define NAME "Esp-radio"  // Maximum number of MQTT reconnects before give-up
  153. #define MAXMQTTCONNECTS 20
  154. #define THERMISTORPIN A0
  155. #define THERMISTORNOMINAL 10000
  156. #define TEMPERATURENOMINAL 25
  157. #define NUMSAMPLES 5
  158. #define BCOEFFICIENT 3950
  159. #define SERIESRESISTOR 10000
  160. int samples[NUMSAMPLES];
  161. //
  162. //******************************************************************************************
  163. // Forward declaration of various functions                                                *
  164. //******************************************************************************************
  165. //void   displayinfo ( const char* str, uint16_t pos, uint16_t height, uint16_t color ) ;
  166. void   showstreamtitle ( const char* ml, bool full = false ) ;
  167. void   handlebyte ( uint8_t b, bool force = false ) ;
  168. void   handlebyte_ch ( uint8_t b, bool force = false ) ;
  169. void   handleFS ( AsyncWebServerRequest* request ) ;
  170. void   handleFSf ( AsyncWebServerRequest* request, const String& filename ) ;
  171. void   handleCmd ( AsyncWebServerRequest* request )  ;
  172. void   handleFileUpload ( AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final ) ;
  173. char*  dbgprint( const char* format, ... ) ;
  174. char*  analyzeCmd ( const char* str ) ;
  175. char*  analyzeCmd ( const char* par, const char* val ) ;
  176. String chomp ( String str ) ;
  177. void   publishIP() ;
  178. String xmlparse ( String mount ) ;
  179. bool   connecttohost() ;
  180. //
  181. //******************************************************************************************
  182. // Global data section.                                                                    *
  183. //******************************************************************************************
  184. // There is a block ini-data that contains some configuration.  Configuration data is      *
  185. // saved in the SPIFFS file radio.ini by the webinterface.  On restart the new data will   *
  186. // be read from this file.                                                                 *
  187. // Items in ini_block can be changed by commands from webserver/MQTT/Serial.               *
  188. //******************************************************************************************
  189. struct ini_struct
  190. {
  191.   String         mqttbroker ;                              // The name of the MQTT broker server
  192.   uint16_t       mqttport ;                                // Port, default 1883
  193.   String         mqttuser ;                                // User for MQTT authentication
  194.   String         mqttpasswd ;                              // Password for MQTT authentication
  195.   String         mqtttopic ;                               // Topic to suscribe to
  196.   String         mqttpubtopic ;                            // Topic to pubtop (IP will be published)
  197.   uint8_t        reqvol ;                                  // Requested volume
  198.   uint8_t        rtone[4] ;                                // Requested bass/treble settings
  199.   int8_t         newpreset ;                               // Requested preset
  200.   String         ssid ;                                    // SSID of WiFi network to connect to
  201.   String         passwd ;                                  // Password for WiFi network
  202. } ;
  203.  
  204. enum datamode_t { INIT = 1, HEADER = 2, DATA = 4, METADATA = 8, PLAYLISTINIT = 16, PLAYLISTHEADER = 32, PLAYLISTDATA = 64, STOPREQD = 128, STOPPED = 256} ;     // State for datastream
  205.  
  206. // Global variables
  207. int              DEBUG = 1 ;
  208. ini_struct       ini_block ;                               // Holds configurable data
  209. WiFiClient       *mp3client = NULL ;                       // An instance of the mp3 client
  210. AsyncWebServer   cmdserver ( 80 ) ;                        // Instance of embedded webserver on port 80
  211. AsyncMqttClient  mqttclient ;                              // Client for MQTT subscriber
  212. IPAddress        mqtt_server_IP ;                          // IP address of MQTT broker
  213. char             cmd[130] ;                                // Command from MQTT or Serial
  214. #if defined ( USETFT )
  215. TFT_ILI9163C     tft = TFT_ILI9163C ( TFT_CS, TFT_DC ) ;
  216. #endif
  217. Ticker           tckr ;                                    // For timing 100 msec
  218. TinyXML          xml;                                      // For XML parser.
  219. uint32_t         totalcount = 0 ;                          // Counter mp3 data
  220. datamode_t       datamode ;                                // State of datastream
  221. int              metacount ;                               // Number of bytes in metadata
  222. int              datacount ;                               // Counter databytes before metadata
  223. String           metaline ;                                // Readable line in metadata
  224. String           icystreamtitle ;                          // Streamtitle from metadata
  225. String           icyname ;                                 // Icecast station name
  226. int              bitrate ;                                 // Bitrate in kb/sec
  227. int              metaint = 0 ;                             // Number of databytes between metadata
  228. int8_t           currentpreset = -1 ;                      // Preset station playing
  229. String           host ;                                    // The URL to connect to or file to play
  230. String           playlist ;                                // The URL of the specified playlist
  231. bool             xmlreq = false ;                          // Request for XML parse.
  232. bool             hostreq = false ;                         // Request for new host
  233. bool             reqtone = false ;                         // New tone setting requested
  234. bool             muteflag = false ;                        // Mute output
  235. uint8_t*         ringbuf ;                                 // Ringbuffer for VS1053
  236. uint16_t         rbwindex = 0 ;                            // Fill pointer in ringbuffer
  237. uint16_t         rbrindex = RINGBFSIZ - 1 ;                // Emptypointer in ringbuffer
  238. uint16_t         rcount = 0 ;                              // Number of bytes in ringbuffer
  239. uint16_t         analogsw[NUMANA] = { asw1, asw2, asw3 } ; // 3 levels of analog input
  240. uint16_t         analogrest ;                              // Rest value of analog input
  241. bool             resetreq = false ;                        // Request to reset the ESP8266
  242. bool             NetworkFound ;                            // True if WiFi network connected
  243. String           networks ;                                // Found networks
  244. String           actualnetwork;                             // Actual conected network
  245. String           anetworks ;                               // Aceptable networks (present in .ini file)
  246. String           presetlist ;                              // List for webserver
  247. String           stations_names[20];                       // Array of stations names
  248. uint8_t          num_an ;                                  // Number of acceptable networks in .ini file
  249. String           testfilename = "" ;                       // File to test (SPIFFS speed)
  250. uint16_t         mqttcount = 0 ;                           // Counter MAXMQTTCONNECTS
  251. int8_t           playlist_num = 0 ;                        // Nonzero for selection from playlist
  252. File             mp3file  ;                                // File containing mp3 on SPIFFS
  253. bool             localfile = false ;                       // Play from local mp3-file or not
  254. bool             chunked = false ;                         // Station provides chunked transfer
  255. int              chunkcount = 0 ;                          // Counter for chunked transfer
  256.  
  257. // XML parse globals.
  258. const char* xmlhost = "playerservices.streamtheworld.com" ;// XML data source
  259. const char* xmlget =  "GET /api/livestream"                // XML get parameters
  260.                       "?version=1.5"                       // API Version of IHeartRadio
  261.                       "&mount=%sAAC"                       // MountPoint with Station Callsign
  262.                       "&lang=en" ;                         // Language
  263. int         xmlport = 80 ;                                 // XML Port
  264. uint8_t     xmlbuffer[150] ;                               // For XML decoding
  265. String      xmlOpen ;                                      // Opening XML tag
  266. String      xmlTag ;                                       // Current XML tag
  267. String      xmlData ;                                      // Data inside tag
  268. String      stationServer( "" ) ;                          // Radio stream server
  269. String      stationPort( "" ) ;                            // Radio stream port
  270. String      stationMount( "" ) ;                           // Radio stream Callsign
  271.  
  272. //FtpServer ftpSrv;
  273. //******************************************************************************************
  274. // End of global data section.                                                             *
  275. //******************************************************************************************
  276.  
  277. //******************************************************************************************
  278. // Pages and CSS for the webinterface.                                                     *
  279. //******************************************************************************************
  280. #include "about_html.h"
  281. #include "config_html.h"
  282. #include "index_html.h"
  283. #include "radio_css.h"
  284. #include "favicon_ico.h"
  285. //
  286. //******************************************************************************************
  287. // VS1053 stuff.  Based on maniacbug library.                                              *
  288. //******************************************************************************************
  289. // VS1053 class definition.                                                                *
  290. //******************************************************************************************
  291. class VS1053
  292. {
  293.   private:
  294.     uint8_t       cs_pin ;                        // Pin where CS line is connected
  295.     uint8_t       dcs_pin ;                       // Pin where DCS line is connected
  296.     uint8_t       dreq_pin ;                      // Pin where DREQ line is connected
  297.     uint8_t       curvol ;                        // Current volume setting 0..100%
  298.     const uint8_t vs1053_chunk_size = 32 ;
  299.     // SCI Register
  300.     const uint8_t SCI_MODE          = 0x0 ;
  301.     const uint8_t SCI_BASS          = 0x2 ;
  302.     const uint8_t SCI_CLOCKF        = 0x3 ;
  303.     const uint8_t SCI_AUDATA        = 0x5 ;
  304.     const uint8_t SCI_WRAM          = 0x6 ;
  305.     const uint8_t SCI_WRAMADDR      = 0x7 ;
  306.     const uint8_t SCI_AIADDR        = 0xA ;
  307.     const uint8_t SCI_VOL           = 0xB ;
  308.     const uint8_t SCI_AICTRL0       = 0xC ;
  309.     const uint8_t SCI_AICTRL1       = 0xD ;
  310.     const uint8_t SCI_num_registers = 0xF ;
  311.     // SCI_MODE bits
  312.     const uint8_t SM_SDINEW         = 11 ;        // Bitnumber in SCI_MODE always on
  313.     const uint8_t SM_RESET          = 2 ;         // Bitnumber in SCI_MODE soft reset
  314.     const uint8_t SM_CANCEL         = 3 ;         // Bitnumber in SCI_MODE cancel song
  315.     const uint8_t SM_TESTS          = 5 ;         // Bitnumber in SCI_MODE for tests
  316.     const uint8_t SM_LINE1          = 14 ;        // Bitnumber in SCI_MODE for Line input
  317.     SPISettings   VS1053_SPI ;                    // SPI settings for this slave
  318.     uint8_t       endFillByte ;                   // Byte to send when stopping song
  319.   protected:
  320.     inline void await_data_request() const
  321.     {
  322.       while ( !digitalRead ( dreq_pin ) )
  323.       {
  324.         yield() ;                                 // Very short delay
  325.       }
  326.     }
  327.  
  328.     inline void control_mode_on() const
  329.     {
  330.       SPI.beginTransaction ( VS1053_SPI ) ;       // Prevent other SPI users
  331.       digitalWrite ( dcs_pin, HIGH ) ;            // Bring slave in control mode
  332.       digitalWrite ( cs_pin, LOW ) ;
  333.     }
  334.  
  335.     inline void control_mode_off() const
  336.     {
  337.       digitalWrite ( cs_pin, HIGH ) ;             // End control mode
  338.       SPI.endTransaction() ;                      // Allow other SPI users
  339.     }
  340.  
  341.     inline void data_mode_on() const
  342.     {
  343.       SPI.beginTransaction ( VS1053_SPI ) ;       // Prevent other SPI users
  344.       digitalWrite ( cs_pin, HIGH ) ;             // Bring slave in data mode
  345.       digitalWrite ( dcs_pin, LOW ) ;
  346.     }
  347.  
  348.     inline void data_mode_off() const
  349.     {
  350.       digitalWrite ( dcs_pin, HIGH ) ;            // End data mode
  351.       SPI.endTransaction() ;                      // Allow other SPI users
  352.     }
  353.  
  354.     uint16_t read_register ( uint8_t _reg ) const ;
  355.     void     write_register ( uint8_t _reg, uint16_t _value ) const ;
  356.     void     sdi_send_buffer ( uint8_t* data, size_t len ) ;
  357.     void     sdi_send_fillers ( size_t length ) ;
  358.     void     wram_write ( uint16_t address, uint16_t data ) ;
  359.     uint16_t wram_read ( uint16_t address ) ;
  360.  
  361.   public:
  362.     // Constructor.  Only sets pin values.  Doesn't touch the chip.  Be sure to call begin()!
  363.     VS1053 ( uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin ) ;
  364.     void     begin() ;                                   // Begin operation.  Sets pins correctly,
  365.     // and prepares SPI bus.
  366.     void     startSong() ;                               // Prepare to start playing. Call this each
  367.     // time a new song starts.
  368.     void     playChunk ( uint8_t* data, size_t len ) ;   // Play a chunk of data.  Copies the data to
  369.     // the chip.  Blocks until complete.
  370.     void     stopSong() ;                                // Finish playing a song. Call this after
  371.     // the last playChunk call.
  372.     void     setVolume ( uint8_t vol ) ;                 // Set the player volume.Level from 0-100,
  373.     // higher is louder.
  374.     void     setTone ( uint8_t* rtone ) ;                // Set the player baas/treble, 4 nibbles for
  375.     // treble gain/freq and bass gain/freq
  376.     uint8_t  getVolume() ;                               // Get the currenet volume setting.
  377.     // higher is louder.
  378.     void     printDetails ( const char *header ) ;       // Print configuration details to serial output.
  379.     void     softReset() ;                               // Do a soft reset
  380.     bool     testComm ( const char *header ) ;           // Test communication with module
  381.     inline bool data_request() const
  382.     {
  383.       return ( digitalRead ( dreq_pin ) == HIGH ) ;
  384.     }
  385.     void     AdjustRate ( long ppm2 ) ;                  // Fine tune the datarate
  386. } ;
  387.  
  388. //******************************************************************************************
  389. // VS1053 class implementation.                                                            *
  390. //******************************************************************************************
  391.  
  392. VS1053::VS1053 ( uint8_t _cs_pin, uint8_t _dcs_pin, uint8_t _dreq_pin ) :
  393.   cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin)
  394. {
  395. }
  396.  
  397. uint16_t VS1053::read_register ( uint8_t _reg ) const
  398. {
  399.   uint16_t result ;
  400.  
  401.   control_mode_on() ;
  402.   SPI.write ( 3 ) ;                                // Read operation
  403.   SPI.write ( _reg ) ;                             // Register to write (0..0xF)
  404.   // Note: transfer16 does not seem to work
  405.   result = ( SPI.transfer ( 0xFF ) << 8 ) |        // Read 16 bits data
  406.            ( SPI.transfer ( 0xFF ) ) ;
  407.   await_data_request() ;                           // Wait for DREQ to be HIGH again
  408.   control_mode_off() ;
  409.   return result ;
  410. }
  411.  
  412. void VS1053::write_register ( uint8_t _reg, uint16_t _value ) const
  413. {
  414.   control_mode_on( );
  415.   SPI.write ( 2 ) ;                                // Write operation
  416.   SPI.write ( _reg ) ;                             // Register to write (0..0xF)
  417.   SPI.write16 ( _value ) ;                         // Send 16 bits data
  418.   await_data_request() ;
  419.   control_mode_off() ;
  420. }
  421.  
  422. void VS1053::sdi_send_buffer ( uint8_t* data, size_t len )
  423. {
  424.   size_t chunk_length ;                            // Length of chunk 32 byte or shorter
  425.  
  426.   data_mode_on() ;
  427.   while ( len )                                    // More to do?
  428.   {
  429.     await_data_request() ;                         // Wait for space available
  430.     chunk_length = len ;
  431.     if ( len > vs1053_chunk_size )
  432.     {
  433.       chunk_length = vs1053_chunk_size ;
  434.     }
  435.     len -= chunk_length ;
  436.     SPI.writeBytes ( data, chunk_length ) ;
  437.     data += chunk_length ;
  438.   }
  439.   data_mode_off() ;
  440. }
  441.  
  442. void VS1053::sdi_send_fillers ( size_t len )
  443. {
  444.   size_t chunk_length ;                            // Length of chunk 32 byte or shorter
  445.  
  446.   data_mode_on() ;
  447.   while ( len )                                    // More to do?
  448.   {
  449.     await_data_request() ;                         // Wait for space available
  450.     chunk_length = len ;
  451.     if ( len > vs1053_chunk_size )
  452.     {
  453.       chunk_length = vs1053_chunk_size ;
  454.     }
  455.     len -= chunk_length ;
  456.     while ( chunk_length-- )
  457.     {
  458.       SPI.write ( endFillByte ) ;
  459.     }
  460.   }
  461.   data_mode_off();
  462. }
  463.  
  464. void VS1053::wram_write ( uint16_t address, uint16_t data )
  465. {
  466.   write_register ( SCI_WRAMADDR, address ) ;
  467.   write_register ( SCI_WRAM, data ) ;
  468. }
  469.  
  470. uint16_t VS1053::wram_read ( uint16_t address )
  471. {
  472.   write_register ( SCI_WRAMADDR, address ) ;            // Start reading from WRAM
  473.   return read_register ( SCI_WRAM ) ;                   // Read back result
  474. }
  475.  
  476. bool VS1053::testComm ( const char *header )
  477. {
  478.   // Test the communication with the VS1053 module.  The result wille be returned.
  479.   // If DREQ is low, there is problably no VS1053 connected.  Pull the line HIGH
  480.   // in order to prevent an endless loop waiting for this signal.  The rest of the
  481.   // software will still work, but readbacks from VS1053 will fail.
  482.   int       i ;                                         // Loop control
  483.   uint16_t  r1, r2, cnt = 0 ;
  484.   uint16_t  delta = 300 ;                               // 3 for fast SPI
  485.  
  486.   if ( !digitalRead ( dreq_pin ) )
  487.   {
  488.     dbgprint ( "VS1053 not properly installed!" ) ;
  489.     // Allow testing without the VS1053 module
  490.     pinMode ( dreq_pin,  INPUT_PULLUP ) ;               // DREQ is now input with pull-up
  491.     return false ;                                      // Return bad result
  492.   }
  493.   // Further TESTING.  Check if SCI bus can write and read without errors.
  494.   // We will use the volume setting for this.
  495.   // Will give warnings on serial output if DEBUG is active.
  496.   // A maximum of 20 errors will be reported.
  497.   if ( strstr ( header, "Fast" ) )
  498.   {
  499.     delta = 3 ;                                         // Fast SPI, more loops
  500.   }
  501.   dbgprint ( header ) ;                                 // Show a header
  502.   for ( i = 0 ; ( i < 0xFFFF ) && ( cnt < 20 ) ; i += delta )
  503.   {
  504.     write_register ( SCI_VOL, i ) ;                     // Write data to SCI_VOL
  505.     r1 = read_register ( SCI_VOL ) ;                    // Read back for the first time
  506.     r2 = read_register ( SCI_VOL ) ;                    // Read back a second time
  507.     if  ( r1 != r2 || i != r1 || i != r2 )              // Check for 2 equal reads
  508.     {
  509.       dbgprint ( "VS1053 error retry SB:%04X R1:%04X R2:%04X", i, r1, r2 ) ;
  510.       cnt++ ;
  511.       delay ( 10 ) ;
  512.     }
  513.     yield() ;                                           // Allow ESP firmware to do some bookkeeping
  514.   }
  515.   return ( cnt == 0 ) ;                                 // Return the result
  516. }
  517.  
  518. void VS1053::begin()
  519. {
  520.   pinMode      ( dreq_pin,  INPUT ) ;                   // DREQ is an input
  521.   pinMode      ( cs_pin,    OUTPUT ) ;                  // The SCI and SDI signals
  522.   pinMode      ( dcs_pin,   OUTPUT ) ;
  523.   digitalWrite ( dcs_pin,   HIGH ) ;                    // Start HIGH for SCI en SDI
  524.   digitalWrite ( cs_pin,    HIGH ) ;
  525.   delay ( 100 ) ;
  526.   dbgprint ( "Reset VS1053..." ) ;
  527.   digitalWrite ( dcs_pin,   LOW ) ;                     // Low & Low will bring reset pin low
  528.   digitalWrite ( cs_pin,    LOW ) ;
  529.   delay ( 2000 ) ;
  530.   dbgprint ( "End reset VS1053..." ) ;
  531.   digitalWrite ( dcs_pin,   HIGH ) ;                    // Back to normal again
  532.   digitalWrite ( cs_pin,    HIGH ) ;
  533.   delay ( 500 ) ;
  534.   // Init SPI in slow mode ( 0.2 MHz )
  535.   VS1053_SPI = SPISettings ( 200000, MSBFIRST, SPI_MODE0 ) ;
  536.   //printDetails ( "Right after reset/startup" ) ;
  537.   delay ( 20 ) ;
  538.   //printDetails ( "20 msec after reset" ) ;
  539.   testComm ( "Slow SPI,Testing VS1053 read/write registers..." ) ;
  540.   // Most VS1053 modules will start up in midi mode.  The result is that there is no audio
  541.   // when playing MP3.  You can modify the board, but there is a more elegant way:
  542.   wram_write ( 0xC017, 3 ) ;                            // GPIO DDR = 3
  543.   wram_write ( 0xC019, 0 ) ;                            // GPIO ODATA = 0
  544.   delay ( 100 ) ;
  545.   //printDetails ( "After test loop" ) ;
  546.   softReset() ;                                         // Do a soft reset
  547.   // Switch on the analog parts
  548.   write_register ( SCI_AUDATA, 44100 + 1 ) ;            // 44.1kHz + stereo
  549.   // The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then.
  550.   write_register ( SCI_CLOCKF, 6 << 12 ) ;              // Normal clock settings multiplyer 3.0 = 12.2 MHz
  551.   //SPI Clock to 4 MHz. Now you can set high speed SPI clock.
  552.   VS1053_SPI = SPISettings ( 4000000, MSBFIRST, SPI_MODE0 ) ;
  553.   write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_LINE1 ) ) ;
  554.   testComm ( "Fast SPI, Testing VS1053 read/write registers again..." ) ;
  555.   delay ( 10 ) ;
  556.   await_data_request() ;
  557.   endFillByte = wram_read ( 0x1E06 ) & 0xFF ;
  558.   dbgprint ( "endFillByte is %X", endFillByte ) ;
  559.   //printDetails ( "After last clocksetting" ) ;
  560.   delay ( 100 ) ;
  561. }
  562.  
  563. void VS1053::setVolume ( uint8_t vol )
  564. {
  565.   // Set volume.  Both left and right.
  566.   // Input value is 0..100.  100 is the loudest.
  567.   // Clicking reduced by using 0xf8 to 0x00 as limits.
  568.   uint16_t value ;                                      // Value to send to SCI_VOL
  569.  
  570.   if ( vol != curvol )
  571.   {
  572.     curvol = vol ;                                      // Save for later use
  573.     value = map ( vol, 0, 100, 0xF8, 0x00 ) ;           // 0..100% to one channel
  574.     value = ( value << 8 ) | value ;
  575.     write_register ( SCI_VOL, value ) ;                 // Volume left and right
  576.   }
  577. }
  578.  
  579. void VS1053::setTone ( uint8_t *rtone )                 // Set bass/treble (4 nibbles)
  580. {
  581.   // Set tone characteristics.  See documentation for the 4 nibbles.
  582.   uint16_t value = 0 ;                                  // Value to send to SCI_BASS
  583.   int      i ;                                          // Loop control
  584.  
  585.   for ( i = 0 ; i < 4 ; i++ )
  586.   {
  587.     value = ( value << 4 ) | rtone[i] ;                 // Shift next nibble in
  588.   }
  589.   write_register ( SCI_BASS, value ) ;                  // Tone settings
  590.   value = read_register ( SCI_BASS ) ;                  // Read back
  591.   dbgprint ( "BASS settings is %04X", value ) ;         // Print for TEST
  592. }
  593.  
  594. uint8_t VS1053::getVolume()                             // Get the currenet volume setting.
  595. {
  596.   return curvol ;
  597. }
  598.  
  599. void VS1053::startSong()
  600. {
  601.   sdi_send_fillers ( 10 ) ;
  602. }
  603.  
  604. void VS1053::playChunk ( uint8_t* data, size_t len )
  605. {
  606.   sdi_send_buffer ( data, len ) ;
  607. }
  608.  
  609. void VS1053::stopSong()
  610. {
  611.   uint16_t modereg ;                     // Read from mode register
  612.   int      i ;                           // Loop control
  613.  
  614.   sdi_send_fillers ( 2052 ) ;
  615.   delay ( 10 ) ;
  616.   write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_CANCEL ) ) ;
  617.   for ( i = 0 ; i < 200 ; i++ )
  618.   {
  619.     sdi_send_fillers ( 32 ) ;
  620.     modereg = read_register ( SCI_MODE ) ;  // Read status
  621.     if ( ( modereg & _BV ( SM_CANCEL ) ) == 0 )
  622.     {
  623.       sdi_send_fillers ( 2052 ) ;
  624.       dbgprint ( "Song stopped correctly after %d msec", i * 10 ) ;
  625.       return ;
  626.     }
  627.     delay ( 10 ) ;
  628.   }
  629.   printDetails ( "Song stopped incorrectly!" ) ;
  630. }
  631.  
  632. void VS1053::softReset()
  633. {
  634.   write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_RESET ) ) ;
  635.   delay ( 10 ) ;
  636.   await_data_request() ;
  637. }
  638.  
  639. void VS1053::printDetails ( const char *header )
  640. {
  641.   uint16_t     regbuf[16] ;
  642.   uint8_t      i ;
  643.  
  644.   dbgprint ( header ) ;
  645.   dbgprint ( "REG   Contents" ) ;
  646.   dbgprint ( "---   -----" ) ;
  647.   for ( i = 0 ; i <= SCI_num_registers ; i++ )
  648.   {
  649.     regbuf[i] = read_register ( i ) ;
  650.   }
  651.   for ( i = 0 ; i <= SCI_num_registers ; i++ )
  652.   {
  653.     delay ( 5 ) ;
  654.     dbgprint ( "%3X - %5X", i, regbuf[i] ) ;
  655.   }
  656. }
  657.  
  658. void VS1053::AdjustRate ( long ppm2 )
  659. {
  660.   write_register ( SCI_WRAMADDR, 0x1e07 ) ;
  661.   write_register ( SCI_WRAM,     ppm2 ) ;
  662.   write_register ( SCI_WRAM,     ppm2 >> 16 ) ;
  663.   // oldClock4KHz = 0 forces  adjustment calculation when rate checked.
  664.   write_register ( SCI_WRAMADDR, 0x5b1c ) ;
  665.   write_register ( SCI_WRAM,     0 ) ;
  666.   // Write to AUDATA or CLOCKF checks rate and recalculates adjustment.
  667.   write_register ( SCI_AUDATA,   read_register ( SCI_AUDATA ) ) ;
  668. }
  669.  
  670.  
  671. // The object for the MP3 player
  672. VS1053 vs1053player (  VS1053_CS, VS1053_DCS, VS1053_DREQ ) ;
  673.  
  674. //******************************************************************************************
  675. // End VS1053 stuff.                                                                       *
  676. //******************************************************************************************
  677.  
  678.  
  679.  
  680. //******************************************************************************************
  681. // Ringbuffer (fifo) routines.                                                             *
  682. //******************************************************************************************
  683. //******************************************************************************************
  684. //                              R I N G S P A C E                                          *
  685. //******************************************************************************************
  686. inline bool ringspace()
  687. {
  688.   return ( rcount < RINGBFSIZ ) ;     // True is at least one byte of free space is available
  689. }
  690.  
  691.  
  692. //******************************************************************************************
  693. //                              R I N G A V A I L                                          *
  694. //******************************************************************************************
  695. inline uint16_t ringavail()
  696. {
  697.   return rcount ;                     // Return number of bytes available
  698. }
  699.  
  700.  
  701. //******************************************************************************************
  702. //                                P U T R I N G                                            *
  703. //******************************************************************************************
  704. void putring ( uint8_t b )                 // Put one byte in the ringbuffer
  705. {
  706.   // No check on available space.  See ringspace()
  707.   *(ringbuf + rbwindex) = b ;         // Put byte in ringbuffer
  708.   if ( ++rbwindex == RINGBFSIZ )      // Increment pointer and
  709.   {
  710.     rbwindex = 0 ;                    // wrap at end
  711.   }
  712.   rcount++ ;                          // Count number of bytes in the
  713. }
  714.  
  715.  
  716. //******************************************************************************************
  717. //                                G E T R I N G                                            *
  718. //******************************************************************************************
  719. uint8_t getring()
  720. {
  721.   // Assume there is always something in the bufferpace.  See ringavail()
  722.   if ( ++rbrindex == RINGBFSIZ )      // Increment pointer and
  723.   {
  724.     rbrindex = 0 ;                    // wrap at end
  725.   }
  726.   rcount-- ;                          // Count is now one less
  727.   return *(ringbuf + rbrindex) ;      // return the oldest byte
  728. }
  729.  
  730. //******************************************************************************************
  731. //                               E M P T Y R I N G                                         *
  732. //******************************************************************************************
  733. void emptyring()
  734. {
  735.   rbwindex = 0 ;                      // Reset ringbuffer administration
  736.   rbrindex = RINGBFSIZ - 1 ;
  737.   rcount = 0 ;
  738. }
  739.  
  740.  
  741. //******************************************************************************************
  742. //                              U T F 8 A S C I I                                          *
  743. //******************************************************************************************
  744. // UTF8-Decoder: convert UTF8-string to extended ASCII.                                    *
  745. // Convert a single Character from UTF8 to Extended ASCII.                                 *
  746. // Return "0" if a byte has to be ignored.                                                 *
  747. //******************************************************************************************
  748. byte utf8ascii ( byte ascii )
  749. {
  750.   static const byte lut_C3[] =
  751.   { "AAAAAAACEEEEIIIIDNOOOOO#0UUUU###aaaaaaaceeeeiiiidnooooo##uuuuyyy" } ;
  752.   static byte       c1 ;              // Last character buffer
  753.   byte              res = 0 ;         // Result, default 0
  754.  
  755.   if ( ascii <= 0x7F )                // Standard ASCII-set 0..0x7F handling
  756.   {
  757.     c1 = 0 ;
  758.     res = ascii ;                     // Return unmodified
  759.   }
  760.   else
  761.   {
  762.     switch ( c1 )                     // Conversion depending on first UTF8-character
  763.     {
  764.       case 0xC2: res = '~' ;
  765.         break ;
  766.       case 0xC3: res = lut_C3[ascii - 128] ;
  767.         break ;
  768.       case 0x82: if ( ascii == 0xAC )
  769.         {
  770.           res = 'E' ;       // Special case Euro-symbol
  771.         }
  772.     }
  773.     c1 = ascii ;                      // Remember actual character
  774.   }
  775.   return res ;                        // Otherwise: return zero, if character has to be ignored
  776. }
  777.  
  778.  
  779. //******************************************************************************************
  780. //                              U T F 8 A S C I I                                          *
  781. //******************************************************************************************
  782. // In Place conversion UTF8-string to Extended ASCII (ASCII is shorter!).                  *
  783. //******************************************************************************************
  784. void utf8ascii ( char* s )
  785. {
  786.   int  i, k = 0 ;                     // Indexes for in en out string
  787.   char c ;
  788.  
  789.   for ( i = 0 ; s[i] ; i++ )          // For every input character
  790.   {
  791.     c = utf8ascii ( s[i] ) ;          // Translate if necessary
  792.     if ( c )                          // Good translation?
  793.     {
  794.       s[k++] = c ;                    // Yes, put in output string
  795.     }
  796.   }
  797.   s[k] = 0 ;                          // Take care of delimeter
  798. }
  799.  
  800.  
  801. //******************************************************************************************
  802. //                                  D B G P R I N T                                        *
  803. //******************************************************************************************
  804. // Send a line of info to serial output.  Works like vsprintf(), but checks the BEDUg flag.*
  805. // Print only if DEBUG flag is true.  Always returns the the formatted string.             *
  806. //******************************************************************************************
  807. char* dbgprint ( const char* format, ... )
  808. {
  809.   static char sbuf[DEBUG_BUFFER_SIZE] ;                // For debug lines
  810.   va_list varArgs ;                                    // For variable number of params
  811.  
  812.   va_start ( varArgs, format ) ;                       // Prepare parameters
  813.   vsnprintf ( sbuf, sizeof(sbuf), format, varArgs ) ;  // Format the message
  814.   va_end ( varArgs ) ;                                 // End of using parameters
  815.   if ( DEBUG )                                         // DEBUG on?
  816.   {
  817.     Serial.print ( "D: " ) ;                           // Yes, print prefix
  818.     Serial.println ( sbuf ) ;                          // and the info
  819.   }
  820.   return sbuf ;                                        // Return stored string
  821. }
  822.  
  823.  
  824. //******************************************************************************************
  825. //                             G E T E N C R Y P T I O N T Y P E                           *
  826. //******************************************************************************************
  827. // Read the encryption type of the network and return as a 4 byte name                     *
  828. //*********************4********************************************************************
  829. const char* getEncryptionType ( int thisType )
  830. {
  831.   switch (thisType)
  832.   {
  833.     case ENC_TYPE_WEP:
  834.       return "WEP " ;
  835.     case ENC_TYPE_TKIP:
  836.       return "WPA " ;
  837.     case ENC_TYPE_CCMP:
  838.       return "WPA2" ;
  839.     case ENC_TYPE_NONE:
  840.       return "None" ;
  841.     case ENC_TYPE_AUTO:
  842.       return "Auto" ;
  843.   }
  844.   return "????" ;
  845. }
  846.  
  847.  
  848. //******************************************************************************************
  849. //                                L I S T N E T W O R K S                                  *
  850. //******************************************************************************************
  851. // List the available networks and select the strongest.                                   *
  852. // Acceptable networks are those who have a "SSID.pw" file in the SPIFFS.                  *
  853. // SSIDs of available networks will be saved for use in webinterface.                      *
  854. //******************************************************************************************
  855. void listNetworks()
  856. {
  857.   int         maxsig = -1000 ;   // Used for searching strongest WiFi signal
  858.   int         newstrength ;
  859.   byte        encryption ;       // TKIP(WPA)=2, WEP=5, CCMP(WPA)=4, NONE=7, AUTO=8
  860.   const char* acceptable ;       // Netwerk is acceptable for connection
  861.   int         i ;                // Loop control
  862.   String      sassid ;           // Search string in anetworks
  863.  
  864.   ini_block.ssid = String ( "none" ) ;                   // No selceted network yet
  865.   // scan for nearby networks:
  866.   dbgprint ( "* Scan Networks *" ) ;
  867.   int numSsid = WiFi.scanNetworks() ;
  868.   if ( numSsid == -1 )
  869.   {
  870.     dbgprint ( "Couldn't get a wifi connection" ) ;
  871.     return ;
  872.   }
  873.   // print the list of networks seen:
  874.   dbgprint ( "Number of available networks: %d",
  875.              numSsid ) ;
  876.   // Print the network number and name for each network found and
  877.   // find the strongest acceptable network
  878.   for ( i = 0 ; i < numSsid ; i++ )
  879.   {
  880.     acceptable = "" ;                                    // Assume not acceptable
  881.     newstrength = WiFi.RSSI ( i ) ;                      // Get the signal strenght
  882.     sassid = WiFi.SSID ( i ) + String ( "|" ) ;          // For search string
  883.     if ( anetworks.indexOf ( sassid ) >= 0 )             // Is this SSID acceptable?
  884.     {
  885.       acceptable = "Acceptable" ;
  886.       if ( newstrength > maxsig )                        // This is a better Wifi
  887.       {
  888.         maxsig = newstrength ;
  889.         ini_block.ssid = WiFi.SSID ( i ) ;               // Remember SSID name
  890.       }
  891.     }
  892.     encryption = WiFi.encryptionType ( i ) ;
  893.     dbgprint ( "%2d - %-25s Signal: %3d dBm Encryption %4s  %s",
  894.                i + 1, WiFi.SSID ( i ).c_str(), WiFi.RSSI ( i ),
  895.                getEncryptionType ( encryption ),
  896.                acceptable ) ;
  897.     // Remember this network for later use
  898.     networks += WiFi.SSID ( i ) + String ( "|" ) ;
  899.   }
  900.   //networks
  901.   dbgprint ( "--------------------------------------" ) ;
  902. }
  903.  
  904.  
  905. //******************************************************************************************
  906. //                                  T I M E R 1 0 S E C                                    *
  907. //******************************************************************************************
  908. // Extra watchdog.  Called every 10 seconds.                                               *
  909. // If totalcount has not been changed, there is a problem and playing will stop.           *
  910. // Note that a "yield()" within this routine or in called functions will cause a crash!    *
  911. //******************************************************************************************
  912. void timer10sec()
  913. {
  914.   static uint32_t oldtotalcount = 7321 ;          // Needed foor change detection
  915.   static uint8_t  morethanonce = 0 ;              // Counter for succesive fails
  916.   static uint8_t  t600 = 0 ;                      // Counter for 10 minutes
  917.  
  918.   if ( datamode & ( INIT | HEADER | DATA |        // Test op playing
  919.                     METADATA | PLAYLISTINIT |
  920.                     PLAYLISTHEADER |
  921.                     PLAYLISTDATA ) )
  922.   {
  923.     if ( totalcount == oldtotalcount )            // Still playing?
  924.     {
  925.       dbgprint ( "No data input" ) ;              // No data detected!
  926.       if ( morethanonce > 10 )                    // Happened too many times?
  927.       {
  928.         dbgprint ( "Going to restart..." ) ;
  929.         ESP.restart() ;                           // Reset the CPU, probably no return
  930.       }
  931.       if ( datamode & ( PLAYLISTDATA |            // In playlist mode?
  932.                         PLAYLISTINIT |
  933.                         PLAYLISTHEADER ) )
  934.       {
  935.         playlist_num = 0 ;                        // Yes, end of playlist
  936.       }
  937.       if ( ( morethanonce > 0 ) ||                // Happened more than once?
  938.            ( playlist_num > 0 ) )                 // Or playlist active?
  939.       {
  940.         datamode = STOPREQD ;                     // Stop player
  941.         ini_block.newpreset++ ;                   // Yes, try next channel
  942.         dbgprint ( "Trying other station/file..." ) ;
  943.       }
  944.       morethanonce++ ;                            // Count the fails
  945.     }
  946.     else
  947.     {
  948.       if ( morethanonce )                         // Recovered from data loss?
  949.       {
  950.         dbgprint ( "Recovered from dataloss" ) ;
  951.         morethanonce = 0 ;                        // Data see, reset failcounter
  952.       }
  953.       oldtotalcount = totalcount ;                // Save for comparison in next cycle
  954.     }
  955.     if ( t600++ == 60 )                           // 10 minutes over?
  956.     {
  957.       t600 = 0 ;                                  // Yes, reset counter
  958.       dbgprint ( "10 minutes over" ) ;
  959.       publishIP() ;                               // Re-publish IP
  960.     }
  961.   }
  962. }
  963.  
  964.  
  965. //******************************************************************************************
  966. //                                  A N A G E T S W                                        *
  967. //******************************************************************************************
  968. // Translate analog input to switch number.  0 is inactive.                                *
  969. // Note that it is adviced to avoid expressions as the argument for the abs function.      *
  970. //******************************************************************************************
  971. uint8_t anagetsw ( uint16_t v )
  972. {
  973.   int      i ;                                    // Loop control
  974.   int      oldmindist = 1000 ;                    // Detection least difference
  975.   int      newdist ;                              // New found difference
  976.   uint8_t  sw = 0 ;                               // Number of switch detected (0 or 1..3)
  977.  
  978.   if ( v > analogrest )                           // Inactive level?
  979.   {
  980.     for ( i = 0 ; i < NUMANA ; i++ )
  981.     {
  982.       newdist = analogsw[i] - v ;                  // Compute difference
  983.       newdist = abs ( newdist ) ;                  // Make it absolute
  984.       if ( newdist < oldmindist )                  // New least difference?
  985.       {
  986.         oldmindist = newdist ;                     // Yes, remember
  987.         sw = i + 1 ;                               // Remember switch
  988.       }
  989.     }
  990.   }
  991.   return sw ;                                      // Return active switch
  992. }
  993.  
  994.  
  995. //******************************************************************************************
  996. //                               T E S T F I L E                                           *
  997. //******************************************************************************************
  998. // Test the performance of SPIFFS read.                                                    *
  999. //******************************************************************************************
  1000. void testfile ( String fspec )
  1001. {
  1002.   String   path ;                                      // Full file spec
  1003.   File     tfile ;                                     // File containing mp3
  1004.   uint32_t len, savlen ;                               // File length
  1005.   uint32_t t0, t1, told ;                              // For time test
  1006.   uint32_t t_error = 0 ;                               // Number of slow reads
  1007.  
  1008.   dbgprint ( "Start test of file %s", fspec.c_str() ) ;
  1009.   t0 = millis() ;                                      // Timestamp at start
  1010.   t1 = t0 ;                                            // Prevent uninitialized value
  1011.   told = t0 ;                                          // For report
  1012.   path = String ( "/" ) + fspec ;                      // Form full path
  1013.   tfile = SPIFFS.open ( path, "r" ) ;                  // Open the file
  1014.   if ( tfile )
  1015.   {
  1016.     len = tfile.available() ;                          // Get file length
  1017.     savlen = len ;                                     // Save for result print
  1018.     while ( len-- )                                    // Any data left?
  1019.     {
  1020.       t1 = millis() ;                                  // To meassure read time
  1021.       tfile.read() ;                                   // Read one byte
  1022.       if ( ( millis() - t1 ) > 5 )                     // Read took more than 5 msec?
  1023.       {
  1024.         t_error++ ;                                    // Yes, count slow reads
  1025.       }
  1026.       if ( ( len % 100 ) == 0 )                        // Yield reguarly
  1027.       {
  1028.         yield() ;
  1029.       }
  1030.       if ( ( ( t1 - told ) / 1000 ) > 0 || len == 0 )
  1031.       {
  1032.         // Show results for debug
  1033.         dbgprint ( "Read %s, length %d/%d took %d seconds, %d slow reads",
  1034.                    fspec.c_str(), savlen - len, savlen, ( t1 - t0 ) / 1000, t_error ) ;
  1035.         told = t1 ;
  1036.       }
  1037.       if ( ( t1 - t0 ) > 100000 )                      // Give up after 100 seconds
  1038.       {
  1039.         dbgprint ( "Give up..." ) ;
  1040.         break ;
  1041.       }
  1042.     }
  1043.     tfile.close() ;
  1044.     dbgprint ( "EOF" ) ;                               // End of file
  1045.   }
  1046. }
  1047.  
  1048.  
  1049. //******************************************************************************************
  1050. //                                  T I M E R 1 0 0                                        *
  1051. //******************************************************************************************
  1052. // Examine button every 100 msec.                                                          *
  1053. //******************************************************************************************
  1054. void timer100()
  1055. {
  1056.   static int     count10sec = 0 ;                 // Counter for activatie 10 seconds process
  1057.   static int     oldval2 = HIGH ;                 // Previous value of digital input button 2
  1058. #if ( not ( defined ( USETFT ) ) )
  1059.   static int     oldval1 = HIGH ;                 // Previous value of digital input button 1
  1060.   static int     oldval3 = HIGH ;                 // Previous value of digital input button 3
  1061. #endif
  1062.   int            newval ;                         // New value of digital input switch
  1063.   uint16_t       v ;                              // Analog input value 0..1023
  1064.   static uint8_t aoldval = 0 ;                    // Previous value of analog input switch
  1065.   uint8_t        anewval ;                        // New value of analog input switch (0..3)
  1066.  
  1067.   if ( ++count10sec == 100  )                     // 10 seconds passed?
  1068.   {
  1069.     timer10sec() ;                                // Yes, do 10 second procedure
  1070.     count10sec = 0 ;                              // Reset count
  1071.   }
  1072.   else
  1073.   {
  1074.     newval = digitalRead ( BUTTON2 ) ;            // Test if below certain level
  1075.     if ( newval != oldval2 )                      // Change?
  1076.     {
  1077.       oldval2 = newval ;                          // Yes, remember value
  1078.       if ( newval == LOW )                        // Button pushed?
  1079.       {
  1080.         ini_block.newpreset = currentpreset + 1 ; // Yes, goto next preset station
  1081.         //dbgprint ( "Digital button 2 pushed" ) ;
  1082.       }
  1083.       return ;
  1084.     }
  1085. #if ( not ( defined ( USETFT ) ) )
  1086.     newval = digitalRead ( BUTTON1 ) ;            // Test if below certain level
  1087.     if ( newval != oldval1 )                      // Change?
  1088.     {
  1089.       oldval1 = newval ;                          // Yes, remember value
  1090.       if ( newval == LOW )                        // Button pushed?
  1091.       {
  1092.         ini_block.newpreset = 0 ;                 // Yes, goto first preset station
  1093.         //dbgprint ( "Digital button 1 pushed" ) ;
  1094.       }
  1095.       return ;
  1096.     }
  1097.     // Note that BUTTON3 has inverted input
  1098.     newval = digitalRead ( BUTTON3 ) ;            // Test if below certain level
  1099.     newval = HIGH + LOW - newval ;                // Reverse polarity
  1100.     if ( newval != oldval3 )                      // Change?
  1101.     {
  1102.       oldval3 = newval ;                          // Yes, remember value
  1103.       if ( newval == LOW )                        // Button pushed?
  1104.       {
  1105.         ini_block.newpreset = currentpreset - 1 ; // Yes, goto previous preset station
  1106.         //dbgprint ( "Digital button 3 pushed" ) ;
  1107.       }
  1108.       return ;
  1109.     }
  1110. #endif
  1111.     v = analogRead ( A0 ) ;                       // Read analog value
  1112.     anewval = anagetsw ( v ) ;                    // Check analog value for program switches
  1113.     if ( anewval != aoldval )                     // Change?
  1114.     {
  1115.       aoldval = anewval ;                         // Remember value for change detection
  1116.       if ( anewval != 0 )                         // Button pushed?
  1117.       {
  1118.         //dbgprint ( "Analog button %d pushed, v = %d", anewval, v ) ;
  1119.         if ( anewval == 1 )                       // Button 1?
  1120.         {
  1121.           ini_block.newpreset = 0 ;               // Yes, goto first preset
  1122.         }
  1123.         else if ( anewval == 2 )                  // Button 2?
  1124.         {
  1125.           ini_block.newpreset = currentpreset + 1 ; // Yes, goto next preset
  1126.         }
  1127.         else if ( anewval == 3 )                  // Button 3?
  1128.         {
  1129.           ini_block.newpreset = currentpreset - 1 ; // Yes, goto previous preset
  1130.         }
  1131.       }
  1132.     }
  1133.   }
  1134. }
  1135.  
  1136.  
  1137. //******************************************************************************************
  1138. //                              D I S P L A Y V O L U M E                                  *
  1139. //******************************************************************************************
  1140. // Show the current volume as an indicator on the screen.                                  *
  1141. //******************************************************************************************
  1142. void displayvolume()
  1143. {
  1144. #if defined ( USETFT )
  1145.   static uint8_t oldvol = 0 ;                        // Previous volume
  1146.   uint8_t pos ;                                      // Positon of volume indicator
  1147.  
  1148.   if ( vs1053player.getVolume() != oldvol )
  1149.   {
  1150.     pos = map ( vs1053player.getVolume(), 0, 100, 0, 160 ) ;
  1151.     tft.fillRect ( 0, 126, pos, 2, RED ) ;             // Paint red part
  1152.     tft.fillRect ( pos, 126, 160 - pos, 2, GREEN ) ;   // Paint green part
  1153.   }
  1154. #endif
  1155. }
  1156.  
  1157.  
  1158. //******************************************************************************************
  1159. //                              D I S P L A Y I N F O                                      *
  1160. //******************************************************************************************
  1161. // Show a string on the LCD at a specified y-position in a specified color                 *
  1162. //******************************************************************************************
  1163. #if defined ( USETFT )
  1164. void displayinfo ( const char* str, uint16_t pos, uint16_t height, uint16_t color )
  1165. {
  1166.   char buf [ strlen ( str ) + 1 ] ;             // Need some buffer space
  1167.  
  1168.   strcpy ( buf, str ) ;                         // Make a local copy of the string
  1169.   utf8ascii ( buf ) ;                           // Convert possible UTF8
  1170.   tft.fillRect ( 0, pos, 160, height, BLACK ) ; // Clear the space for new info
  1171.   tft.setTextColor ( color ) ;                  // Set the requested color
  1172.   tft.setCursor ( 0, pos ) ;                    // Prepare to show the info
  1173.   tft.println ( buf ) ;                         // Show the string
  1174. }
  1175. #else
  1176. #define displayinfo(a,b,c,d)                    // Empty declaration
  1177. #endif
  1178.  
  1179.  
  1180. //******************************************************************************************
  1181. //                        S H O W S T R E A M T I T L E                                    *
  1182. //******************************************************************************************
  1183. // Show artist and songtitle if present in metadata.                                       *
  1184. // Show always if full=true.                                                               *
  1185. //******************************************************************************************
  1186. void showstreamtitle ( const char *ml, bool full )
  1187. {
  1188.   char*             p1 ;
  1189.   char*             p2 ;
  1190.   char              streamtitle[150] ;           // Streamtitle from metadata
  1191.  
  1192.   if ( strstr ( ml, "StreamTitle=" ) )
  1193.   {
  1194.     dbgprint ( "Streamtitle found, %d bytes", strlen ( ml ) ) ;
  1195.     dbgprint ( ml ) ;
  1196.     p1 = (char*)ml + 12 ;                       // Begin of artist and title
  1197.     if ( ( p2 = strstr ( ml, ";" ) ) )          // Search for end of title
  1198.     {
  1199.       if ( *p1 == '\'' )                        // Surrounded by quotes?
  1200.       {
  1201.         p1++ ;
  1202.         p2-- ;
  1203.       }
  1204.       *p2 = '\0' ;                              // Strip the rest of the line
  1205.     }
  1206.     // Save last part of string as streamtitle.  Protect against buffer overflow
  1207.     strncpy ( streamtitle, p1, sizeof ( streamtitle ) ) ;
  1208.     streamtitle[sizeof ( streamtitle ) - 1] = '\0' ;
  1209.   }
  1210.   else if ( full )
  1211.   {
  1212.     // Info probably from playlist
  1213.     strncpy ( streamtitle, ml, sizeof ( streamtitle ) ) ;
  1214.     streamtitle[sizeof ( streamtitle ) - 1] = '\0' ;
  1215.   }
  1216.   else
  1217.   {
  1218.     icystreamtitle = "" ;                       // Unknown type
  1219.     return ;                                    // Do not show
  1220.   }
  1221.   // Save for status request from browser ;
  1222.   icystreamtitle = streamtitle ;
  1223.   if ( ( p1 = strstr ( streamtitle, " - " ) ) ) // look for artist/title separator
  1224.   {
  1225.     *p1++ = '\n' ;                              // Found: replace 3 characters by newline
  1226.     p2 = p1 + 2 ;
  1227.     if ( *p2 == ' ' )                           // Leading space in title?
  1228.     {
  1229.       p2++ ;
  1230.     }
  1231.     strcpy ( p1, p2 ) ;                         // Shift 2nd part of title 2 or 3 places
  1232.   }
  1233.   displayinfo ( streamtitle, 20, 40, CYAN ) ;   // Show title at position 20
  1234. }
  1235.  
  1236.  
  1237. //******************************************************************************************
  1238. //                            S T O P _ M P 3 C L I E N T                                  *
  1239. //******************************************************************************************
  1240. // Disconnect from the server.                                                             *
  1241. //******************************************************************************************
  1242. void stop_mp3client ()
  1243. {
  1244.   if ( mp3client )
  1245.   {
  1246.     if ( mp3client->connected() )                    // Need to stop client?
  1247.     {
  1248.       dbgprint ( "Stopping client" ) ;               // Stop connection to host
  1249.       mp3client->flush() ;
  1250.       mp3client->stop() ;
  1251.       delay ( 500 ) ;
  1252.     }
  1253.     delete ( mp3client ) ;
  1254.     mp3client = NULL ;
  1255.   }
  1256. }
  1257.  
  1258.  
  1259. //******************************************************************************************
  1260. //                            C O N N E C T T O H O S T                                    *
  1261.  
  1262. //******************************************************************************************
  1263. // Connect to the Internet radio server specified by newpreset.                            *
  1264. //******************************************************************************************
  1265. bool connecttohost()
  1266. {
  1267.   int         inx ;                                 // Position of ":" in hostname
  1268.   char*       pfs ;                                 // Pointer to formatted string
  1269.   int         port = 80 ;                           // Port number for host
  1270.   String      extension = "/" ;                     // May be like "/mp3" in "skonto.ls.lv:8002/mp3"
  1271.   String      hostwoext ;                           // Host without extension and portnumber
  1272.  
  1273.   stop_mp3client() ;                                // Disconnect if still connected
  1274.   dbgprint ( "Connect to new host %s", host.c_str() ) ;
  1275.   displayinfo ( "   ** Internet radio **", 0, 20, WHITE ) ;
  1276.   datamode = INIT ;                                 // Start default in metamode
  1277.   chunked = false ;                                 // Assume not chunked
  1278.   if ( host.endsWith ( ".m3u" ) )                   // Is it an m3u playlist?
  1279.   {
  1280.     playlist = host ;                               // Save copy of playlist URL
  1281.     datamode = PLAYLISTINIT ;                       // Yes, start in PLAYLIST mode
  1282.     if ( playlist_num == 0 )                        // First entry to play?
  1283.     {
  1284.       playlist_num = 1 ;                            // Yes, set index
  1285.     }
  1286.     dbgprint ( "Playlist request, entry %d", playlist_num ) ;
  1287.   }
  1288.   // In the URL there may be an extension
  1289.   inx = host.indexOf ( "/" ) ;                      // Search for begin of extension
  1290.   if ( inx > 0 )                                    // Is there an extension?
  1291.   {
  1292.     extension = host.substring ( inx ) ;            // Yes, change the default
  1293.     hostwoext = host.substring ( 0, inx ) ;         // Host without extension
  1294.   }
  1295.   // In the URL there may be a portnumber
  1296.   inx = host.indexOf ( ":" ) ;                      // Search for separator
  1297.   if ( inx >= 0 )                                   // Portnumber available?
  1298.   {
  1299.     port = host.substring ( inx + 1 ).toInt() ;     // Get portnumber as integer
  1300.     hostwoext = host.substring ( 0, inx ) ;         // Host without portnumber
  1301.   }
  1302.   pfs = dbgprint ( "Connect to %s on port %d, extension %s",
  1303.                    hostwoext.c_str(), port, extension.c_str() ) ;
  1304.   displayinfo ( pfs, 60, 66, YELLOW ) ;             // Show info at position 60..125
  1305.   mp3client = new WiFiClient() ;
  1306.   if ( mp3client->connect ( hostwoext.c_str(), port ) )
  1307.   {
  1308.     dbgprint ( "Connected to server" ) ;
  1309.     // This will send the request to the server. Request metadata.
  1310.     mp3client->print ( String ( "GET " ) +
  1311.                        extension +
  1312.                        String ( " HTTP/1.1\r\n" ) +
  1313.                        String ( "Host: " ) +
  1314.                        hostwoext +
  1315.                        String ( "\r\n" ) +
  1316.                        String ( "Icy-MetaData:1\r\n" ) +
  1317.                        String ( "Connection: close\r\n\r\n" ) ) ;
  1318.     return true ;
  1319.   }
  1320.   dbgprint ( "Request %s failed!", host.c_str() ) ;
  1321.   return false ;
  1322. }
  1323.  
  1324.  
  1325. //******************************************************************************************
  1326. //                               C O N N E C T T O F I L E                                 *
  1327. //******************************************************************************************
  1328. // Open the local mp3-file.                                                                *
  1329. //******************************************************************************************
  1330. bool connecttofile()
  1331. {
  1332.   String path ;                                           // Full file spec
  1333.   char*  p ;                                              // Pointer to filename
  1334.  
  1335.   displayinfo ( "   **** MP3 Player ****", 0, 20, WHITE ) ;
  1336.   path = host.substring ( 9 ) ;                           // Path, skip the "localhost" part
  1337.   mp3file = SPIFFS.open ( path, "r" ) ;                   // Open the file
  1338.   if ( !mp3file )
  1339.   {
  1340.     dbgprint ( "Error opening file %s", path.c_str() ) ;  // No luck
  1341.     return false ;
  1342.   }
  1343.   p = (char*)path.c_str() + 1 ;                           // Point to filename
  1344.   showstreamtitle ( p, true ) ;                           // Show the filename as title
  1345.   displayinfo ( "Playing from local file",
  1346.                 60, 68, YELLOW ) ;                        // Show Source at position 60
  1347.   icyname = "" ;                                          // No icy name yet
  1348.   chunked = false ;                                       // File not chunked
  1349.   return true ;
  1350. }
  1351.  
  1352.  
  1353. //******************************************************************************************
  1354. //                               C O N N E C T W I F I                                     *
  1355. //******************************************************************************************
  1356. // Connect to WiFi using passwords available in the SPIFFS.                                *
  1357. // If connection fails, an AP is created and the function returns false.                   *
  1358. //******************************************************************************************
  1359. bool connectwifi()
  1360. {
  1361.   char*  pfs ;                                         // Pointer to formatted string
  1362.  
  1363.   WiFi.disconnect() ;                                  // After restart the router could
  1364.   WiFi.softAPdisconnect(true) ;                        // still keep the old connection
  1365.   WiFi.begin ( ini_block.ssid.c_str(),
  1366.                ini_block.passwd.c_str() ) ;            // Connect to selected SSID
  1367.   dbgprint ( "Try WiFi %s", ini_block.ssid.c_str() ) ; // Message to show during WiFi connect
  1368.   if (  WiFi.waitForConnectResult() != WL_CONNECTED )  // Try to connect
  1369.   {
  1370.     dbgprint ( "WiFi Failed!  Trying to setup AP with name %s and password %s.", NAME, NAME ) ;
  1371.     WiFi.softAP ( NAME, NAME ) ;                       // This ESP will be an AP
  1372.     delay ( 5000 ) ;
  1373.     pfs = dbgprint ( "IP = 192.168.4.1" ) ;            // Address if AP
  1374.     return false ;
  1375.   }
  1376.   actualnetwork = ini_block.ssid;
  1377.   pfs = dbgprint ( "IP = %d.%d.%d.%d",
  1378.                    WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3] ) ;
  1379. #if defined ( USETFT )
  1380.   tft.println ( pfs ) ;
  1381. #endif
  1382.   return true ;
  1383. }
  1384.  
  1385.  
  1386. //******************************************************************************************
  1387. //                                   O T A S T A R T                                       *
  1388. //******************************************************************************************
  1389. // Update via WiFi has been started by Arduino IDE.                                        *
  1390. //******************************************************************************************
  1391. void otastart()
  1392. {
  1393.   dbgprint ( "OTA Started" ) ;
  1394. }
  1395.  
  1396.  
  1397. //******************************************************************************************
  1398. //                          R E A D H O S T F R O M I N I F I L E                          *
  1399. //******************************************************************************************
  1400. // Read the mp3 host from the ini-file specified by the parameter.                         *
  1401. // The host will be returned.                                                              *
  1402. //******************************************************************************************
  1403. String readhostfrominifile ( int8_t preset )
  1404. {
  1405.   String      path ;                                   // Full file spec as string
  1406.   File        inifile ;                                // File containing URL with mp3
  1407.   char        tkey[10] ;                               // Key as an array of chars
  1408.   String      line ;                                   // Input line from .ini file
  1409.   String      linelc ;                                 // Same, but lowercase
  1410.   int         inx ;                                    // Position within string
  1411.   String      res = "" ;                               // Assume not found
  1412.  
  1413.   path = String ( INIFILENAME ) ;                      // Form full path
  1414.   inifile = SPIFFS.open ( path, "r" ) ;                // Open the file
  1415.   if ( inifile )
  1416.   {
  1417.     sprintf ( tkey, "preset_%02d", preset ) ;           // Form the search key
  1418.     while ( inifile.available() )
  1419.     {
  1420.       line = inifile.readStringUntil ( '\n' ) ;        // Read next line
  1421.       linelc = line ;                                  // Copy for lowercase
  1422.       linelc.toLowerCase() ;                           // Set to lowercase
  1423.       if ( linelc.startsWith ( tkey ) )                // Found the key?
  1424.       {
  1425.         inx = line.indexOf ( "=" ) ;                   // Get position of "="
  1426.         if ( inx > 0 )                                 // Equal sign present?
  1427.         {
  1428.           line.remove ( 0, inx + 1 ) ;                 // Yes, remove key
  1429.           res = chomp ( line ) ;                       // Remove garbage
  1430.           break ;                                      // End the while loop
  1431.         }
  1432.       }
  1433.     }
  1434.     inifile.close() ;                                  // Close the file
  1435.   }
  1436.   else
  1437.   {
  1438.     dbgprint ( "File %s not found, please create one!", INIFILENAME ) ;
  1439.   }
  1440.   return res ;
  1441. }
  1442.  
  1443.  
  1444. //******************************************************************************************
  1445. //                               R E A D I N I F I L E                                     *
  1446. //******************************************************************************************
  1447. // Read the .ini file and interpret the commands.                                          *
  1448. //******************************************************************************************
  1449. void readinifile()
  1450. {
  1451.   String      path ;                                   // Full file spec as string
  1452.   File        inifile ;                                // File containing URL with mp3
  1453.   String      line ;                                   // Input line from .ini file
  1454.  
  1455.   path = String ( INIFILENAME ) ;                      // Form full path
  1456.   inifile = SPIFFS.open ( path, "r" ) ;                // Open the file
  1457.   if ( inifile )
  1458.   {
  1459.     while ( inifile.available() )
  1460.     {
  1461.       line = inifile.readStringUntil ( '\n' ) ;        // Read next line
  1462.       analyzeCmd ( line.c_str() ) ;
  1463.     }
  1464.     inifile.close() ;                                  // Close the file
  1465.   }
  1466.   else
  1467.   {
  1468.     dbgprint ( "File %s not found, use save command to create one!", INIFILENAME ) ;
  1469.   }
  1470. }
  1471.  
  1472.  
  1473. //******************************************************************************************
  1474. //                            P U B L I S H I P                                            *
  1475. //******************************************************************************************
  1476. // Publish IP to MQTT broker.                                                              *
  1477. //******************************************************************************************
  1478. void publishIP()
  1479. {
  1480.   IPAddress ip ;
  1481.   char      ipstr[20] ;                          // Hold IP as string
  1482.  
  1483.   if ( ini_block.mqttpubtopic.length() )        // Topic to publish?
  1484.   {
  1485.     ip = WiFi.localIP() ;
  1486.     // Publish IP-adress.  qos=1, retain=true
  1487.     sprintf ( ipstr, "%d.%d.%d.%d",
  1488.               ip[0], ip[1], ip[2], ip[3] ) ;
  1489.     mqttclient.publish ( ini_block.mqttpubtopic.c_str(), 1, true, ipstr ) ;
  1490.     dbgprint ( "Publishing IP %s to topic %s",
  1491.                ipstr, ini_block.mqttpubtopic.c_str() ) ;
  1492.   }
  1493. }
  1494.  
  1495.  
  1496. //******************************************************************************************
  1497. //                            O N M Q T T C O N N E C T                                    *
  1498. //******************************************************************************************
  1499. // Will be called on connection to the broker.  Subscribe to our topic and publish a topic.*
  1500. //******************************************************************************************
  1501. void onMqttConnect( bool sessionPresent )
  1502. {
  1503.   uint16_t    packetIdSub ;
  1504.   const char* present = "is" ;                      // Assume Session is present
  1505.  
  1506.   if ( !sessionPresent )
  1507.   {
  1508.     present = "is not" ;                            // Session is NOT present
  1509.   }
  1510.   dbgprint ( "MQTT Connected to the broker %s, session %s present",
  1511.              ini_block.mqttbroker.c_str(), present ) ;
  1512.   packetIdSub = mqttclient.subscribe ( ini_block.mqtttopic.c_str(), 2 ) ;
  1513.   dbgprint ( "Subscribing to %s at QoS 2, packetId = %d ",
  1514.              ini_block.mqtttopic.c_str(),
  1515.              packetIdSub ) ;
  1516.   publishIP() ;                                     // Topic to publish: IP
  1517. }
  1518.  
  1519.  
  1520. //******************************************************************************************
  1521. //                      O N M Q T T D I S C O N N E C T                                    *
  1522. //******************************************************************************************
  1523. // Will be called on disconnect.                                                           *
  1524. //******************************************************************************************
  1525. void onMqttDisconnect ( AsyncMqttClientDisconnectReason reason )
  1526. {
  1527.   dbgprint ( "MQTT Disconnected from the broker, reason %d, reconnecting...",
  1528.              reason ) ;
  1529.   if ( mqttcount < MAXMQTTCONNECTS )            // Try again?
  1530.   {
  1531.     mqttcount++ ;                               // Yes, count number of tries
  1532.     mqttclient.connect() ;                      // Reconnect
  1533.   }
  1534. }
  1535.  
  1536.  
  1537. //******************************************************************************************
  1538. //                      O N M Q T T S U B S C R I B E                                      *
  1539. //******************************************************************************************
  1540. // Will be called after a successful subscribe.                                            *
  1541. //******************************************************************************************
  1542. void onMqttSubscribe ( uint16_t packetId, uint8_t qos )
  1543. {
  1544.   dbgprint ( "MQTT Subscribe acknowledged, packetId = %d, QoS = %d",
  1545.              packetId, qos ) ;
  1546. }
  1547.  
  1548.  
  1549. //******************************************************************************************
  1550. //                              O N M Q T T U N S U B S C R I B E                          *
  1551. //******************************************************************************************
  1552. // Will be executed if this program unsubscribes from a topic.                             *
  1553. // Not used at the moment.                                                                 *
  1554. //******************************************************************************************
  1555. void onMqttUnsubscribe ( uint16_t packetId )
  1556. {
  1557.   dbgprint ( "MQTT Unsubscribe acknowledged, packetId = %d",
  1558.              packetId ) ;
  1559. }
  1560.  
  1561.  
  1562. //******************************************************************************************
  1563. //                            O N M Q T T M E S S A G E                                    *
  1564. //******************************************************************************************
  1565. // Executed when a subscribed message is received.                                         *
  1566. // Note that message is not delimited by a '\0'.                                           *
  1567. //******************************************************************************************
  1568. void onMqttMessage ( char* topic, char* payload, AsyncMqttClientMessageProperties properties,
  1569.                      size_t len, size_t index, size_t total )
  1570. {
  1571.   char*  reply ;                                    // Result from analyzeCmd
  1572.  
  1573.   // Available properties.qos, properties.dup, properties.retain
  1574.   if ( len >= sizeof(cmd) )                         // Message may not be too long
  1575.   {
  1576.     len = sizeof(cmd) - 1 ;
  1577.   }
  1578.   strncpy ( cmd, payload, len ) ;                   // Make copy of message
  1579.   cmd[len] = '\0' ;                                 // Take care of delimeter
  1580.   dbgprint ( "MQTT message arrived [%s], lenght = %d, %s", topic, len, cmd ) ;
  1581.   reply = analyzeCmd ( cmd ) ;                      // Analyze command and handle it
  1582.   dbgprint ( reply ) ;                              // Result for debugging
  1583. }
  1584.  
  1585.  
  1586. //******************************************************************************************
  1587. //                             O N M Q T T P U B L I S H                                   *
  1588. //******************************************************************************************
  1589. // Will be executed if a message is published by this program.                             *
  1590. // Not used at the moment.                                                                 *
  1591. //******************************************************************************************
  1592. void onMqttPublish ( uint16_t packetId )
  1593. {
  1594.   dbgprint ( "MQTT Publish acknowledged, packetId = %d",
  1595.              packetId ) ;
  1596. }
  1597.  
  1598.  
  1599. //******************************************************************************************
  1600. //                             S C A N S E R I A L                                         *
  1601. //******************************************************************************************
  1602. // Listen to commands on the Serial inputline.                                             *
  1603. //******************************************************************************************
  1604. void scanserial()
  1605. {
  1606.   static String serialcmd ;                      // Command from Serial input
  1607.   char          c ;                              // Input character
  1608.   char*         reply ;                          // Reply string froma analyzeCmd
  1609.   uint16_t      len ;                            // Length of input string
  1610.  
  1611.   while ( Serial.available() )                   // Any input seen?
  1612.   {
  1613.     c =  (char)Serial.read() ;                   // Yes, read the next input character
  1614.     //Serial.write ( c ) ;                       // Echo
  1615.     len = serialcmd.length() ;                   // Get the length of the current string
  1616.     if ( ( c == '\n' ) || ( c == '\r' ) )
  1617.     {
  1618.       if ( len )
  1619.       {
  1620.         strncpy ( cmd, serialcmd.c_str(), sizeof(cmd) ) ;
  1621.         reply = analyzeCmd ( cmd) ;              // Analyze command and handle it
  1622.         dbgprint ( reply ) ;                     // Result for debugging
  1623.         serialcmd = "" ;                         // Prepare for new command
  1624.       }
  1625.     }
  1626.     if ( c >= ' ' )                              // Only accept useful characters
  1627.     {
  1628.       serialcmd += c ;                           // Add to the command
  1629.     }
  1630.     if ( len >= ( sizeof(cmd) - 2 )  )           // Check for excessive length
  1631.     {
  1632.       serialcmd = "" ;                           // Too long, reset
  1633.     }
  1634.   }
  1635. }
  1636.  
  1637.  
  1638. //******************************************************************************************
  1639. //                                   M K _ L S A N                                         *
  1640. //******************************************************************************************
  1641. // Make al list of acceptable networks in .ini file.                                       *
  1642. // The result will be stored in anetworks like "|SSID1|SSID2|......|SSIDN|".               *
  1643. // The number of acceptable networks will be stored in num_an.                             *
  1644. //******************************************************************************************
  1645. void mk_lsan()
  1646. {
  1647.   String      path ;                                   // Full file spec as string
  1648.   File        inifile ;                                // File containing URL with mp3
  1649.   String      line ;                                   // Input line from .ini file
  1650.   String      ssid ;                                   // SSID in line
  1651.   int         inx ;                                    // Place of "/"
  1652.  
  1653.   num_an = 0 ;                                         // Count acceptable networks
  1654.   anetworks = "|" ;                                    // Initial value
  1655.   path = String ( INIFILENAME ) ;                      // Form full path
  1656.   inifile = SPIFFS.open ( path, "r" ) ;                // Open the file
  1657.   if ( inifile )
  1658.   {
  1659.     while ( inifile.available() )
  1660.     {
  1661.       line = inifile.readStringUntil ( '\n' ) ;        // Read next line
  1662.       ssid = line ;                                    // Copy holds original upper/lower case
  1663.       line.toLowerCase() ;                             // Case insensitive
  1664.       if ( line.startsWith ( "wifi" ) )                // Line with WiFi spec?
  1665.       {
  1666.         inx = line.indexOf ( "/" ) ;                   // Find separator between ssid and password
  1667.         if ( inx > 0 )                                 // Separator found?
  1668.         {
  1669.           ssid = ssid.substring ( 5, inx ) ;           // Line holds SSID now
  1670.           dbgprint ( "Added SSID %s to acceptable networks",
  1671.                      ssid.c_str() ) ;
  1672.           anetworks += ssid ;                          // Add to list
  1673.           anetworks += "|" ;                           // Separator
  1674.           num_an++ ;                                   // Count number oif acceptable networks
  1675.         }
  1676.       }
  1677.     }
  1678.     inifile.close() ;                                  // Close the file
  1679.   }
  1680.   else
  1681.   {
  1682.     dbgprint ( "File %s not found!", INIFILENAME ) ;   // No .ini file
  1683.   }
  1684. }
  1685.  
  1686.  
  1687. //******************************************************************************************
  1688. //                             G E T P R E S E T S                                         *
  1689. //******************************************************************************************
  1690. // Make a list of all preset stations.                                                     *
  1691. // The result will be stored in the String presetlist (global data).                       *
  1692. //******************************************************************************************
  1693. void getpresets()
  1694. {
  1695.   String              path ;                             // Full file spec as string
  1696.   File                inifile ;                          // File containing URL with mp3
  1697.   String              line ;                             // Input line from .ini file
  1698.   int                 inx ;                              // Position of search char in line
  1699.   int                 i ;                                // Loop control
  1700.   int                 j = 0;
  1701.   char                vnr[3] ;                           // 2 digit presetnumber as string
  1702.  
  1703.   presetlist = String ( "" ) ;                           // No result yet
  1704.   path = String ( INIFILENAME ) ;                        // Form full path
  1705.   inifile = SPIFFS.open ( path, "r" ) ;                  // Open the file
  1706.   if ( inifile )
  1707.   {
  1708.     while ( inifile.available() )
  1709.     {
  1710.       line = inifile.readStringUntil ( '\n' ) ;          // Read next line
  1711.       if ( line.startsWith ( "preset_" ) )               // Found the key?
  1712.       {
  1713.         i = line.substring(7, 9).toInt() ;               // Get index 00..99
  1714.         // Show just comment if available.  Otherwise the preset itself.
  1715.         inx = line.indexOf ( "#" ) ;                     // Get position of "#"
  1716.         if ( inx > 0 )                                   // Hash sign present?
  1717.         {
  1718.           line.remove ( 0, inx + 1 ) ;                   // Yes, remove non-comment part
  1719.         }
  1720.         else
  1721.         {
  1722.           inx = line.indexOf ( "=" ) ;                   // Get position of "="
  1723.           if ( inx > 0 )                                 // Equal sign present?
  1724.           {
  1725.             line.remove ( 0, inx + 1 ) ;                 // Yes, remove first part of line
  1726.           }
  1727.         }
  1728.         line = chomp ( line ) ;                          // Remove garbage from description
  1729.         stations_names[i] = line.substring(line.indexOf("-") + 1, line.length());
  1730.         sprintf ( vnr, "%02d", i ) ;                     // Preset number
  1731.         presetlist += ( String ( vnr ) + line +          // 2 digits plus description
  1732.                         String ( "|" ) ) ;
  1733.       }
  1734.     }
  1735.     inifile.close() ;                                    // Close the file
  1736.   }
  1737. }
  1738.  
  1739.  
  1740. //******************************************************************************************
  1741. //                                   S E T U P                                             *
  1742. //******************************************************************************************
  1743. // Setup for the program.                                                                  *
  1744. //******************************************************************************************
  1745. void setup()
  1746. {
  1747.   FSInfo      fs_info ;                                // Info about SPIFFS
  1748.   Dir         dir ;                                    // Directory struct for SPIFFS
  1749.   File        f ;                                      // Filehandle
  1750.   String      filename ;                               // Name of file found in SPIFFS
  1751.  
  1752.   Serial.begin (74880) ;                            // For debug
  1753.   Serial.println() ;
  1754.   system_update_cpu_freq (160) ;                     // Set to 80/160 MHz
  1755.   ringbuf = (uint8_t *) malloc ( RINGBFSIZ ) ;         // Create ring buffer
  1756.   xml.init ( xmlbuffer, sizeof(xmlbuffer),             // Initilize XML stream.
  1757.              &XML_callback ) ;
  1758.   memset( &ini_block, 0, sizeof(ini_block) ) ;        // Init ini_block
  1759.   ini_block.mqttport = 1883 ;                          // Default port for MQTT
  1760.   SPIFFS.begin() ;                                     // Enable file system
  1761.   // Show some info about the SPIFFS
  1762.   SPIFFS.info (fs_info) ;
  1763.   dbgprint ( "FS Total %d, used %d", fs_info.totalBytes, fs_info.usedBytes ) ;
  1764.   if ( fs_info.totalBytes == 0 )
  1765.   {
  1766.     dbgprint ( "No SPIFFS found!  See documentation." ) ;
  1767.   }
  1768.   dir = SPIFFS.openDir("/") ;                          // Show files in FS
  1769.   while ( dir.next() )                                 // All files
  1770.   {
  1771.     f = dir.openFile ( "r" ) ;
  1772.     filename = dir.fileName() ;
  1773.     dbgprint ( "%-32s - %7d",                          // Show name and size
  1774.                filename.c_str(), f.size() ) ;
  1775.   }
  1776.   mk_lsan() ;                                          // Make a list of acceptable networks in ini file.
  1777.   listNetworks() ;                                     // Search for WiFi networks
  1778.   readinifile() ;                                      // Read .ini file
  1779.   getpresets() ;                                       // Get the presets from .ini-file
  1780.   WiFi.setPhyMode ( WIFI_PHY_MODE_11N ) ;              // Force 802.11N connection
  1781.   WiFi.persistent (false) ;                            // Do not save SSID and password
  1782.   WiFi.disconnect() ;                                  // The router may keep the old connection
  1783.   WiFi.mode ( WIFI_STA ) ;                             // This ESP is a station
  1784.   wifi_station_set_hostname ( (char*)NAME ) ;
  1785.   SPI.begin() ;                                        // Init SPI bus
  1786.   // Print some memory and sketch info
  1787.   dbgprint ( "Starting ESP Version %s...  Free memory %d",
  1788.              VERSION,
  1789.              system_get_free_heap_size() ) ;
  1790.   dbgprint ( "Sketch size %d, free size %d",
  1791.              ESP.getSketchSize(),
  1792.              ESP.getFreeSketchSpace() ) ;
  1793.   pinMode ( BUTTON2, INPUT_PULLUP ) ;                  // Input for control button 2
  1794.   vs1053player.begin() ;                               // Initialize VS1053 player
  1795. #if defined ( USETFT )
  1796.   tft.begin() ;                                        // Init TFT interface
  1797.   tft.fillRect ( 0, 0, 160, 128, BLACK ) ;             // Clear screen does not work when rotated
  1798.   tft.setRotation ( 3 ) ;                              // Use landscape format
  1799.   tft.clearScreen() ;                                  // Clear screen
  1800.   tft.setTextSize ( 1 ) ;                              // Small character font
  1801.   tft.setTextColor ( WHITE ) ;                         // Info in white
  1802.   tft.println ( "Starting" ) ;
  1803.   tft.println ( "Version:" ) ;
  1804.   tft.println ( VERSION ) ;
  1805. #else
  1806.   pinMode ( BUTTON1, INPUT_PULLUP ) ;                  // Input for control button 1
  1807.   pinMode ( BUTTON3, INPUT_PULLUP ) ;                  // Input for control button 3
  1808. #endif
  1809.   delay(10);
  1810.   analogrest = ( analogRead ( A0 ) + asw1 ) / 2  ;     // Assumed inactive analog input
  1811.   tckr.attach ( 0.100, timer100 ) ;                    // Every 100 msec
  1812.   dbgprint ( "Selected network: %-25s", ini_block.ssid.c_str() ) ;
  1813.   NetworkFound = connectwifi() ;                       // Connect to WiFi network
  1814.   //NetworkFound = false ;                             // TEST, uncomment for no network test
  1815.   dbgprint ( "Start server for commands" ) ;
  1816.   cmdserver.on ( "/", handleCmd ) ;                    // Handle startpage
  1817.   cmdserver.onNotFound ( handleFS ) ;                  // Handle file from FS
  1818.   cmdserver.onFileUpload ( handleFileUpload ) ;        // Handle file uploads
  1819.   cmdserver.begin() ;
  1820.   if ( NetworkFound )                                  // OTA and MQTT only if Wifi network found
  1821.   {
  1822.     ArduinoOTA.setHostname ( NAME ) ;                  // Set the hostname
  1823.     ArduinoOTA.onStart ( otastart ) ;
  1824.     ArduinoOTA.begin() ;                               // Allow update over the air
  1825.     if ( ini_block.mqttbroker.length() )               // Broker specified?
  1826.     {
  1827.       // Initialize the MQTT client
  1828.       WiFi.hostByName ( ini_block.mqttbroker.c_str(),
  1829.                         mqtt_server_IP ) ;             // Lookup IP of MQTT server
  1830.       mqttclient.onConnect ( onMqttConnect ) ;
  1831.       mqttclient.onDisconnect ( onMqttDisconnect ) ;
  1832.       mqttclient.onSubscribe ( onMqttSubscribe ) ;
  1833.       mqttclient.onUnsubscribe ( onMqttUnsubscribe ) ;
  1834.       mqttclient.onMessage ( onMqttMessage ) ;
  1835.       mqttclient.onPublish ( onMqttPublish ) ;
  1836.       mqttclient.setServer ( mqtt_server_IP,           // Specify the broker
  1837.                              ini_block.mqttport ) ;    // And the port
  1838.       mqttclient.setCredentials ( ini_block.mqttuser.c_str(),
  1839.                                   ini_block.mqttpasswd.c_str() ) ;
  1840.       mqttclient.setClientId ( NAME ) ;
  1841.       dbgprint ( "Connecting to MQTT %s, port %d, user %s, password %s...",
  1842.                  ini_block.mqttbroker.c_str(),
  1843.                  ini_block.mqttport,
  1844.                  ini_block.mqttuser.c_str(),
  1845.                  ini_block.mqttpasswd.c_str() ) ;
  1846.       mqttclient.connect();
  1847.     }
  1848.   }
  1849.   else
  1850.   {
  1851.     currentpreset = ini_block.newpreset ;              // No network: do not start radio
  1852.   }
  1853.   delay ( 1000 ) ;                                     // Show IP for a while
  1854.   analogrest = ( analogRead ( A0 ) + asw1 ) / 2  ;     // Assumed inactive analog input
  1855.   //ftpSrv.begin(NAME,NAME);
  1856. }
  1857.  
  1858.  
  1859. //******************************************************************************************
  1860. //                                  X M L  C A L L B A C K                                 *
  1861. //******************************************************************************************
  1862. // Process XML tags into variables.                                                        *
  1863. //******************************************************************************************
  1864. void XML_callback ( uint8_t statusflags, char* tagName, uint16_t tagNameLen,
  1865.                     char* data,  uint16_t dataLen )
  1866. {
  1867.   if ( statusflags & STATUS_START_TAG )
  1868.   {
  1869.     if ( tagNameLen )
  1870.     {
  1871.       xmlOpen = String ( tagName ) ;
  1872.       //dbgprint ( "Start tag %s",tagName ) ;
  1873.     }
  1874.   }
  1875.   else if ( statusflags & STATUS_END_TAG )
  1876.   {
  1877.     //dbgprint ( "End tag %s", tagName ) ;
  1878.   }
  1879.   else if ( statusflags & STATUS_TAG_TEXT )
  1880.   {
  1881.     xmlTag = String( tagName ) ;
  1882.     xmlData = String( data ) ;
  1883.     //dbgprint ( Serial.print( "Tag: %s, text: %s", tagName, data ) ;
  1884.   }
  1885.   else if ( statusflags & STATUS_ATTR_TEXT )
  1886.   {
  1887.     //dbgprint ( "Attribute: %s, text: %s", tagName, data ) ;
  1888.   }
  1889.   else if  ( statusflags & STATUS_ERROR )
  1890.   {
  1891.     //dbgprint ( "XML Parsing error  Tag: %s, text: %s", tagName, data ) ;
  1892.   }
  1893. }
  1894.  
  1895.  
  1896. //******************************************************************************************
  1897. //                                  X M L  P A R S E                                       *
  1898. //******************************************************************************************
  1899. // Parses streams from XML data.                                                           *
  1900. //******************************************************************************************
  1901. String xmlparse ( String mount )
  1902. {
  1903.   // Example URL for XML Data Stream:
  1904.   // http://playerservices.streamtheworld.com/api/livestream?version=1.5&mount=IHR_TRANAAC&lang=en
  1905.   // Clear all variables for use.
  1906.   char   tmpstr[200] ;                              // Full GET command, later stream URL
  1907.   char   c ;                                        // Next input character from reply
  1908.   String urlout ;                                   // Result URL
  1909.   bool   urlfound = false ;                         // Result found
  1910.  
  1911.   stationServer = "" ;
  1912.   stationPort = "" ;
  1913.   stationMount = "" ;
  1914.   xmlTag = "" ;
  1915.   xmlData = "" ;
  1916.   stop_mp3client() ; // Stop any current wificlient connections.
  1917.   dbgprint ( "Connect to new iHeartRadio host: %s", mount.c_str() ) ;
  1918.   datamode = INIT ;                                 // Start default in metamode
  1919.   chunked = false ;                                 // Assume not chunked
  1920.   // Create a GET commmand for the request.
  1921.   sprintf ( tmpstr, xmlget, mount.c_str() ) ;
  1922.   dbgprint ( "%s", tmpstr ) ;
  1923.   // Connect to XML stream.
  1924.   mp3client = new WiFiClient() ;
  1925.   if ( mp3client->connect ( xmlhost, xmlport ) ) {
  1926.     dbgprint ( "Connected!" ) ;
  1927.     mp3client->print ( String ( tmpstr ) + " HTTP/1.1\r\n"
  1928.                        "Host: " + xmlhost + "\r\n"
  1929.                        "User-Agent: Mozilla/5.0\r\n"
  1930.                        "Connection: close\r\n\r\n" ) ;
  1931.     // Check for XML Data.
  1932.     while ( true )
  1933.     {
  1934.       if ( mp3client->available() )
  1935.       {
  1936.         char c = mp3client->read() ;
  1937.         if ( c == '<' )
  1938.         {
  1939.           c = mp3client->read() ;
  1940.           if ( c == '?' )
  1941.           {
  1942.             xml.processChar ( '<' ) ;
  1943.             xml.processChar ( '?' ) ;
  1944.             break ;
  1945.           }
  1946.         }
  1947.       }
  1948.       yield() ;
  1949.     }
  1950.     dbgprint ( "XML parser processing..." ) ;
  1951.     // Process XML Data.
  1952.     while (true)
  1953.     {
  1954.       if ( mp3client->available() )
  1955.       {
  1956.         c = mp3client->read() ;
  1957.         xml.processChar ( c ) ;
  1958.         if ( xmlTag != "" )
  1959.         {
  1960.           if ( xmlTag.endsWith ( "/status-code" ) )   // Status code seen?
  1961.           {
  1962.             if ( xmlData != "200" )                   // Good result?
  1963.             {
  1964.               dbgprint ( "Bad xml status-code %s",    // No, show and stop interpreting
  1965.                          xmlData.c_str() ) ;
  1966.               break ;
  1967.             }
  1968.           }
  1969.           if ( xmlTag.endsWith ( "/ip" ) )
  1970.           {
  1971.             stationServer = xmlData ;
  1972.           }
  1973.           else if ( xmlTag.endsWith ( "/port" ) )
  1974.           {
  1975.             stationPort = xmlData ;
  1976.           }
  1977.           else if ( xmlTag.endsWith ( "/mount"  ) )
  1978.           {
  1979.             stationMount = xmlData ;
  1980.           }
  1981.         }
  1982.       }
  1983.       // Check if all the station values are stored.
  1984.       urlfound = ( stationServer != "" && stationPort != "" && stationMount != "" ) ;
  1985.       if ( urlfound )
  1986.       {
  1987.         xml.reset() ;
  1988.         break ;
  1989.       }
  1990.       yield() ;
  1991.     }
  1992.     tmpstr[0] = '\0' ;
  1993.     if ( urlfound )
  1994.     {
  1995.       sprintf ( tmpstr, "%s:%s/%s_SC",                   // Build URL for ESP-Radio to stream.
  1996.                 stationServer.c_str(),
  1997.                 stationPort.c_str(),
  1998.                 stationMount.c_str() ) ;
  1999.       dbgprint ( "Found: %s", tmpstr ) ;
  2000.     }
  2001.     dbgprint ( "Closing XML connection." ) ;
  2002.     stop_mp3client () ;
  2003.   }
  2004.   else
  2005.   {
  2006.     dbgprint ( "Can't connect to XML host!" ) ;
  2007.     tmpstr[0] = '\0' ;
  2008.   }
  2009.   return String ( tmpstr ) ;                           // Return final streaming URL.
  2010. }
  2011.  
  2012.  
  2013. //******************************************************************************************
  2014. //                                   L O O P                                               *
  2015. //******************************************************************************************
  2016. // Main loop of the program.  Minimal time is 20 usec.  Will take about 4 msec if VS1053   *
  2017. // needs data.                                                                             *
  2018. // Sometimes the loop is called after an interval of more than 100 msec.                   *
  2019. // In that case we will not be able to fill the internal VS1053-fifo in time (especially   *
  2020. // at high bitrate).                                                                       *
  2021. // A connection to an MP3 server is active and we are ready to receive data.               *
  2022. // Normally there is about 2 to 4 kB available in the data stream.  This depends on the    *
  2023. // sender.                                                                                 *
  2024. //******************************************************************************************
  2025. void loop()
  2026. {
  2027.   uint32_t    maxfilechunk  ;                           // Max number of bytes to read from
  2028.   // stream or file
  2029.   // Try to keep the ringbuffer filled up by adding as much bytes as possible
  2030.   if ( datamode & ( INIT | HEADER | DATA |              // Test op playing
  2031.                     METADATA | PLAYLISTINIT |
  2032.                     PLAYLISTHEADER |
  2033.                     PLAYLISTDATA ) )
  2034.   {
  2035.     if ( localfile )
  2036.     {
  2037.       maxfilechunk = mp3file.available() ;              // Bytes left in file
  2038.       if ( maxfilechunk > 1024 )                        // Reduce byte count for this loop()
  2039.       {
  2040.         maxfilechunk = 1024 ;
  2041.       }
  2042.       while ( ringspace() && maxfilechunk-- )
  2043.       {
  2044.         putring ( mp3file.read() ) ;                    // Yes, store one byte in ringbuffer
  2045.         yield() ;
  2046.       }
  2047.     }
  2048.     else
  2049.     {
  2050.       maxfilechunk = mp3client->available() ;          // Bytes available from mp3 server
  2051.       if ( maxfilechunk > 1024 )                       // Reduce byte count for this loop()
  2052.       {
  2053.         maxfilechunk = 1024 ;
  2054.       }
  2055.       while ( ringspace() && maxfilechunk-- )
  2056.       {
  2057.         putring ( mp3client->read() ) ;                // Yes, store one byte in ringbuffer
  2058.         yield() ;
  2059.       }
  2060.     }
  2061.     yield() ;
  2062.   }
  2063.   while ( vs1053player.data_request() && ringavail() ) // Try to keep VS1053 filled
  2064.   {
  2065.     handlebyte_ch ( getring() ) ;                      // Yes, handle it
  2066.   }
  2067.   yield() ;
  2068.   if ( datamode == STOPREQD )                          // STOP requested?
  2069.   {
  2070.     dbgprint ( "STOP requested" ) ;
  2071.     if ( localfile )
  2072.     {
  2073.       mp3file.close() ;
  2074.     }
  2075.     else
  2076.     {
  2077.       stop_mp3client() ;                               // Disconnect if still connected
  2078.     }
  2079.     handlebyte_ch ( 0, true ) ;                        // Force flush of buffer
  2080.     vs1053player.setVolume ( 0 ) ;                     // Mute
  2081.     vs1053player.stopSong() ;                          // Stop playing
  2082.     emptyring() ;                                      // Empty the ringbuffer
  2083.     datamode = STOPPED ;                               // Yes, state becomes STOPPED
  2084. #if defined ( USETFT )
  2085.     tft.fillRect ( 0, 0, 160, 128, BLACK ) ;           // Clear screen does not work when rotated
  2086. #endif
  2087.     delay ( 500 ) ;
  2088.   }
  2089.   if ( localfile )
  2090.   {
  2091.     if ( datamode & ( INIT | HEADER | DATA |           // Test op playing
  2092.                       METADATA | PLAYLISTINIT |
  2093.                       PLAYLISTHEADER |
  2094.                       PLAYLISTDATA ) )
  2095.     {
  2096.       if ( ( mp3file.available() == 0 ) && ( ringavail() == 0 ) )
  2097.       {
  2098.         datamode = STOPREQD ;                          // End of local mp3-file detected
  2099.       }
  2100.     }
  2101.   }
  2102.   if ( ini_block.newpreset != currentpreset )          // New station or next from playlist requested?
  2103.   {
  2104.     if ( datamode != STOPPED )                         // Yes, still busy?
  2105.     {
  2106.       datamode = STOPREQD ;                            // Yes, request STOP
  2107.     }
  2108.     else
  2109.     {
  2110.       if ( playlist_num )                               // Playing from playlist?
  2111.       { // Yes, retrieve URL of playlist
  2112.         playlist_num += ini_block.newpreset -
  2113.                         currentpreset ;                 // Next entry in playlist
  2114.         ini_block.newpreset = currentpreset ;           // Stay at current preset
  2115.       }
  2116.       else
  2117.       {
  2118.         host = readhostfrominifile(ini_block.newpreset) ; // Lookup preset in ini-file
  2119.       }
  2120.       dbgprint ( "New preset/file requested (%d/%d) from %s",
  2121.                  currentpreset, playlist_num, host.c_str() ) ;
  2122.       if ( host != ""  )                                // Preset in ini-file?
  2123.       {
  2124.         hostreq = true ;                                // Force this station as new preset
  2125.       }
  2126.       else
  2127.       {
  2128.         // This preset is not available, return to preset 0, will be handled in next loop()
  2129.         ini_block.newpreset = 0 ;                       // Wrap to first station
  2130.       }
  2131.     }
  2132.   }
  2133.   if ( hostreq )                                        // New preset or station?
  2134.   {
  2135.     hostreq = false ;
  2136.     currentpreset = ini_block.newpreset ;               // Remember current preset
  2137.  
  2138.     localfile = host.startsWith ( "localhost/" ) ;      // Find out if this URL is on localhost
  2139.     if ( localfile )                                    // Play file from localhost?
  2140.     {
  2141.       if ( connecttofile() )                            // Yes, open mp3-file
  2142.       {
  2143.         datamode = DATA ;                               // Start in DATA mode
  2144.       }
  2145.     }
  2146.     else
  2147.     {
  2148.       if ( host.startsWith ( "ihr/" ) )                 // iHeartRadio station requested?
  2149.       {
  2150.         host = host.substring ( 4 ) ;                   // Yes, remove "ihr/"
  2151.         host = xmlparse ( host ) ;                      // Parse the xml to get the host
  2152.       }
  2153.       connecttohost() ;                                 // Switch to new host
  2154.     }
  2155.   }
  2156.   if ( xmlreq )                                         // Directly xml requested?
  2157.   {
  2158.     xmlreq = false ;                                    // Yes, clear request flag
  2159.     host = xmlparse ( host ) ;                          // Parse the xml to get the host
  2160.     connecttohost() ;                                   // and connect to this host
  2161.   }
  2162.   if ( reqtone )                                        // Request to change tone?
  2163.   {
  2164.     reqtone = false ;
  2165.     vs1053player.setTone ( ini_block.rtone ) ;          // Set SCI_BASS to requested value
  2166.   }
  2167.   if ( muteflag )
  2168.   {
  2169.     vs1053player.setVolume ( 0 ) ;                      // Mute
  2170.   }
  2171.   else
  2172.   {
  2173.     vs1053player.setVolume ( ini_block.reqvol ) ;       // Unmute
  2174.   }
  2175.   displayvolume() ;                                     // Show volume on display
  2176.   if ( testfilename.length() )                          // File to test?
  2177.   {
  2178.     testfile ( testfilename ) ;                         // Yes, do the test
  2179.     testfilename = "" ;                                 // Clear test request
  2180.   }
  2181.   scanserial() ;                                        // Handle serial input
  2182.   ArduinoOTA.handle() ;                                 // Check for OTA
  2183.   if ( resetreq )                                       // Reset requested?
  2184.   {
  2185.     dbgprint ( "Going to restart..." ) ;          // Yes, wait some time
  2186.     delay(1000);
  2187.     ESP.restart() ;                                     // Reboot
  2188.   }
  2189.   //ftpSrv.handleFTP();
  2190. }
  2191.  
  2192.  
  2193. //******************************************************************************************
  2194. //                            C H K H D R L I N E                                          *
  2195. //******************************************************************************************
  2196. // Check if a line in the header is a reasonable headerline.                               *
  2197. // Normally it should contain something like "icy-xxxx:abcdef".                            *
  2198. //******************************************************************************************
  2199. bool chkhdrline ( const char* str )
  2200. {
  2201.   char    b ;                                         // Byte examined
  2202.   int     len = 0 ;                                   // Lengte van de string
  2203.  
  2204.   while ( ( b = *str++ ) )                            // Search to end of string
  2205.   {
  2206.     len++ ;                                           // Update string length
  2207.     if ( ! isalpha ( b ) )                            // Alpha (a-z, A-Z)
  2208.     {
  2209.       if ( b != '-' )                                 // Minus sign is allowed
  2210.       {
  2211.         if ( b == ':' )                               // Found a colon?
  2212.         {
  2213.           return ( ( len > 5 ) && ( len < 50 ) ) ;    // Yes, okay if length is okay
  2214.         }
  2215.         else
  2216.         {
  2217.           return false ;                              // Not a legal character
  2218.         }
  2219.       }
  2220.     }
  2221.   }
  2222.   return false ;                                      // End of string without colon
  2223. }
  2224.  
  2225.  
  2226. //******************************************************************************************
  2227. //                           H A N D L E B Y T E _ C H                                     *
  2228. //******************************************************************************************
  2229. // Handle the next byte of data from server.                                               *
  2230. // Chunked transfer encoding aware. Chunk extensions are not supported.                    *
  2231. //******************************************************************************************
  2232. void handlebyte_ch ( uint8_t b, bool force )
  2233. {
  2234.   static int  chunksize = 0 ;                         // Chunkcount read from stream
  2235.  
  2236.   if ( chunked && !force &&
  2237.        ( datamode & ( DATA |                          // Test op DATA handling
  2238.                       METADATA |
  2239.                       PLAYLISTDATA ) ) )
  2240.   {
  2241.     if ( chunkcount == 0 )                            // Expecting a new chunkcount?
  2242.     {
  2243.       if ( b == '\r' )                               // Skip CR
  2244.       {
  2245.         return ;
  2246.       }
  2247.       else if ( b == '\n' )                          // LF ?
  2248.       {
  2249.         chunkcount = chunksize ;                     // Yes, set new count
  2250.         chunksize = 0 ;                              // For next decode
  2251.         return ;
  2252.       }
  2253.       // We have received a hexadecimal character.  Decode it and add to the result.
  2254.       b = toupper ( b ) - '0' ;                      // Be sure we have uppercase
  2255.       if ( b > 9 )
  2256.       {
  2257.         b = b - 7 ;                                  // Translate A..F to 10..15
  2258.       }
  2259.       chunksize = ( chunksize << 4 ) + b ;
  2260.     }
  2261.     else
  2262.     {
  2263.       handlebyte ( b, force ) ;                       // Normal data byte
  2264.       chunkcount-- ;                                  // Update count to next chunksize block
  2265.     }
  2266.   }
  2267.   else
  2268.   {
  2269.     handlebyte ( b, force ) ;                         // Normal handling of this byte
  2270.   }
  2271. }
  2272.  
  2273.  
  2274. //******************************************************************************************
  2275. //                           H A N D L E B Y T E                                           *
  2276. //******************************************************************************************
  2277. // Handle the next byte of data from server.                                               *
  2278. // This byte will be send to the VS1053 most of the time.                                  *
  2279. // Note that the buffer the data chunk must start at an address that is a muttiple of 4.   *
  2280. // Set force to true if chunkbuffer must be flushed.                                       *
  2281. //******************************************************************************************
  2282. void handlebyte ( uint8_t b, bool force )
  2283. {
  2284.   static uint16_t  playlistcnt ;                       // Counter to find right entry in playlist
  2285.   static bool      firstmetabyte ;                     // True if first metabyte (counter)
  2286.   static int       LFcount ;                           // Detection of end of header
  2287.   static __attribute__((aligned(4))) uint8_t buf[32] ; // Buffer for chunk
  2288.   static int       bufcnt = 0 ;                        // Data in chunk
  2289.   static bool      firstchunk = true ;                 // First chunk as input
  2290.   String           lcml ;                              // Lower case metaline
  2291.   String           ct ;                                // Contents type
  2292.   static bool      ctseen = false ;                    // First line of header seen or not
  2293.   int              inx ;                               // Pointer in metaline
  2294.   int              i ;                                 // Loop control
  2295.  
  2296.   if ( datamode == INIT )                              // Initialize for header receive
  2297.   {
  2298.     ctseen = false ;                                   // Contents type not seen yet
  2299.     metaint = 0 ;                                      // No metaint found
  2300.     LFcount = 0 ;                                      // For detection end of header
  2301.     bitrate = 0 ;                                      // Bitrate still unknown
  2302.     dbgprint ( "Switch to HEADER" ) ;
  2303.     datamode = HEADER ;                                // Handle header
  2304.     totalcount = 0 ;                                   // Reset totalcount
  2305.     metaline = "" ;                                    // No metadata yet
  2306.     firstchunk = true ;                                // First chunk expected
  2307.   }
  2308.   if ( datamode == DATA )                              // Handle next byte of MP3/Ogg data
  2309.   {
  2310.     buf[bufcnt++] = b ;                                // Save byte in chunkbuffer
  2311.     if ( bufcnt == sizeof(buf) || force )              // Buffer full?
  2312.     {
  2313.       if ( firstchunk )
  2314.       {
  2315.         firstchunk = false ;
  2316.         dbgprint ( "First chunk:" ) ;                  // Header for printout of first chunk
  2317.         for ( i = 0 ; i < 32 ; i += 8 )                // Print 4 lines
  2318.         {
  2319.           dbgprint ( "%02X %02X %02X %02X %02X %02X %02X %02X",
  2320.                      buf[i],   buf[i + 1], buf[i + 2], buf[i + 3],
  2321.                      buf[i + 4], buf[i + 5], buf[i + 6], buf[i + 7] ) ;
  2322.         }
  2323.       }
  2324.       vs1053player.playChunk ( buf, bufcnt ) ;         // Yes, send to player
  2325.       bufcnt = 0 ;                                     // Reset count
  2326.     }
  2327.     totalcount++ ;                                     // Count number of bytes, ignore overflow
  2328.     if ( metaint != 0 )                                // No METADATA on Ogg streams or mp3 files
  2329.     {
  2330.       if ( --datacount == 0 )                          // End of datablock?
  2331.       {
  2332.         if ( bufcnt )                                  // Yes, still data in buffer?
  2333.         {
  2334.           vs1053player.playChunk ( buf, bufcnt ) ;     // Yes, send to player
  2335.           bufcnt = 0 ;                                 // Reset count
  2336.         }
  2337.         datamode = METADATA ;
  2338.         firstmetabyte = true ;                         // Expecting first metabyte (counter)
  2339.       }
  2340.     }
  2341.     return ;
  2342.   }
  2343.   if ( datamode == HEADER )                            // Handle next byte of MP3 header
  2344.   {
  2345.     if ( ( b > 0x7F ) ||                               // Ignore unprintable characters
  2346.          ( b == '\r' ) ||                              // Ignore CR
  2347.          ( b == '\0' ) )                               // Ignore NULL
  2348.     {
  2349.       // Yes, ignore
  2350.     }
  2351.     else if ( b == '\n' )                              // Linefeed ?
  2352.     {
  2353.       LFcount++ ;                                      // Count linefeeds
  2354.       if ( chkhdrline ( metaline.c_str() ) )           // Reasonable input?
  2355.       {
  2356.         lcml = metaline ;                              // Use lower case for compare
  2357.         lcml.toLowerCase() ;
  2358.         dbgprint ( metaline.c_str() ) ;                // Yes, Show it
  2359.         if ( lcml.indexOf ( "content-type" ) >= 0 )    // Line with "Content-Type: xxxx/yyy"
  2360.         {
  2361.           ctseen = true ;                              // Yes, remember seeing this
  2362.           ct = metaline.substring ( 14 ) ;             // Set contentstype. Not used yet
  2363.           dbgprint ( "%s seen.", ct.c_str() ) ;
  2364.         }
  2365.         if ( lcml.startsWith ( "icy-br:" ) )
  2366.         {
  2367.           bitrate = metaline.substring(7).toInt() ;    // Found bitrate tag, read the bitrate
  2368.           if ( bitrate == 0 )                          // For Ogg br is like "Quality 2"
  2369.           {
  2370.             bitrate = 87 ;                             // Dummy bitrate
  2371.           }
  2372.         }
  2373.         else if ( lcml.startsWith ( "icy-metaint:" ) )
  2374.         {
  2375.           metaint = metaline.substring(12).toInt() ;   // Found metaint tag, read the value
  2376.         }
  2377.         else if ( lcml.startsWith ( "icy-name:" ) )
  2378.         {
  2379.           icyname = metaline.substring(9) ;            // Get station name
  2380.           icyname.trim() ;                             // Remove leading and trailing spaces
  2381.           displayinfo ( icyname.c_str(), 60, 68,
  2382.                         YELLOW ) ;                     // Show station name at position 60
  2383.         }
  2384.         else if ( lcml.startsWith ( "transfer-encoding:" ) )
  2385.         {
  2386.           // Station provides chunked transfer
  2387.           if ( lcml.endsWith ( "chunked" ) )
  2388.           {
  2389.             chunked = true ;                           // Remember chunked transfer mode
  2390.             chunkcount = 0 ;                           // Expect chunkcount in DATA
  2391.           }
  2392.         }
  2393.       }
  2394.       metaline = "" ;                                  // Reset this line
  2395.       if ( ( LFcount == 2 ) && ctseen )                // Some data seen and a double LF?
  2396.       {
  2397.         dbgprint ( "Switch to DATA, bitrate is %d"     // Show bitrate
  2398.                    ", metaint is %d",                  // and metaint
  2399.                    bitrate, metaint ) ;
  2400.         datamode = DATA ;                              // Expecting data now
  2401.         datacount = metaint ;                          // Number of bytes before first metadata
  2402.         bufcnt = 0 ;                                   // Reset buffer count
  2403.         vs1053player.startSong() ;                     // Start a new song
  2404.       }
  2405.     }
  2406.     else
  2407.     {
  2408.       metaline += (char)b ;                            // Normal character, put new char in metaline
  2409.       LFcount = 0 ;                                    // Reset double CRLF detection
  2410.     }
  2411.     return ;
  2412.   }
  2413.   if ( datamode == METADATA )                          // Handle next byte of metadata
  2414.   {
  2415.     if ( firstmetabyte )                               // First byte of metadata?
  2416.     {
  2417.       firstmetabyte = false ;                          // Not the first anymore
  2418.       metacount = b * 16 + 1 ;                         // New count for metadata including length byte
  2419.       if ( metacount > 1 )
  2420.       {
  2421.         dbgprint ( "Metadata block %d bytes",
  2422.                    metacount - 1 ) ;                   // Most of the time there are zero bytes of metadata
  2423.       }
  2424.       metaline = "" ;                                  // Set to empty
  2425.     }
  2426.     else
  2427.     {
  2428.       metaline += (char)b ;                            // Normal character, put new char in metaline
  2429.     }
  2430.     if ( --metacount == 0 )
  2431.     {
  2432.       if ( metaline.length() )                         // Any info present?
  2433.       {
  2434.         // metaline contains artist and song name.  For example:
  2435.         // "StreamTitle='Don McLean - American Pie';StreamUrl='';"
  2436.         // Sometimes it is just other info like:
  2437.         // "StreamTitle='60s 03 05 Magic60s';StreamUrl='';"
  2438.         // Isolate the StreamTitle, remove leading and trailing quotes if present.
  2439.         showstreamtitle ( metaline.c_str() ) ;         // Show artist and title if present in metadata
  2440.       }
  2441.       if ( metaline.length() > 1500 )                  // Unlikely metaline length?
  2442.       {
  2443.         dbgprint ( "Metadata block too long! Skipping all Metadata from now on." ) ;
  2444.         metaint = 0 ;                                  // Probably no metadata
  2445.         metaline = "" ;                                // Do not waste memory on this
  2446.       }
  2447.       datacount = metaint ;                            // Reset data count
  2448.       bufcnt = 0 ;                                     // Reset buffer count
  2449.       datamode = DATA ;                                // Expecting data
  2450.     }
  2451.   }
  2452.   if ( datamode == PLAYLISTINIT )                      // Initialize for receive .m3u file
  2453.   {
  2454.     // We are going to use metadata to read the lines from the .m3u file
  2455.     metaline = "" ;                                    // Prepare for new line
  2456.     LFcount = 0 ;                                      // For detection end of header
  2457.     datamode = PLAYLISTHEADER ;                        // Handle playlist data
  2458.     playlistcnt = 1 ;                                  // Reset for compare
  2459.     totalcount = 0 ;                                   // Reset totalcount
  2460.     dbgprint ( "Read from playlist" ) ;
  2461.   }
  2462.   if ( datamode == PLAYLISTHEADER )                    // Read header
  2463.   {
  2464.     if ( ( b > 0x7F ) ||                               // Ignore unprintable characters
  2465.          ( b == '\r' ) ||                              // Ignore CR
  2466.          ( b == '\0' ) )                               // Ignore NULL
  2467.     {
  2468.       // Yes, ignore
  2469.     }
  2470.     else if ( b == '\n' )                              // Linefeed ?
  2471.     {
  2472.       LFcount++ ;                                      // Count linefeeds
  2473.       dbgprint ( "Playlistheader: %s",                 // Show playlistheader
  2474.                  metaline.c_str() ) ;
  2475.       metaline = "" ;                                  // Ready for next line
  2476.       if ( LFcount == 2 )
  2477.       {
  2478.         dbgprint ( "Switch to PLAYLISTDATA" ) ;
  2479.         datamode = PLAYLISTDATA ;                      // Expecting data now
  2480.         return ;
  2481.       }
  2482.     }
  2483.     else
  2484.     {
  2485.       metaline += (char)b ;                            // Normal character, put new char in metaline
  2486.       LFcount = 0 ;                                    // Reset double CRLF detection
  2487.     }
  2488.   }
  2489.   if ( datamode == PLAYLISTDATA )                      // Read next byte of .m3u file data
  2490.   {
  2491.     if ( ( b > 0x7F ) ||                               // Ignore unprintable characters
  2492.          ( b == '\r' ) ||                              // Ignore CR
  2493.          ( b == '\0' ) )                               // Ignore NULL
  2494.     {
  2495.       // Yes, ignore
  2496.     }
  2497.     else if ( b == '\n' )                              // Linefeed ?
  2498.     {
  2499.       dbgprint ( "Playlistdata: %s",                   // Show playlistheader
  2500.                  metaline.c_str() ) ;
  2501.       if ( metaline.length() < 5 )                     // Skip short lines
  2502.       {
  2503.         return ;
  2504.       }
  2505.       if ( metaline.indexOf ( "#EXTINF:" ) >= 0 )      // Info?
  2506.       {
  2507.         if ( playlist_num == playlistcnt )             // Info for this entry?
  2508.         {
  2509.           inx = metaline.indexOf ( "," ) ;             // Comma in this line?
  2510.           if ( inx > 0 )
  2511.           {
  2512.             // Show artist and title if present in metadata
  2513.             showstreamtitle ( metaline.substring ( inx + 1 ).c_str(), true ) ;
  2514.           }
  2515.         }
  2516.       }
  2517.       if ( metaline.startsWith ( "#" ) )               // Commentline?
  2518.       {
  2519.         metaline = "" ;
  2520.         return ;                                       // Ignore commentlines
  2521.       }
  2522.       // Now we have an URL for a .mp3 file or stream.  Is it the rigth one?
  2523.       dbgprint ( "Entry %d in playlist found: %s", playlistcnt, metaline.c_str() ) ;
  2524.       if ( playlist_num == playlistcnt  )
  2525.       {
  2526.         inx = metaline.indexOf ( "http://" ) ;         // Search for "http://"
  2527.         if ( inx >= 0 )                                // Does URL contain "http://"?
  2528.         {
  2529.           host = metaline.substring ( inx + 7 ) ;      // Yes, remove it and set host
  2530.         }
  2531.         else
  2532.         {
  2533.           host = metaline ;                            // Yes, set new host
  2534.         }
  2535.         connecttohost() ;                              // Connect to it
  2536.       }
  2537.       metaline = "" ;
  2538.       host = playlist ;                                // Back to the .m3u host
  2539.       playlistcnt++ ;                                  // Next entry in playlist
  2540.     }
  2541.     else
  2542.     {
  2543.       metaline += (char)b ;                            // Normal character, add it to metaline
  2544.     }
  2545.     return ;
  2546.   }
  2547. }
  2548.  
  2549.  
  2550. //******************************************************************************************
  2551. //                             G E T C O N T E N T T Y P E                                 *
  2552. //******************************************************************************************
  2553. // Returns the contenttype of a file to send.                                              *
  2554. //******************************************************************************************
  2555. String getContentType ( String filename )
  2556. {
  2557.   if      ( filename.endsWith ( ".html" ) ) return "text/html" ;
  2558.   else if ( filename.endsWith ( ".png"  ) ) return "image/png" ;
  2559.   else if ( filename.endsWith ( ".gif"  ) ) return "image/gif" ;
  2560.   else if ( filename.endsWith ( ".jpg"  ) ) return "image/jpeg" ;
  2561.   else if ( filename.endsWith ( ".ico"  ) ) return "image/x-icon" ;
  2562.   else if ( filename.endsWith ( ".css"  ) ) return "text/css" ;
  2563.   else if ( filename.endsWith ( ".zip"  ) ) return "application/x-zip" ;
  2564.   else if ( filename.endsWith ( ".gz"   ) ) return "application/x-gzip" ;
  2565.   else if ( filename.endsWith ( ".mp3"  ) ) return "audio/mpeg" ;
  2566.   else if ( filename.endsWith ( ".pw"   ) ) return "" ;              // Passwords are secret
  2567.   return "text/plain" ;
  2568. }
  2569.  
  2570.  
  2571. //******************************************************************************************
  2572. //                         H A N D L E F I L E U P L O A D                                 *
  2573. //******************************************************************************************
  2574. // Handling of upload request.  Write file to SPIFFS.                                      *
  2575. //******************************************************************************************
  2576. void handleFileUpload ( AsyncWebServerRequest *request, String filename,
  2577.                         size_t index, uint8_t *data, size_t len, bool final )
  2578. {
  2579.   String          path ;                              // Filename including "/"
  2580.   static File     f ;                                 // File handle output file
  2581.   char*           reply ;                             // Reply for webserver
  2582.   static uint32_t t ;                                 // Timer for progress messages
  2583.   uint32_t        t1 ;                                // For compare
  2584.   static uint32_t totallength ;                       // Total file length
  2585.   static size_t   lastindex ;                         // To test same index
  2586.  
  2587.   if ( index == 0 )
  2588.   {
  2589.     path = String ( "/" ) + filename ;                // Form SPIFFS filename
  2590.     SPIFFS.remove ( path ) ;                          // Remove old file
  2591.     f = SPIFFS.open ( path, "w" ) ;                   // Create new file
  2592.     t = millis() ;                                    // Start time
  2593.     totallength = 0 ;                                 // Total file lengt still zero
  2594.     lastindex = 0 ;                                   // Prepare test
  2595.   }
  2596.   t1 = millis() ;                                     // Current timestamp
  2597.   // Yes, print progress
  2598.   dbgprint ( "File upload %s, t = %d msec, len %d, index %d",
  2599.              filename.c_str(), t1 - t, len, index ) ;
  2600.   if ( len )                                          // Something to write?
  2601.   {
  2602.     if ( ( index != lastindex ) || ( index == 0 ) )   // New chunk?
  2603.     {
  2604.       f.write ( data, len ) ;                         // Yes, transfer to SPIFFS
  2605.       totallength += len ;                            // Update stored length
  2606.       lastindex = index ;                             // Remenber this part
  2607.     }
  2608.   }
  2609.   if ( final )                                        // Was this last chunk?
  2610.   {
  2611.     f.close() ;                                       // Yes, clode the file
  2612.     reply = dbgprint ( "File upload %s, %d bytes finished",
  2613.                        filename.c_str(), totallength ) ;
  2614.     request->send ( 200, "", reply ) ;
  2615.   }
  2616. }
  2617.  
  2618.  
  2619. //******************************************************************************************
  2620. //                                H A N D L E F S F                                        *
  2621. //******************************************************************************************
  2622. // Handling of requesting files from the SPIFFS/PROGMEM. Example: /favicon.ico             *
  2623. //******************************************************************************************
  2624. void handleFSf ( AsyncWebServerRequest* request, const String& filename )
  2625. {
  2626.   static String          ct ;                           // Content type
  2627.   AsyncWebServerResponse *response ;                    // For extra headers
  2628.  
  2629.   dbgprint ( "FileRequest received %s", filename.c_str() ) ;
  2630.   ct = getContentType ( filename ) ;                    // Get content type
  2631.   if ( ( ct == "" ) || ( filename == "" ) )             // Empty is illegal
  2632.   {
  2633.     request->send ( 404, "text/plain", "File not found" ) ;
  2634.   }
  2635.   else
  2636.   {
  2637.     if ( filename.indexOf ( "index.html" ) >= 0 )       // Index page is in PROGMEM
  2638.     {
  2639.       response = request->beginResponse_P ( 200, ct, index_html ) ;
  2640.     }
  2641.     else if ( filename.indexOf ( "radio.css" ) >= 0 )   // CSS file is in PROGMEM
  2642.     {
  2643.       response = request->beginResponse_P ( 200, ct, radio_css ) ;
  2644.     }
  2645.     else if ( filename.indexOf ( "config.html" ) >= 0 ) // Config page is in PROGMEM
  2646.     {
  2647.       response = request->beginResponse_P ( 200, ct, config_html ) ;
  2648.     }
  2649.     else if ( filename.indexOf ( "about.html" ) >= 0 )  // About page is in PROGMEM
  2650.     {
  2651.       response = request->beginResponse_P ( 200, ct, about_html ) ;
  2652.     }
  2653.     else if ( filename.indexOf ( "favicon.ico" ) >= 0 ) // Favicon icon is in PROGMEM
  2654.     {
  2655.       response = request->beginResponse_P ( 200, ct, favicon_ico, sizeof ( favicon_ico ) ) ;
  2656.     }
  2657.     else
  2658.     {
  2659.       response = request->beginResponse ( SPIFFS, filename, ct ) ;
  2660.     }
  2661.     // Add extra headers
  2662.     response->addHeader ( "Server", NAME ) ;
  2663.     response->addHeader ( "Cache-Control", "max-age=3600" ) ;
  2664.     response->addHeader ( "Last-Modified", VERSION ) ;
  2665.     request->send ( response ) ;
  2666.   }
  2667.   dbgprint ( "Response sent" ) ;
  2668. }
  2669.  
  2670.  
  2671. //******************************************************************************************
  2672. //                                H A N D L E F S                                          *
  2673. //******************************************************************************************
  2674. // Handling of requesting files from the SPIFFS. Example: /favicon.ico                     *
  2675. //******************************************************************************************
  2676. void handleFS ( AsyncWebServerRequest* request )
  2677. {
  2678.   handleFSf ( request, request->url() ) ;               // Rest of handling
  2679. }
  2680.  
  2681.  
  2682. //******************************************************************************************
  2683. //                             A N A L Y Z E C M D                                         *
  2684. //******************************************************************************************
  2685. // Handling of the various commands from remote webclient, Serial or MQTT.                 *
  2686. // Version for handling string with: <parameter>=<value>                                   *
  2687. //******************************************************************************************
  2688. char* analyzeCmd ( const char* str )
  2689. {
  2690.   char*  value ;                                 // Points to value after equalsign in command
  2691.  
  2692.   value = strstr ( str, "=" ) ;                  // See if command contains a "="
  2693.   if ( value )
  2694.   {
  2695.     *value = '\0' ;                              // Separate command from value
  2696.     value++ ;                                    // Points to value after "="
  2697.   }
  2698.   else
  2699.   {
  2700.     value = (char*) "0" ;                        // No value, assume zero
  2701.   }
  2702.   return  analyzeCmd ( str, value ) ;            // Analyze command and handle it
  2703. }
  2704.  
  2705.  
  2706. //******************************************************************************************
  2707. //                                 C H O M P                                               *
  2708. //******************************************************************************************
  2709. // Do some filtering on de inputstring:                                                    *
  2710. //  - String comment part (starting with "#").                                             *
  2711. //  - Strip trailing CR.                                                                   *
  2712. //  - Strip leading spaces.                                                                *
  2713. //  - Strip trailing spaces.                                                               *
  2714. //******************************************************************************************
  2715. String chomp ( String str )
  2716. {
  2717.   int   inx ;                                         // Index in de input string
  2718.  
  2719.   if ( ( inx = str.indexOf ( "#" ) ) >= 0 )           // Comment line or partial comment?
  2720.   {
  2721.     str.remove ( inx ) ;                              // Yes, remove
  2722.   }
  2723.   str.trim() ;                                        // Remove spaces and CR
  2724.   return str ;                                        // Return the result
  2725. }
  2726.  
  2727.  
  2728. //******************************************************************************************
  2729. //                             A N A L Y Z E C M D                                         *
  2730. //******************************************************************************************
  2731. // Handling of the various commands from remote webclient, serial or MQTT.                 *
  2732. // par holds the parametername and val holds the value.                                    *
  2733. // "wifi_00" and "preset_00" may appear more than once, like wifi_01, wifi_02, etc.        *
  2734. // Examples with available parameters:                                                     *
  2735. //   preset     = 12                        // Select start preset to connect to           *
  2736. //   preset_00  = <mp3 stream>              // Specify station for a preset 00-99 *)       *
  2737. //   volume     = 95                        // Percentage between 0 and 100                *
  2738. //   upvolume   = 2                         // Add percentage to current volume            *
  2739. //   downvolume = 2                         // Subtract percentage from current volume     *
  2740. //   toneha     = <0..15>                   // Setting treble gain                         *
  2741. //   tonehf     = <0..15>                   // Setting treble frequency                    *
  2742. //   tonela     = <0..15>                   // Setting bass gain                           *
  2743. //   tonelf     = <0..15>                   // Setting treble frequency                    *
  2744. //   station    = <mp3 stream>              // Select new station (will not be saved)      *
  2745. //   station    = <URL>.mp3                 // Play standalone .mp3 file (not saved)       *
  2746. //   station    = <URL>.m3u                 // Select playlist (will not be saved)         *
  2747. //   stop                                   // Stop playing                                *
  2748. //   resume                                 // Resume playing                              *
  2749. //   mute                                   // Mute the music                              *
  2750. //   unmute                                 // Unmute the music                            *
  2751. //   wifi_00    = mySSID/mypassword         // Set WiFi SSID and password *)               *
  2752. //   mqttbroker = mybroker.com              // Set MQTT broker to use *)                   *
  2753. //   mqttport   = 1883                      // Set MQTT port to use, default 1883 *)       *
  2754. //   mqttuser   = myuser                    // Set MQTT user for authentication *)         *
  2755. //   mqttpasswd = mypassword                // Set MQTT password for authentication *)     *
  2756. //   mqtttopic  = mytopic                   // Set MQTT topic to subscribe to *)           *
  2757. //   mqttpubtopic = mypubtopic              // Set MQTT topic to publish to *)             *
  2758. //   status                                 // Show current URL to play                    *
  2759. //   testfile   = <file on SPIFFS>          // Test SPIFFS reads for debugging purpose     *
  2760. //   test                                   // For test purposes                           *
  2761. //   debug      = 0 or 1                    // Switch debugging on or off                  *
  2762. //   reset                                  // Restart the ESP8266                         *
  2763. //   analog                                 // Show current analog input                   *
  2764. // Commands marked with "*)" are sensible in ini-file only                                 *
  2765. // Note that it is adviced to avoid expressions as the argument for the abs function.      *
  2766. //******************************************************************************************
  2767. char* analyzeCmd ( const char* par, const char* val )
  2768. {
  2769.   String             argument ;                       // Аргумент как строка
  2770.   String             value ;                          // Значение аргумента в виде строки
  2771.   int                ivalue ;                         // Value of argument as an integer
  2772.   static char        reply[250] ;                     // Reply to client, will be returned
  2773.   uint8_t            oldvol ;                         // Current volume
  2774.   bool               relative ;                       // Relative argument (+ or -)
  2775.   int                inx ;                            // Index in string
  2776.  
  2777.   strcpy ( reply, "Команду прийнято" ) ;              // Default reply
  2778.   argument = chomp ( par ) ;                          // Get the argument
  2779.   if ( argument.length() == 0 )                       // Empty commandline (comment)?
  2780.   {
  2781.     return reply ;                                    // Ignore
  2782.   }
  2783.   argument.toLowerCase() ;                            // Force to lower case
  2784.   value = chomp ( val ) ;                             // Get the specified value
  2785.   ivalue = value.toInt() ;                            // Also as an integer
  2786.   ivalue = abs ( ivalue ) ;                           // Make it absolute
  2787.   relative = argument.indexOf ( "up" ) == 0 ;         // + relative setting?
  2788.   if ( argument.indexOf ( "down" ) == 0 )             // - relative setting?
  2789.   {
  2790.     relative = true ;                                 // It's relative
  2791.     ivalue = - ivalue ;                               // But with negative value
  2792.   }
  2793.   if ( value.startsWith ( "http://" ) )               // Does (possible) URL contain "http://"?
  2794.   {
  2795.     value.remove ( 0, 7 ) ;                           // Yes, remove it
  2796.   }
  2797.   if ( value.length() )
  2798.   {
  2799.     dbgprint ( "Command: %s with parameter %s",
  2800.                argument.c_str(), value.c_str() ) ;
  2801.   }
  2802.   else
  2803.   {
  2804.     dbgprint ( "Command: %s (without parameter)",
  2805.                argument.c_str() ) ;
  2806.   }
  2807.   if ( argument.indexOf ( "volume" ) >= 0 )           // Volume setting?
  2808.   {
  2809.     // Volume may be of the form "upvolume", "downvolume" or "volume" for relative or absolute setting
  2810.     oldvol = vs1053player.getVolume() ;               // Get current volume
  2811.     if ( relative )                                   // + relative setting?
  2812.     {
  2813.       ini_block.reqvol = oldvol + ivalue ;            // Up by 0.5 or more dB
  2814.     }
  2815.     else
  2816.     {
  2817.       ini_block.reqvol = ivalue ;                     // Absolue setting
  2818.     }
  2819.     if ( ini_block.reqvol > 100 )
  2820.     {
  2821.       ini_block.reqvol = 100 ;                        // Limit to normal values
  2822.     }
  2823.     sprintf ( reply, "Поточна гучність %d відсотків",              // Reply new volume
  2824.               ini_block.reqvol ) ;
  2825.   }
  2826.   else if ( argument == "mute" )                      // Mute request
  2827.   {
  2828.     muteflag = true ;                                 // Request volume to zero
  2829.   }
  2830.   else if ( argument == "unmute" )                    // Unmute request?
  2831.   {
  2832.     muteflag = false ;                                // Request normal volume
  2833.   }
  2834.   else if ( argument.indexOf ( "preset" ) >= 0 )      // Preset station?
  2835.   {
  2836.     if ( !argument.startsWith ( "preset_" ) )         // But not a station URL
  2837.     {
  2838.       if ( relative )                                 // Relative argument?
  2839.       {
  2840.         ini_block.newpreset += ivalue ;               // Yes, adjust currentpreset
  2841.       }
  2842.       else
  2843.       {
  2844.         ini_block.newpreset = ivalue ;                // Otherwise set preset station
  2845.       }
  2846.       int ether1 = ini_block.newpreset + 1;
  2847.       sprintf ( reply, "Обрано станцію %s", stations_names[ether1 - 1].c_str()) ; // Reply new preset
  2848.       playlist_num = 0 ;
  2849.     }
  2850.   }
  2851.   else if ( argument == "stop" )                      // Stop requested?
  2852.   {
  2853.     if ( datamode & ( HEADER | DATA | METADATA | PLAYLISTINIT |
  2854.                       PLAYLISTHEADER | PLAYLISTDATA ) )
  2855.  
  2856.     {
  2857.       datamode = STOPREQD ;                           // Request STOP
  2858.     }
  2859.     else
  2860.     {
  2861.       strcpy ( reply, "Програвання вже зупинено!" );  // Error reply
  2862.     }
  2863.   }
  2864.   else if ( argument == "resume" )                    // Request to resume?
  2865.   {
  2866.     if ( datamode == STOPPED )                        // Yes, are we stopped?
  2867.     {
  2868.       hostreq = true ;                                // Yes, request restart
  2869.     }
  2870.   }
  2871.   else if ( argument == "station" )                   // Station in the form address:port
  2872.   {
  2873.     if ( datamode & ( HEADER | DATA | METADATA | PLAYLISTINIT |
  2874.                       PLAYLISTHEADER | PLAYLISTDATA ) )
  2875.     {
  2876.       datamode = STOPREQD ;                           // Request STOP
  2877.     }
  2878.     host = value ;                                    // Save it for storage and selection later
  2879.     hostreq = true ;                                  // Force this station as new preset
  2880.     sprintf ( reply,
  2881.               "Програвання ресурсу %s прийнято до виконання",       // Format reply
  2882.               host.c_str() ) ;
  2883.   }
  2884.   else if ( argument == "xml" )
  2885.   {
  2886.     if ( datamode & ( HEADER | DATA | METADATA | PLAYLISTINIT |
  2887.                       PLAYLISTHEADER | PLAYLISTDATA ) )
  2888.     {
  2889.       datamode = STOPREQD ;                           // Request STOP
  2890.     }
  2891.     host = value ;                                    // Save it for storage and selection later
  2892.     xmlreq = true ;                                   // Run XML parsing process.
  2893.     sprintf ( reply, "Нову XML станцію %s прийнято", host.c_str() ) ;       // Format reply
  2894.   }
  2895.   else if ( argument == "status" )                    // Status request
  2896.   {
  2897.     if ( datamode == STOPPED )
  2898.     {
  2899.       sprintf ( reply, "Програвання зупинено" ) ;           // Format reply
  2900.     }
  2901.     else
  2902.     {
  2903.       sprintf ( reply, "%s - %s", icyname.c_str(),
  2904.                 icystreamtitle.c_str() ) ;            // Streamtitle from metadata
  2905.     }
  2906.   }
  2907.   else if ( argument.startsWith ( "reset" ) )         // Reset request
  2908.   {
  2909.     resetreq = true ;                                    // Reset all
  2910.   }
  2911.   else if ( argument == "testfile" )                  // Testfile command?
  2912.   {
  2913.     testfilename = value ;                            // Yes, set file to test accordingly
  2914.   }
  2915.   else if ( argument == "test" )                      // Test command
  2916.   {
  2917.     sprintf ( reply, "Вільна пам'ять %d, буфер %d, потоків %d",
  2918.               system_get_free_heap_size(), rcount, mp3client->available() ) ;
  2919.   }
  2920.   // Commands for bass/treble control
  2921.   else if ( argument.startsWith ( "tone" ) )          // Tone command
  2922.   {
  2923.     if ( argument.indexOf ( "ha" ) > 0 )              // High amplitue? (for treble)
  2924.     {
  2925.       ini_block.rtone[0] = ivalue ;                   // Yes, prepare to set ST_AMPLITUDE
  2926.       float to_demoa[] = {0, 1.5, 3, 4.5, 6, 7.5, 9, 10.5, -12, -10.5, -9, -7.5, -6, -4.5, -3, -1.5};
  2927.       //sprintf ( reply, "Значення тембру ВЧ на заданій частоті встановлено %d децибел", to_demoa[ivalue] ) ;
  2928.       dtostrf (to_demoa[ivalue], 2, 1, reply);
  2929.       char cat[100] = "Значення тембру ВЧ на заданій частоті встановлено ";
  2930.       strcat(cat, reply);
  2931.       strcat(cat, " децибел");
  2932.       strcpy(reply, cat);
  2933.     }
  2934.     if ( argument.indexOf ( "hf" ) > 0 )              // High frequency? (for treble)
  2935.     {
  2936.       ini_block.rtone[1] = ivalue ;                   // Yes, prepare to set ST_FREQLIMIT
  2937.       sprintf ( reply, "Обрано частоту для корекції тембру ВЧ значенням %d кілогерц", ivalue ) ;
  2938.     }
  2939.     if ( argument.indexOf ( "la" ) > 0 )              // Low amplitue? (for bass)
  2940.     {
  2941.       ini_block.rtone[2] = ivalue ;                   // Yes, prepare to set SB_AMPLITUDE
  2942.       sprintf ( reply, "Підсилення тембру НЧ на заданій частоті встановлено +%d децибел", ivalue ) ;
  2943.     }
  2944.     if ( argument.indexOf ( "lf" ) > 0 )              // High frequency? (for bass)
  2945.     {
  2946.       ini_block.rtone[3] = ivalue ;                   // Yes, prepare to set SB_FREQLIMIT
  2947.       int to_demob = (ivalue - 1) * 10;
  2948.       sprintf ( reply, "Обрано частоту для корекції тембру НЧ значенням %d герц", to_demob ) ;
  2949.     }
  2950.     reqtone = true ;                                  // Set change request
  2951.   }
  2952.   else if ( argument == "rate" )                      // Rate command?
  2953.   {
  2954.     vs1053player.AdjustRate ( ivalue ) ;              // Yes, adjust
  2955.   }
  2956.   else if ( argument.startsWith ( "mqtt" ) )          // Parameter fo MQTT?
  2957.   {
  2958.     strcpy ( reply, "Значення MQTT брокера змінено. Збережіть та перезапустіть для отримання ефекту" ) ;
  2959.     if ( argument.indexOf ( "broker" ) > 0 )          // Broker specified?
  2960.     {
  2961.       ini_block.mqttbroker = value.c_str() ;          // Yes, set broker accordingly
  2962.     }
  2963.     else if ( argument.indexOf ( "port" ) > 0 )       // Port specified?
  2964.     {
  2965.       ini_block.mqttport = ivalue ;                   // Yes, set port user accordingly
  2966.     }
  2967.     else if ( argument.indexOf ( "user" ) > 0 )       // User specified?
  2968.     {
  2969.       ini_block.mqttuser = value ;                    // Yes, set user accordingly
  2970.     }
  2971.     else if ( argument.indexOf ( "passwd" ) > 0 )     // Password specified?
  2972.     {
  2973.       ini_block.mqttpasswd = value.c_str() ;          // Yes, set broker password accordingly
  2974.     }
  2975.     else if ( argument.indexOf ( "pubtopic" ) > 0 )   // Publish topic specified?
  2976.     {
  2977.       ini_block.mqttpubtopic = value.c_str() ;        // Yes, set broker password accordingly
  2978.     }
  2979.     else if ( argument.indexOf ( "topic" ) > 0 )      // Topic specified?
  2980.     {
  2981.       ini_block.mqtttopic = value.c_str() ;           // Yes, set broker topic accordingly
  2982.     }
  2983.   }
  2984.   else if ( argument == "debug" )                     // debug on/off request?
  2985.   {
  2986.     DEBUG = ivalue ;                                  // Yes, set flag accordingly
  2987.   }
  2988.   else if ( argument == "analog" )                    // Show analog request?
  2989.   {
  2990.     sprintf ( reply, "Аналоговий вхід = %d одиниць",  // Read the analog input for test
  2991.               analogRead ( A0 ) ) ;
  2992.   }
  2993.   else if ( argument.startsWith ( "wifi" ) )          // WiFi SSID and passwd?
  2994.   {
  2995.     inx = value.indexOf ( "/" ) ;                     // Find separator between ssid and password
  2996.     // Was this the strongest SSID or the only acceptable?
  2997.     if ( num_an == 1 )
  2998.     {
  2999.       ini_block.ssid = value.substring ( 0, inx ) ;   // Only one.  Set as the strongest
  3000.     }
  3001.     if ( value.substring ( 0, inx ) == ini_block.ssid )
  3002.     {
  3003.       ini_block.passwd = value.substring ( inx + 1 ) ; // Yes, set password
  3004.     }
  3005.   }
  3006.   else if ( argument == "getnetworks" )               // List all WiFi networks?
  3007.   {
  3008.     sprintf ( reply, networks.c_str() ) ;             // Reply is SSIDs
  3009.   }
  3010.   else if ( argument == "actnet")
  3011.   {
  3012.     sprintf ( reply, actualnetwork.c_str() ) ;
  3013.   }
  3014.   else if (argument == "numofnets")
  3015.   {
  3016.     sprintf ( reply, "Кількість доступних мереж %d", num_an) ;
  3017.   }
  3018.   else
  3019.   {
  3020.     sprintf ( reply, "%s викликано з неприйнятним параметром: %s",
  3021.               NAME, argument.c_str() ) ;
  3022.   }
  3023.   return reply ;                                      // Return reply to the caller
  3024. }
  3025.  
  3026.  
  3027. //******************************************************************************************
  3028. //                             H A N D L E C M D                                           *
  3029. //******************************************************************************************
  3030. // Handling of the various commands from remote (case sensitive). All commands have the    *
  3031. // form "/?parameter[=value]".  Example: "/?volume=50".                                    *
  3032. // The startpage will be returned if no arguments are given.                               *
  3033. // Multiple parameters are ignored.  An extra parameter may be "version=<random number>"   *
  3034. // in order to prevent browsers like Edge and IE to use their cache.  This "version" is    *
  3035. // ignored.                                                                                *
  3036. // Example: "/?upvolume=5&version=0.9775479450590543"                                      *
  3037. // The save and the list commands are handled specially.                                   *
  3038. //******************************************************************************************
  3039. void handleCmd ( AsyncWebServerRequest* request )
  3040. {
  3041.   AsyncWebParameter* p ;                                // Points to parameter structure
  3042.   static String      argument ;                         // Next argument in command
  3043.   static String      value ;                            // Value of an argument
  3044.   const char*        reply ;                            // Reply to client
  3045.   //uint32_t         t ;                                // For time test
  3046.   int                params ;                           // Number of params
  3047.   static File        f ;                                // Handle for writing /radio.ini to SPIFFS
  3048.  
  3049.   //t = millis() ;                                      // Timestamp at start
  3050.   params = request->params() ;                          // Get number of arguments
  3051.   if ( params == 0 )                                    // Any arguments
  3052.   {
  3053.     if ( NetworkFound )
  3054.     {
  3055.       handleFSf ( request, String( "/index.html") ) ;   // No parameters, send the startpage
  3056.     }
  3057.     else
  3058.     {
  3059.       handleFSf ( request, String( "/config.html") ) ;  // Or the configuration page if in AP mode
  3060.     }
  3061.     return ;
  3062.   }
  3063.   p = request->getParam ( 0 ) ;                         // Get pointer to parameter structure
  3064.   argument = p->name() ;                                // Get the argument
  3065.   argument.toLowerCase() ;                              // Force to lower case
  3066.   value = p->value() ;                                  // Get the specified value
  3067.   // For the "save" command, the contents is the value of the next parameter
  3068.   if ( argument.startsWith ( "save" ) && ( params > 1 ) )
  3069.   {
  3070.     reply = "Error saving " INIFILENAME ;               // Default reply
  3071.     p = request->getParam ( 1 ) ;                       // Get pointer to next parameter structure
  3072.     if ( p->isPost() )                                  // Does it have a POST?
  3073.     {
  3074.       f = SPIFFS.open ( INIFILENAME, "w" ) ;            // Save to inifile
  3075.       if ( f )
  3076.       {
  3077.         f.print ( p->value() ) ;
  3078.         f.close() ;
  3079.         reply = dbgprint ( "%s saved", INIFILENAME ) ;
  3080.       }
  3081.     }
  3082.   }
  3083.   else if ( argument.startsWith ( "list" ) )            // List all presets?
  3084.   {
  3085.     dbgprint ( "list request from browser" ) ;
  3086.     request->send ( 200, "text/plain", presetlist ) ;   // Send the reply
  3087.     return ;
  3088.   }
  3089.   else
  3090.   {
  3091.     reply = analyzeCmd ( argument.c_str(),              // Analyze it
  3092.                          value.c_str() ) ;
  3093.   }
  3094.   request->send ( 200, "text/plain", reply ) ;          // Send the reply
  3095.   //t = millis() - t ;
  3096.   // If it takes too long to send a reply, we run into the "LmacRxBlk:1"-problem.
  3097.   // Reset the ESP8266.....
  3098.   //if ( t > 8000 )
  3099.   //{
  3100.   //  ESP.restart() ;                                   // Last resource
  3101.   //}
  3102. }
Add Comment
Please, Sign In to add comment