forked from Edzelf/ESP32-Radio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Esp32_radio.ino
5876 lines (5558 loc) · 289 KB
/
Esp32_radio.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//***************************************************************************************************
//* ESP32_Radio -- Webradio receiver for ESP32, VS1053 MP3 module and optional display. *
//* By Ed Smallenburg. *
//***************************************************************************************************
// ESP32 libraries used:
// - WiFiMulti
// - nvs
// - Adafruit_ST7735
// - ArduinoOTA
// - PubSubClientenc_dt_pin
// - SD
// - FS
// - update
// A library for the VS1053 (for ESP32) is not available (or not easy to find). Therefore
// a class for this module is derived from the maniacbug library and integrated in this sketch.
//
// See http://www.internet-radio.com for suitable stations. Add the stations of your choice
// to the preferences in either Esp32_radio_init.ino sketch or through the webinterface.
//
// Brief description of the program:
// First a suitable WiFi network is found and a connection is made.
// Then a connection will be made to a shoutcast server. The server starts with some
// info in the header in readable ascii, ending with a double CRLF, like:
// icy-name:Classic Rock Florida - SHE Radio
// icy-genre:Classic Rock 60s 70s 80s Oldies Miami South Florida
// icy-url:http://www.ClassicRockFLorida.com
// content-type:audio/mpeg
// icy-pub:1
// icy-metaint:32768 - Metadata after 32768 bytes of MP3-data
// icy-br:128 - in kb/sec (for Ogg this is like "icy-br=Quality 2"
//
// After de double CRLF is received, the server starts sending mp3- or Ogg-data. For mp3, this
// data may contain metadata (non mp3) after every "metaint" mp3 bytes.
// The metadata is empty in most cases, but if any is available the content will be
// presented on the TFT.
// Pushing an input button causes the player to execute a programmable command.
//
// The display used is a Chinese 1.8 color TFT module 128 x 160 pixels.
// Now there is room for 26 characters per line and 16 lines.
// Software will work without installing the display.
// Other displays are also supported. See documentation.
// The SD card interface of the module may be used to play mp3-tracks on the SD card.
//
// For configuration of the WiFi network(s): see the global data section further on.
//
// The VSPI interface is used for VS1053, TFT and SD.
//
// Wiring. Note that this is just an example. Pins (except 18,19 and 23 of the SPI interface)
// can be configured in the config page of the web interface.
// ESP32dev Signal Wired to LCD Wired to VS1053 SDCARD Wired to the rest
// -------- ------ -------------- ------------------- ------ ---------------
// GPIO32 - pin 1 XDCS - -
// GPIO5 - pin 2 XCS - -
// GPIO4 - pin 4 DREQ - -
// GPIO2 pin 3 D/C or A0 - - -
// GPIO22 - - CS -
// GPIO16 RXD2 - - - TX of NEXTION (if in use)
// GPIO17 TXD2 - - - RX of NEXTION (if in use)
// GPIO18 SCK pin 5 CLK or SCK pin 5 SCK CLK -
// GPIO19 MISO - pin 7 MISO MISO -
// GPIO23 MOSI pin 4 DIN or SDA pin 6 MOSI MOSI -
// GPIO15 pin 2 CS - - -
// GPI03 RXD0 - - - Reserved serial input
// GPIO1 TXD0 - - - Reserved serial output
// GPIO34 - - - - Optional pull-up resistor
// GPIO35 - - - - Infrared receiver VS1838B
// GPIO25 - - - - Rotary encoder CLK
// GPIO26 - - - - Rotary encoder DT
// GPIO27 - - - - Rotary encoder SW
// ------- ------ --------------- ------------------- ------ ----------------
// GND - pin 8 GND pin 8 GND Power supply GND
// VCC 5 V - pin 7 BL - Power supply
// VCC 5 V - pin 6 VCC pin 9 5V Power supply
// EN - pin 1 RST pin 3 XRST -
//
// 26-04-2017, ES: First set-up, derived from ESP8266 version.
// 08-05-2017, ES: Handling of preferences.
// 20-05-2017, ES: Handling input buttons and MQTT.
// 22-05-2017, ES: Save preset, volume and tone settings.
// 23-05-2017, ES: No more calls of non-iram functions on interrupts.
// 24-05-2017, ES: Support for featherboard.
// 26-05-2017, ES: Correction playing from .m3u playlist. Allow single hidden SSID.
// 30-05-2017, ES: Add SD card support (FAT format), volume indicator.
// 26-06-2017, ES: Correction: start in AP-mode if no WiFi networks configured.
// 28-06-2017, ES: Added IR interface.
// 30-06-2017, ES: Improved functions for SD card play.
// 03-07-2017, ES: Webinterface control page shows current settings.
// 04-07-2017, ES: Correction MQTT subscription. Keep playing during long operations.
// 08-07-2017, ES: More space for streamtitle on TFT.
// 18-07-2017, ES: Time Of Day on TFT.
// 19-07-2017, ES: Minor corrections.
// 26-07-2017, ES: Flexible pin assignment. Add rotary encoder switch.
// 27-07-2017, ES: Removed tinyXML library.
// 18-08-2017, Es: Minor corrections
// 28-08-2017, ES: Preferences for pins used for SPI bus,
// Corrected bug in handling programmable pins,
// Introduced touch pins.
// 30-08-2017, ES: Limit number of retries for MQTT connection.
// Added MDNS responder.
// 11-11-2017, ES: Increased ringbuffer. Measure bit rate.
// 13-11-2017, ES: Forward declarations.
// 16-11-2017, ES: Replaced ringbuffer by FreeRTOS queue, play function on second CPU,
// Included improved rotary switch routines supplied by fenyvesi,
// Better IR sensitivity.
// 30-11-2017, ES: Hide passwords in config page.
// 01-12-2017, ES: Better handling of playlist.
// 07-12-2017, ES: Faster handling of config screen.
// 08-12-2017, ES: More MQTT items to publish, added pin_shutdown.
// 13-12-2017, ES: Correction clear LCD.
// 15-12-2017, ES: Correction defaultprefs.h.
// 18-12-2017, ES: Stop playing during config.
// 02-01-2018, ES: Stop/resume is same command.
// 22-01-2018, ES: Read ADC (GPIO36) and display as a battery capacity percentage.
// 13-02-2018, ES: Stop timer during NVS write.
// 15-02-2018, ES: Correction writing wifi credentials in NVS.
// 03-03-2018, ES: Correction bug IR pinnumber.
// 05-03-2018, ES: Improved rotary encoder interface.
// 10-03-2018, ES: Minor corrections.
// 13-04-2018, ES: Guard against empty string send to TFT, thanks to Andreas Spiess.
// 16-04-2018, ES: ID3 tags handling while playing from SD.
// 25-04-2018, ES: Choice of several display boards.
// 30-04-2018, ES: Bugfix: crash when no IR is configured, no display without VS1063.
// 08-05-2018, ES: 1602 LCD display support (limited).
// 11-05-2018, ES: Bugfix: incidental crash in isr_enc_turn().
// 30-05-2018, ES: Bugfix: Assigned DRAM to global variables used in timer ISR.
// 31-05-2018, ES: Bugfix: Crashed if I2C is used, but pins not defined.
// 01-06-2018, ES: Run Playtask on CPU 0.
// 04-06-2018, ES: Made handling of playlistdata more tolerant (NDR).
// 09-06-2018, ES: Typo in defaultprefs.h.
// 10-06-2018, ES: Rotary encoder, interrupts on all 3 signals.
// 25-06-2018, ES: Timing of mp3loop. Limit read from stream to free queue space.
// 16-07-2018, ES: Correction tftset().
// 25-07-2018, ES: Correction touch pins.
// 30-07-2018, ES: Added GPIO39 and inversed shutdown pin. Thanks to fletsche.
// 31-07-2018, ES: Added TFT backlight control.
// 01-08-2018, ES: Debug info for IR. Shutdown amplifier if volume is 0.
// 02-08-2018, ES: Added support for ILI9341 display.
// 03-08-2018, ES: Added playlistposition for MQTT.
// 06-08-2018, ES: Correction negative time offset, OTA through remote host.
// 16-08-2018, ES: Added Nextion support.
// 18-09-2018, ES: "uppreset" and "downpreset" for MP3 player.
// 04-10-2018, ES: Fixed compile error OLED 64x128 display.
// 09-10-2018, ES: Bug fix xSemaphoreTake.
// 05-01-2019, ES: Fine tune datarate.
// 05-01-2019, ES: Basic http authentication. (just one user)
// 11-02-2019, ES: MQTT topic and subtopic size enlarged.
// 24-04-2019, ES: Do not lock SPI during gettime(). Calling gettime may take a long time.
// 15-05-2019, ES: MAX number of presets as a defined constant.
// 16-12-2019, ES: Modify of claimSPI() function for debugability.
// 21-12-2019, ES: Check chip version.
// 23-03-2020, ES: Allow playlists on SD card.
// 25-03-2020, ES: End of playlist: start over.
//
//
// Define the version number, also used for webserver as Last-Modified header and to
// check version for update. The format must be exactly as specified by the HTTP standard!
#define VERSION "Mon, 25 Mar 2020 10:45:00 GMT"
// ESP32-Radio can be updated (OTA) to the latest version from a remote server.
// The download uses the following server and files:
#define UPDATEHOST "smallenburg.nl" // Host for software updates
#define BINFILE "/Arduino/Esp32_radio.ino.bin" // Binary file name for update software
#define TFTFILE "/Arduino/ESP32-Radio.tft" // Binary file name for update NEXTION image
//
// Define (just one) type of display. See documentation.
// #define BLUETFT // Works also for RED TFT 128x160
//#define OLED // 64x128 I2C OLED
// #define DUMMYTFT // Dummy display
#define TM1637 // 7 Segment Clock Element
//#define LCD1602I2C // LCD 1602 display with I2C backpack
//#define ILI9341 // ILI9341 240*320
//#define NEXTION // Nextion display. Uses UART 2 (pin 16 and 17)
//
#include <nvs.h>
#include <PubSubClient.h>
#include <WiFiMulti.h>
#include <ESPmDNS.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#include <ArduinoOTA.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <esp_task_wdt.h>
#include <esp_partition.h>
#include <driver/adc.h>
#include <Update.h>
#include <base64.h>
// Number of entries in the queue
#define QSIZ 400
// Debug buffer size
#define DEBUG_BUFFER_SIZE 150
#define NVSBUFSIZE 150
// Access point name if connection to WiFi network fails. Also the hostname for WiFi and OTA.
// Note that the password of an AP must be at least as long as 8 characters.
// Also used for other naming.
#define NAME "ESP32Radio"
// Max number of presets in preferences
#define MAXPRESETS 200
// Maximum number of MQTT reconnects before give-up
#define MAXMQTTCONNECTS 5
// Adjust size of buffer to the longest expected string for nvsgetstr
#define NVSBUFSIZE 150
// Position (column) of time in topline relative to end
#define TIMEPOS -52
// SPI speed for SD card
#define SDSPEED 1000000
// Size of metaline buffer
#define METASIZ 1024
// Max. number of NVS keys in table
#define MAXKEYS 200
// Time-out [sec] for blanking TFT display (BL pin)
#define BL_TIME 45
//
// Subscription topics for MQTT. The topic will be pefixed by "PREFIX/", where PREFIX is replaced
// by the the mqttprefix in the preferences. The next definition will yield the topic
// "ESP32Radio/command" if mqttprefix is "ESP32Radio".
#define MQTT_SUBTOPIC "command" // Command to receive from MQTT
//
#define otaclient mp3client // OTA uses mp3client for connection to host
//**************************************************************************************************
// Forward declaration and prototypes of various functions. *
//**************************************************************************************************
void displaytime ( const char* str, uint16_t color = 0xFFFF ) ;
void showstreamtitle ( const char* ml, bool full = false ) ;
void handlebyte_ch ( uint8_t b ) ;
void handleFSf ( const String& pagename ) ;
void handleCmd() ;
char* dbgprint( const char* format, ... ) ;
const char* analyzeCmd ( const char* str ) ;
const char* analyzeCmd ( const char* par, const char* val ) ;
void chomp ( String &str ) ;
String httpheader ( String contentstype ) ;
bool nvssearch ( const char* key ) ;
void mp3loop() ;
void tftlog ( const char *str ) ;
void playtask ( void * parameter ) ; // Task to play the stream
void spftask ( void * parameter ) ; // Task for special functions
void gettime() ;
void reservepin ( int8_t rpinnr ) ;
//**************************************************************************************************
// Several structs. *
//**************************************************************************************************
//
struct scrseg_struct // For screen segments
{
bool update_req ; // Request update of screen
uint16_t color ; // Textcolor
uint16_t y ; // Begin of segment row
uint16_t height ; // Height of segment
String str ; // String to be displayed
} ;
enum qdata_type { QDATA, QSTARTSONG, QSTOPSONG } ; // datatyp in qdata_struct
struct qdata_struct
{
int datatyp ; // Identifier
__attribute__((aligned(4))) uint8_t buf[32] ; // Buffer for chunk
} ;
struct ini_struct
{
String mqttbroker ; // The name of the MQTT broker server
String mqttprefix ; // Prefix to use for topics
uint16_t mqttport ; // Port, default 1883
String mqttuser ; // User for MQTT authentication
String mqttpasswd ; // Password for MQTT authentication
uint8_t reqvol ; // Requested volume
uint8_t rtone[4] ; // Requested bass/treble settings
int16_t newpreset ; // Requested preset
String clk_server ; // Server to be used for time of day clock
int8_t clk_offset ; // Offset in hours with respect to UTC
int8_t clk_dst ; // Number of hours shift during DST
int8_t ir_pin ; // GPIO connected to output of IR decoder
int8_t enc_clk_pin ; // GPIO connected to CLK of rotary encoder
int8_t enc_dt_pin ; // GPIO connected to DT of rotary encoder
int8_t enc_sw_pin ; // GPIO connected to SW of rotary encoder
int8_t tft_cs_pin ; // GPIO connected to CS of TFT screen
int8_t tft_dc_pin ; // GPIO connected to D/C or A0 of TFT screen
int8_t tft_scl_pin ; // GPIO connected to SCL of i2c TFT screen
int8_t tft_sda_pin ; // GPIO connected to SDA of I2C TFT screen
int8_t tft_bl_pin ; // GPIO to activate BL of display
int8_t tft_blx_pin ; // GPIO to activate BL of display (inversed logic)
int8_t sd_cs_pin ; // GPIO connected to CS of SD card
int8_t vs_cs_pin ; // GPIO connected to CS of VS1053
int8_t vs_dcs_pin ; // GPIO connected to DCS of VS1053
int8_t vs_dreq_pin ; // GPIO connected to DREQ of VS1053
int8_t vs_shutdown_pin ; // GPIO to shut down the amplifier
int8_t vs_shutdownx_pin ; // GPIO to shut down the amplifier (inversed logic)
int8_t spi_sck_pin ; // GPIO connected to SPI SCK pin
int8_t spi_miso_pin ; // GPIO connected to SPI MISO pin
int8_t spi_mosi_pin ; // GPIO connected to SPI MOSI pin
uint16_t bat0 ; // ADC value for 0 percent battery charge
uint16_t bat100 ; // ADC value for 100 percent battery charge
} ;
struct WifiInfo_t // For list with WiFi info
{
uint8_t inx ; // Index as in "wifi_00"
char * ssid ; // SSID for an entry
char * passphrase ; // Passphrase for an entry
} ;
struct nvs_entry
{
uint8_t Ns ; // Namespace ID
uint8_t Type ; // Type of value
uint8_t Span ; // Number of entries used for this item
uint8_t Rvs ; // Reserved, should be 0xFF
uint32_t CRC ; // CRC
char Key[16] ; // Key in Ascii
uint64_t Data ; // Data in entry
} ;
struct nvs_page // For nvs entries
{ // 1 page is 4096 bytes
uint32_t State ;
uint32_t Seqnr ;
uint32_t Unused[5] ;
uint32_t CRC ;
uint8_t Bitmap[32] ;
nvs_entry Entry[126] ;
} ;
struct keyname_t // For keys in NVS
{
char Key[16] ; // Max length is 15 plus delimeter
} ;
//**************************************************************************************************
// Global data section. *
//**************************************************************************************************
// There is a block ini-data that contains some configuration. Configuration data is *
// saved in the preferences by the webinterface. On restart the new data will *
// de read from these preferences. *
// Items in ini_block can be changed by commands from webserver/MQTT/Serial. *
//**************************************************************************************************
enum display_t { T_UNDEFINED, T_BLUETFT, T_OLED, // Various types of display
T_DUMMYTFT, T_TM1637, T_LCD1602I2C, T_ILI9341,
T_NEXTION
} ;
enum datamode_t { INIT = 1, HEADER = 2, DATA = 4, // State for datastream
METADATA = 8, PLAYLISTINIT = 16,
PLAYLISTHEADER = 32, PLAYLISTDATA = 64,
STOPREQD = 128, STOPPED = 256
} ;
// Global variables
int DEBUG = 1 ; // Debug on/off
int numSsid ; // Number of available WiFi networks
WiFiMulti wifiMulti ; // Possible WiFi networks
ini_struct ini_block ; // Holds configurable data
WiFiServer cmdserver ( 80 ) ; // Instance of embedded webserver, port 80
WiFiClient mp3client ; // An instance of the mp3 client, also used for OTA
WiFiClient cmdclient ; // An instance of the client for commands
WiFiClient wmqttclient ; // An instance for mqtt
PubSubClient mqttclient ( wmqttclient ) ; // Client for MQTT subscriber
HardwareSerial* nxtserial = NULL ; // Serial port for NEXTION (if defined)
TaskHandle_t maintask ; // Taskhandle for main task
TaskHandle_t xplaytask ; // Task handle for playtask
TaskHandle_t xspftask ; // Task handle for special functions
SemaphoreHandle_t SPIsem = NULL ; // For exclusive SPI usage
hw_timer_t* timer = NULL ; // For timer
char timetxt[9] ; // Converted timeinfo
char cmd[130] ; // Command from MQTT or Serial
uint8_t tmpbuff[6000] ; // Input buffer for mp3 or data stream
QueueHandle_t dataqueue ; // Queue for mp3 datastream
QueueHandle_t spfqueue ; // Queue for special functions
qdata_struct outchunk ; // Data to queue
qdata_struct inchunk ; // Data from queue
uint8_t* outqp = outchunk.buf ; // Pointer to buffer in outchunk
uint32_t totalcount = 0 ; // Counter mp3 data
datamode_t datamode ; // State of datastream
int metacount ; // Number of bytes in metadata
int datacount ; // Counter databytes before metadata
char metalinebf[METASIZ + 1] ; // Buffer for metaline/ID3 tags
int16_t metalinebfx ; // Index for metalinebf
String icystreamtitle ; // Streamtitle from metadata
String icyname ; // Icecast station name
String ipaddress ; // Own IP-address
int bitrate ; // Bitrate in kb/sec
int mbitrate ; // Measured bitrate
int metaint = 0 ; // Number of databytes between metadata
int16_t currentpreset = -1 ; // Preset station playing
String host ; // The URL to connect to or file to play
String playlist ; // The URL of the specified playlist
bool hostreq = false ; // Request for new host
bool reqtone = false ; // New tone setting requested
bool muteflag = false ; // Mute output
bool resetreq = false ; // Request to reset the ESP32
bool updatereq = false ; // Request to update software from remote host
bool NetworkFound = false ; // True if WiFi network connected
bool mqtt_on = false ; // MQTT in use
String networks ; // Found networks in the surrounding
uint16_t mqttcount = 0 ; // Counter MAXMQTTCONNECTS
int8_t playingstat = 0 ; // 1 if radio is playing (for MQTT)
int16_t playlist_num = 0 ; // Nonzero for selection from playlist
File mp3file ; // File containing mp3 on SD card
uint32_t mp3filelength ; // File length
bool localfile = false ; // Play from local mp3-file or not
bool chunked = false ; // Station provides chunked transfer
int chunkcount = 0 ; // Counter for chunked transfer
String http_getcmd ; // Contents of last GET command
String http_rqfile ; // Requested file
bool http_response_flag = false ; // Response required
uint16_t ir_value = 0 ; // IR code
uint32_t ir_0 = 550 ; // Average duration of an IR short pulse
uint32_t ir_1 = 1650 ; // Average duration of an IR long pulse
struct tm timeinfo ; // Will be filled by NTP server
bool time_req = false ; // Set time requested
bool SD_okay = false ; // True if SD card in place and readable
String SD_nodelist ; // Nodes of mp3-files on SD
int SD_nodecount = 0 ; // Number of nodes in SD_nodelist
String SD_currentnode = "" ; // Node ID of song playing ("0" if random)
uint16_t adcval ; // ADC value (battery voltage)
uint32_t clength ; // Content length found in http header
uint32_t max_mp3loop_time = 0 ; // To check max handling time in mp3loop (msec)
int16_t scanios ; // TEST*TEST*TEST
int16_t scaniocount ; // TEST*TEST*TEST
uint16_t bltimer = 0 ; // Backlight time-out counter
display_t displaytype = T_UNDEFINED ; // Display type
std::vector<WifiInfo_t> wifilist ; // List with wifi_xx info
// nvs stuff
nvs_page nvsbuf ; // Space for 1 page of NVS info
const esp_partition_t* nvs ; // Pointer to partition struct
esp_err_t nvserr ; // Error code from nvs functions
uint32_t nvshandle = 0 ; // Handle for nvs access
uint8_t namespace_ID ; // Namespace ID found
char nvskeys[MAXKEYS][16] ; // Space for NVS keys
std::vector<keyname_t> keynames ; // Keynames in NVS
// Rotary encoder stuff
#define sv DRAM_ATTR static volatile
sv uint16_t clickcount = 0 ; // Incremented per encoder click
sv int16_t rotationcount = 0 ; // Current position of rotary switch
sv uint16_t enc_inactivity = 0 ; // Time inactive
sv bool singleclick = false ; // True if single click detected
sv bool doubleclick = false ; // True if double click detected
sv bool tripleclick = false ; // True if triple click detected
sv bool longclick = false ; // True if longclick detected
enum enc_menu_t { VOLUME, PRESET, TRACK } ; // State for rotary encoder menu
enc_menu_t enc_menu_mode = VOLUME ; // Default is VOLUME mode
String clockdotstr = "" ; // State of the segment clock dots
String clockbrightnesstr = "" ; // State of the segment clock brightness
String overallstate = "" ; // Overall state (to report startup etc.)
//
struct progpin_struct // For programmable input pins
{
int8_t gpio ; // Pin number
bool reserved ; // Reserved for connected devices
bool avail ; // Pin is available for a command
String command ; // Command to execute when activated
// Example: "uppreset=1"
bool cur ; // Current state, true = HIGH, false = LOW
} ;
progpin_struct progpin[] = // Input pins and programmed function
{
{ 0, false, false, "", false },
//{ 1, true, false, "", false }, // Reserved for TX Serial output
{ 2, false, false, "", false },
//{ 3, true, false, "", false }, // Reserved for RX Serial input
{ 4, false, false, "", false },
{ 5, false, false, "", false },
//{ 6, true, false, "", false }, // Reserved for FLASH SCK
//{ 7, true, false, "", false }, // Reserved for FLASH D0
//{ 8, true, false, "", false }, // Reserved for FLASH D1
//{ 9, true, false, "", false }, // Reserved for FLASH D2
//{ 10, true, false, "", false }, // Reserved for FLASH D3
//{ 11, true, false, "", false }, // Reserved for FLASH CMD
{ 12, false, false, "", false },
{ 13, false, false, "", false },
{ 14, false, false, "", false },
{ 15, false, false, "", false },
{ 16, false, false, "", false }, // May be UART 2 RX for Nextion
{ 17, false, false, "", false }, // May be UART 2 TX for Nextion
{ 18, false, false, "", false }, // Default for SPI CLK
{ 19, false, false, "", false }, // Default for SPI MISO
//{ 20, true, false, "", false }, // Not exposed on DEV board
{ 21, false, false, "", false }, // Also Wire SDA
{ 22, false, false, "", false }, // Also Wire SCL
{ 23, false, false, "", false }, // Default for SPI MOSI
//{ 24, true, false, "", false }, // Not exposed on DEV board
{ 25, false, false, "", false },
{ 26, false, false, "", false },
{ 27, false, false, "", false },
//{ 28, true, false, "", false }, // Not exposed on DEV board
//{ 29, true, false, "", false }, // Not exposed on DEV board
//{ 30, true, false, "", false }, // Not exposed on DEV board
//{ 31, true, false, "", false }, // Not exposed on DEV board
{ 32, false, false, "", false },
{ 33, false, false, "", false },
{ 34, false, false, "", false }, // Note, no internal pull-up
{ 35, false, false, "", false }, // Note, no internal pull-up
//{ 36, true, false, "", false }, // Reserved for ADC battery level
{ 39, false, false, "", false }, // Note, no internal pull-up
{ -1, false, false, "", false } // End of list
} ;
struct touchpin_struct // For programmable input pins
{
int8_t gpio ; // Pin number GPIO
bool reserved ; // Reserved for connected devices
bool avail ; // Pin is available for a command
String command ; // Command to execute when activated
// Example: "uppreset=1"
bool cur ; // Current state, true = HIGH, false = LOW
int16_t count ; // Counter number of times low level
} ;
touchpin_struct touchpin[] = // Touch pins and programmed function
{
{ 4, false, false, "", false, 0 }, // TOUCH0
{ 0, true, false, "", false, 0 }, // TOUCH1, reserved for BOOT button
{ 2, false, false, "", false, 0 }, // TOUCH2
{ 15, false, false, "", false, 0 }, // TOUCH3
{ 13, false, false, "", false, 0 }, // TOUCH4
{ 12, false, false, "", false, 0 }, // TOUCH5
{ 14, false, false, "", false, 0 }, // TOUCH6
{ 27, false, false, "", false, 0 }, // TOUCH7
{ 33, false, false, "", false, 0 }, // TOUCH8
{ 32, false, false, "", false, 0 }, // TOUCH9
{ -1, false, false, "", false, 0 } // End of list
// End of table
} ;
//**************************************************************************************************
// Pages, CSS and data for the webinterface. *
//**************************************************************************************************
#include "about_html.h"
#include "config_html.h"
#include "index_html.h"
#include "mp3play_html.h"
#include "radio_css.h"
#include "favicon_ico.h"
#include "defaultprefs.h"
//**************************************************************************************************
// End of global data section. *
//**************************************************************************************************
//**************************************************************************************************
// M Q T T P U B _ C L A S S *
//**************************************************************************************************
// ID's for the items to publish to MQTT. Is index in amqttpub[]
enum { MQTT_IP, MQTT_ICYNAME, MQTT_STREAMTITLE, MQTT_NOWPLAYING,
MQTT_PRESET, MQTT_VOLUME, MQTT_PLAYING, MQTT_PLAYLISTPOS,
MQTT_CLOCKDOTS, MQTT_CLOCKBRIGHTNESS, MQTT_STATE
} ;
enum { MQSTRING, MQINT8, MQINT16 } ; // Type of variable to publish
class mqttpubc // For MQTT publishing
{
struct mqttpub_struct
{
const char* topic ; // Topic as partial string (without prefix)
uint8_t type ; // Type of payload
void* payload ; // Payload for this topic
bool topictrigger ; // Set to true to trigger MQTT publish
} ;
// Publication topics for MQTT. The topic will be pefixed by "PREFIX/", where PREFIX is replaced
// by the the mqttprefix in the preferences.
protected:
mqttpub_struct amqttpub[12] = // Definitions of various MQTT topic to publish
{ // Index is equal to enum above
{ "ip", MQSTRING, &ipaddress, false }, // Definition for MQTT_IP
{ "icy/name", MQSTRING, &icyname, false }, // Definition for MQTT_ICYNAME
{ "icy/streamtitle", MQSTRING, &icystreamtitle, false }, // Definition for MQTT_STREAMTITLE
{ "nowplaying", MQSTRING, &ipaddress, false }, // Definition for MQTT_NOWPLAYING
{ "preset", MQINT8, ¤tpreset, false }, // Definition for MQTT_PRESET
{ "volume", MQINT8, &ini_block.reqvol, false }, // Definition for MQTT_VOLUME
{ "playing", MQINT8, &playingstat, false }, // Definition for MQTT_PLAYING
{ "playlist/pos", MQINT16, &playlist_num, false }, // Definition for MQTT_PLAYLISTPOS
{ "clock/dots", MQSTRING, &clockdotstr, false }, // Definition for MQTT_CLOCKDOTS
{ "clock/brightness", MQINT8, &clockbrightnesstr, false }, // Definition for MQTT_CLOCKBRIGHTNESS
{ "state", MQSTRING, &overallstate, false }, // Definition for MQTT_STATE
{ NULL, 0, NULL, false } // End of definitions
} ;
public:
void trigger ( uint8_t item ) ; // Trigger publishig for one item
void publishtopic() ; // Publish triggerer items
} ;
//**************************************************************************************************
// MQTTPUB class implementation. *
//**************************************************************************************************
//**************************************************************************************************
// T R I G G E R *
//**************************************************************************************************
// Set request for an item to publish to MQTT. *
//**************************************************************************************************
void mqttpubc::trigger ( uint8_t item ) // Trigger publishig for one item
{
amqttpub[item].topictrigger = true ; // Request re-publish for an item
}
//**************************************************************************************************
// P U B L I S H T O P I C *
//**************************************************************************************************
// Publish a topic to MQTT broker. *
//**************************************************************************************************
void mqttpubc::publishtopic()
{
int i = 0 ; // Loop control
char topic[80] ; // Topic to send
const char* payload ; // Points to payload
char intvar[10] ; // Space for integer parameter
while ( amqttpub[i].topic )
{
if ( amqttpub[i].topictrigger ) // Topic ready to send?
{
amqttpub[i].topictrigger = false ; // Success or not: clear trigger
sprintf ( topic, "%s/%s", ini_block.mqttprefix.c_str(),
amqttpub[i].topic ) ; // Add prefix to topic
switch ( amqttpub[i].type ) // Select conversion method
{
case MQSTRING :
payload = ((String*)amqttpub[i].payload)->c_str() ;
//payload = pstr->c_str() ; // Get pointer to payload
break ;
case MQINT8 :
sprintf ( intvar, "%d",
*(int8_t*)amqttpub[i].payload ) ; // Convert to array of char
payload = intvar ; // Point to this array
break ;
case MQINT16 :
sprintf ( intvar, "%d",
*(int16_t*)amqttpub[i].payload ) ; // Convert to array of char
payload = intvar ; // Point to this array
break ;
default :
continue ; // Unknown data type
}
dbgprint ( "Publish to topic %s : %s", // Show for debug
topic, payload ) ;
if ( !mqttclient.publish ( topic, payload ) ) // Publish!
{
dbgprint ( "MQTT publish failed!" ) ; // Failed
}
return ; // Do the rest later
}
i++ ; // Next entry
}
}
mqttpubc mqttpub ; // Instance for mqttpubc
//
//**************************************************************************************************
// VS1053 stuff. Based on maniacbug library. *
//**************************************************************************************************
// VS1053 class definition. *
//**************************************************************************************************
class VS1053
{
private:
int8_t cs_pin ; // Pin where CS line is connected
int8_t dcs_pin ; // Pin where DCS line is connected
int8_t dreq_pin ; // Pin where DREQ line is connected
int8_t shutdown_pin ; // Pin where the shutdown line is connected
int8_t shutdownx_pin ; // Pin where the shutdown (inversed) line is connected
uint8_t curvol ; // Current volume setting 0..100%
const uint8_t vs1053_chunk_size = 32 ;
// SCI Register
const uint8_t SCI_MODE = 0x0 ;
const uint8_t SCI_STATUS = 0x1 ;
const uint8_t SCI_BASS = 0x2 ;
const uint8_t SCI_CLOCKF = 0x3 ;
const uint8_t SCI_AUDATA = 0x5 ;
const uint8_t SCI_WRAM = 0x6 ;
const uint8_t SCI_WRAMADDR = 0x7 ;
const uint8_t SCI_AIADDR = 0xA ;
const uint8_t SCI_VOL = 0xB ;
const uint8_t SCI_AICTRL0 = 0xC ;
const uint8_t SCI_AICTRL1 = 0xD ;
const uint8_t SCI_num_registers = 0xF ;
// SCI_MODE bits
const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on
const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset
const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song
const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests
const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for Line input
SPISettings VS1053_SPI ; // SPI settings for this slave
uint8_t endFillByte ; // Byte to send when stopping song
bool okay = true ; // VS1053 is working
protected:
inline void await_data_request() const
{
while ( ( dreq_pin >= 0 ) &&
( !digitalRead ( dreq_pin ) ) )
{
NOP() ; // Very short delay
}
}
inline void control_mode_on() const
{
SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users
digitalWrite ( cs_pin, LOW ) ;
}
inline void control_mode_off() const
{
digitalWrite ( cs_pin, HIGH ) ; // End control mode
SPI.endTransaction() ; // Allow other SPI users
}
inline void data_mode_on() const
{
SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users
//digitalWrite ( cs_pin, HIGH ) ; // Bring slave in data mode
digitalWrite ( dcs_pin, LOW ) ;
}
inline void data_mode_off() const
{
digitalWrite ( dcs_pin, HIGH ) ; // End data mode
SPI.endTransaction() ; // Allow other SPI users
}
uint16_t read_register ( uint8_t _reg ) const ;
void write_register ( uint8_t _reg, uint16_t _value ) const ;
inline bool sdi_send_buffer ( uint8_t* data, size_t len ) ;
void sdi_send_fillers ( size_t length ) ;
void wram_write ( uint16_t address, uint16_t data ) ;
uint16_t wram_read ( uint16_t address ) ;
void output_enable ( bool ena ) ; // Enable amplifier through shutdown pin(s)
public:
// Constructor. Only sets pin values. Doesn't touch the chip. Be sure to call begin()!
VS1053 ( int8_t _cs_pin, int8_t _dcs_pin, int8_t _dreq_pin,
int8_t _shutdown_pin, int8_t _shutdownx_pin ) ;
void begin() ; // Begin operation. Sets pins correctly,
// and prepares SPI bus.
void startSong() ; // Prepare to start playing. Call this each
// time a new song starts.
inline bool playChunk ( uint8_t* data, // Play a chunk of data. Copies the data to
size_t len ) ; // the chip. Blocks until complete.
// Returns true if more data can be added
// to fifo
void stopSong() ; // Finish playing a song. Call this after
// the last playChunk call.
void setVolume ( uint8_t vol ) ; // Set the player volume.Level from 0-100,
// higher is louder.
void setTone ( uint8_t* rtone ) ; // Set the player baas/treble, 4 nibbles for
// treble gain/freq and bass gain/freq
inline uint8_t getVolume() const // Get the current volume setting.
{ // higher is louder.
return curvol ;
}
void printDetails ( const char *header ) ; // Print config details to serial output
void softReset() ; // Do a soft reset
bool testComm ( const char *header ) ; // Test communication with module
inline bool data_request() const
{
return ( digitalRead ( dreq_pin ) == HIGH ) ;
}
void AdjustRate ( long ppm2 ) ; // Fine tune the datarate
} ;
//**************************************************************************************************
// VS1053 class implementation. *
//**************************************************************************************************
VS1053::VS1053 ( int8_t _cs_pin, int8_t _dcs_pin, int8_t _dreq_pin,
int8_t _shutdown_pin, int8_t _shutdownx_pin ) :
cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin), shutdown_pin(_shutdown_pin),
shutdownx_pin(_shutdownx_pin)
{
}
uint16_t VS1053::read_register ( uint8_t _reg ) const
{
uint16_t result ;
control_mode_on() ;
SPI.write ( 3 ) ; // Read operation
SPI.write ( _reg ) ; // Register to write (0..0xF)
// Note: transfer16 does not seem to work
result = ( SPI.transfer ( 0xFF ) << 8 ) | // Read 16 bits data
( SPI.transfer ( 0xFF ) ) ;
await_data_request() ; // Wait for DREQ to be HIGH again
control_mode_off() ;
return result ;
}
void VS1053::write_register ( uint8_t _reg, uint16_t _value ) const
{
control_mode_on( );
SPI.write ( 2 ) ; // Write operation
SPI.write ( _reg ) ; // Register to write (0..0xF)
SPI.write16 ( _value ) ; // Send 16 bits data
await_data_request() ;
control_mode_off() ;
}
bool VS1053::sdi_send_buffer ( uint8_t* data, size_t len )
{
size_t chunk_length ; // Length of chunk 32 byte or shorter
data_mode_on() ;
while ( len ) // More to do?
{
chunk_length = len ;
if ( len > vs1053_chunk_size )
{
chunk_length = vs1053_chunk_size ;
}
len -= chunk_length ;
await_data_request() ; // Wait for space available
SPI.writeBytes ( data, chunk_length ) ;
data += chunk_length ;
}
data_mode_off() ;
return data_request() ; // True if more data can de stored in fifo
}
void VS1053::sdi_send_fillers ( size_t len )
{
size_t chunk_length ; // Length of chunk 32 byte or shorter
data_mode_on() ;
while ( len ) // More to do?
{
await_data_request() ; // Wait for space available
chunk_length = len ;
if ( len > vs1053_chunk_size )
{
chunk_length = vs1053_chunk_size ;
}
len -= chunk_length ;
while ( chunk_length-- )
{
SPI.write ( endFillByte ) ;
}
}
data_mode_off();
}
void VS1053::wram_write ( uint16_t address, uint16_t data )
{
write_register ( SCI_WRAMADDR, address ) ;
write_register ( SCI_WRAM, data ) ;
}
uint16_t VS1053::wram_read ( uint16_t address )
{
write_register ( SCI_WRAMADDR, address ) ; // Start reading from WRAM
return read_register ( SCI_WRAM ) ; // Read back result
}
bool VS1053::testComm ( const char *header )
{
// Test the communication with the VS1053 module. The result wille be returned.
// If DREQ is low, there is problably no VS1053 connected. Pull the line HIGH
// in order to prevent an endless loop waiting for this signal. The rest of the
// software will still work, but readbacks from VS1053 will fail.
int i ; // Loop control
uint16_t r1, r2, cnt = 0 ;
uint16_t delta = 300 ; // 3 for fast SPI
const uint16_t vstype[] = { 1001, 1011, 1002, 1003, // Possible chip versions
1053, 1033, 0000, 1103
} ;
dbgprint ( header ) ; // Show a header
if ( !digitalRead ( dreq_pin ) )
{
dbgprint ( "VS1053 not properly installed!" ) ;
// Allow testing without the VS1053 module
pinMode ( dreq_pin, INPUT_PULLUP ) ; // DREQ is now input with pull-up
return false ; // Return bad result
}
// Further TESTING. Check if SCI bus can write and read without errors.
// We will use the volume setting for this.
// Will give warnings on serial output if DEBUG is active.
// A maximum of 20 errors will be reported.
if ( strstr ( header, "Fast" ) )
{
delta = 3 ; // Fast SPI, more loops
}
for ( i = 0 ; ( i < 0xFFFF ) && ( cnt < 20 ) ; i += delta )
{
write_register ( SCI_VOL, i ) ; // Write data to SCI_VOL
r1 = read_register ( SCI_VOL ) ; // Read back for the first time
r2 = read_register ( SCI_VOL ) ; // Read back a second time
if ( r1 != r2 || i != r1 || i != r2 ) // Check for 2 equal reads
{
dbgprint ( "VS1053 SPI error. SB:%04X R1:%04X R2:%04X", i, r1, r2 ) ;
cnt++ ;
delay ( 10 ) ;
}
}
okay = ( cnt == 0 ) ; // True if working correctly
// Further testing: is it the right chip?
r1 = ( read_register ( SCI_STATUS ) >> 4 ) & 0x7 ; // Read status to get the version
if ( r1 != 4 ) // Version 4 is a genuine VS1053
{
dbgprint ( "This is not a VS1053, " // Report the wrong chip
"but a VS%d instead!",
vstype[r1] ) ;
okay = false ;
}
return ( okay ) ; // Return the result
}
void VS1053::begin()
{
pinMode ( dreq_pin, INPUT ) ; // DREQ is an input
pinMode ( cs_pin, OUTPUT ) ; // The SCI and SDI signals
pinMode ( dcs_pin, OUTPUT ) ;
digitalWrite ( dcs_pin, HIGH ) ; // Start HIGH for SCI en SDI
digitalWrite ( cs_pin, HIGH ) ;
if ( shutdown_pin >= 0 ) // Shutdown in use?
{
pinMode ( shutdown_pin, OUTPUT ) ;
}
if ( shutdownx_pin >= 0 ) // Shutdown (inversed logic) in use?
{
pinMode ( shutdownx_pin, OUTPUT ) ;
}
output_enable ( false ) ; // Disable amplifier through shutdown pin(s)
delay ( 100 ) ;
// Init SPI in slow mode ( 0.2 MHz )
VS1053_SPI = SPISettings ( 200000, MSBFIRST, SPI_MODE0 ) ;
SPI.setDataMode ( SPI_MODE0 ) ;
SPI.setBitOrder ( MSBFIRST ) ;
//printDetails ( "Right after reset/startup" ) ;
delay ( 20 ) ;
//printDetails ( "20 msec after reset" ) ;
if ( testComm ( "Slow SPI, Testing VS1053 read/write registers..." ) )
{
// Most VS1053 modules will start up in midi mode. The result is that there is no audio
// when playing MP3. You can modify the board, but there is a more elegant way:
wram_write ( 0xC017, 3 ) ; // GPIO DDR = 3
wram_write ( 0xC019, 0 ) ; // GPIO ODATA = 0
delay ( 100 ) ;
//printDetails ( "After test loop" ) ;
softReset() ; // Do a soft reset
// Switch on the analog parts
write_register ( SCI_AUDATA, 44100 + 1 ) ; // 44.1kHz + stereo
// The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then.
write_register ( SCI_CLOCKF, 6 << 12 ) ; // Normal clock settings
// multiplyer 3.0 = 12.2 MHz
//SPI Clock to 4 MHz. Now you can set high speed SPI clock.
VS1053_SPI = SPISettings ( 5000000, MSBFIRST, SPI_MODE0 ) ;
write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_LINE1 ) ) ;
testComm ( "Fast SPI, Testing VS1053 read/write registers again..." ) ;
delay ( 10 ) ;
await_data_request() ;
endFillByte = wram_read ( 0x1E06 ) & 0xFF ;
dbgprint ( "endFillByte is %X", endFillByte ) ;
//printDetails ( "After last clocksetting" ) ;
delay ( 100 ) ;
}
}
void VS1053::setVolume ( uint8_t vol )
{
// Set volume. Both left and right.
// Input value is 0..100. 100 is the loudest.
// Clicking reduced by using 0xf8 to 0x00 as limits.
uint16_t value ; // Value to send to SCI_VOL
if ( vol != curvol )
{
curvol = vol ; // Save for later use
value = map ( vol, 0, 100, 0xF8, 0x00 ) ; // 0..100% to one channel
value = ( value << 8 ) | value ;
write_register ( SCI_VOL, value ) ; // Volume left and right
output_enable ( vol != 0 ) ; // Enable/disable amplifier through shutdown pin(s)
}
}
void VS1053::setTone ( uint8_t *rtone ) // Set bass/treble (4 nibbles)
{
// Set tone characteristics. See documentation for the 4 nibbles.
uint16_t value = 0 ; // Value to send to SCI_BASS
int i ; // Loop control
for ( i = 0 ; i < 4 ; i++ )
{
value = ( value << 4 ) | rtone[i] ; // Shift next nibble in
}
write_register ( SCI_BASS, value ) ; // Volume left and right
}