-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSonos_Controller_ESP32-V3-ASYNC.ino
2099 lines (1786 loc) · 75.1 KB
/
Sonos_Controller_ESP32-V3-ASYNC.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
// Controller for Sonos soundsystems by frank
// provided as is
// work in progres
// boardmanager -> ESP32 Dev Module
// a mix of all sorts of libraries found on github is used
// feature list
// wifi setup manager to enter your wifi ssid and password
// OTA enabled for updating
// drives a color pixel ledstrip to illuminate buttons of jukebox
// drives a TFT display 240x240
// reads the 20 jukebox keys using 1 adc pin and a switched resistor voltage divider to save wiring
// a rotary encoder to control the volume
// SD card to store music files and artwork
// scans network to find sonos, max 4 devices
// play internet radio or jukebox mode with your own playlist
// webserver for serving music files and more
// printing of 1" x 3" paper jukebox strips to insert in jukebox or wallbox
// printing of 'menu' card with the offered selection of songs
// connection with MQTT broker to send/receive events and commands
// todo
// artwork records
// pimp up the web pages
// pimp up the webmanager portal
// setup of radiostations using sonos favorite list
// moving more sonos commands to asynchronous method
// clean up the code mess
#include "ConfigOnSwitch.h" // deals with Wifi connection, using WifiManager
#include <AsyncTCP.h>
static AsyncClient * aClient = NULL;
#include <TJpg_Decoder.h>
#include <arduino.h>
#include "hardcoded.h" // list of predefined radio stations
#define WALLBOX 1 // hardware is wallbox, uses volume up/down buttons
#define JUKEBOX 0 // 1 hardware is a large jukebox
int DeviceType = JUKEBOX;
#define SELECTSONG 1 // function of jukebox keys
#define SELECTRADIO 0 // function of jukebox keys
int DeviceMode = SELECTRADIO;
#define SONOSDEFAULTVOLUME 15 // reset volume to this level if lower
// sonos async processed commands, not all implemented yet
#define SONOSGETVOLUME 1
#define SONOSSETVOLUME 2
#define SONOSGETMODE 3 // radio, playing file, whatever
#define SONOSGETSTATE 4 // stopped, playing, paused
#define SONOSPLAY 5 // starts playing if not started yet
#define SONOSADDSONGTOQUEUE 6 // add song, from playlist, song stored on SD, or from Samba server
#define SONOSEMPTY QUEUE 7
#define SONOSSELECTRADIOPRESET 8
#define SONOSCHECKJUKEBOXQUEUE 9
// Orientation of hardware connections ESP32-DEV module, USB pointing downwards
// Left row
// EN
// VP GPIO36 ADC RIGHTHAND/BOTTOM BUTTON ROW 1-2-3-4-5-6-7-8-9-10
// VN GPIO39 ADC LEFTHAND/TOP BUTTON ROW A_B-C-D-E-F-G-H-J-K
// D34 GPIO34 ADC CONTROL WALLBOX VOLUME UP/DN, SKIP (WALLBOX ONLY)
// D35 (GPIO35) CANCEL BUTTON met 10K pullup naar 3V3
// D32 BLUE BUTTON ROCKOLA
// D33 ENCODER B
// D25 ENCODER A
// D26 SD_MISO
// D27 (GPIO27) LEDMATRIX MAX7219 CLK
// D14 SD_SCLK
// D12 (GPIO12) LEDMATRIX MAX7219 DATA
// D13 SD_MOSI
// GND
// VIN
// Right row
// D23 (GPION23) TFT MOSI (VSPI)
// D22 (GPIO22) LEDMATRIX MAX7219 CS
// TXO (GPIO1)
// RXO (GPIO3)
// D21 (GPIO21) SOLENOID
// D19 (GPIO19) TFT MISO (VSPI)
// D18 (GPIO18) TFT CLK (VSPI)
// D5 (GPIO5) TFT CS
// TX2 (GPIO17) WS2812 LED STRIP DATA
// RX2 (GPIO16) ENCODER BUTTON
// D4 (GPIO4) TFT RESET
// D2 (GPIO2) TFT DC
// D15 (GPIO15) SD CS
// GND
// 3V3
#include <SD.h>
//#include "FS.h"
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#define HARDWARE_TYPE MD_MAX72XX::DR1CR0RR0_HW ///< Structured name equivalent to FC16_HW
#define MAX_DEVICES 11 // 25
#define CLK_PIN_MAX2719 27 // 14
#define DATA_PIN_MAX2719 12 // 13
#define CS_PIN 22
uint8_t scrollSpeed = 60; //10;//25; // default frame delay value
textEffect_t scrollEffect = PA_SCROLL_LEFT;
textPosition_t scrollAlign = PA_RIGHT;
uint16_t scrollPause = 0; // 2000; // in milliseconds
// Global message buffers shared by Serial and Scrolling functions
#define BUF_SIZE 150 // 75
char curMessage[BUF_SIZE] = { "BOOTING --- " };
char newMessage[BUF_SIZE] = { "" };
bool newMessageAvailable = false;
int FollowTFTLine = 0;
// HARDWARE SPI
//MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// SOFT SPI
//MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN_MAX2719, CLK_PIN_MAX2719, CS_PIN, MAX_DEVICES);
#include <SPI.h>
SPIClass sdSPI(HSPI);
bool SD_present = false;
#define SD_MISO 26 // Change from default pin to allow for reset without issues
#define SD_MOSI 13
#define SD_SCLK 14
#define SD_CS 15
//#include "LedMatrix.h" // https://github.com/squix78/MAX7219LedMatrix
// ledmatrix DIN = GPIO13
// ledmatrix CLK = GPIO14
//LedMatrix ledMatrix = LedMatrix(2, 22); // number of modules, CS pin
const char KnobDecals[]="ABCDEFGHJK1234567890";
char LastRadioStream[128]="";
char LastRadioStation[128]="";
// setup of OTA
#include "OTA.h"
// invoke TFT_eSPI (by Bodmer - version 2.4.72) display library
// make sure to choose the correct dispay driver in User_Setup in the libray folder TFT_eSPI
// tested with ILI9341_DRIVER 320x240 display
// also tested with ST7789_DRIVER + #define TFT_RGB_ORDER TFT_BGR 240x240 display
// with the 320x240 ILI9341 display I only use 240x240 portion of it, as it is mounted behind a square bezel that exposes only 240x240 area of the display
#include "Arialnarrow.h"
#include <TFT_eSPI.h> // Hardware-specific library
TFT_eSPI tft = TFT_eSPI(240,320);
struct TFTline
{
bool refresh;
char content[256];
bool scroll;
int length;
int pixelwidth;
int scrollpos;
int scrolldelay;
int nchar;
int toeat;
int noffset;
int textcolor;
int backgroundcolor;
};
// note to self - connections for ST7789 display
// purple wire DC to pin 2 (TFT_DC in User_Setup.h)
// grey wire RES to pin D4 (TFT_RST in User_Setup.h)
// white wire SDA to pin 23 (TFT_MOSI in User_Setup.h)
// black wire SLCK to pin D18 (TFT_SLCK in User_Setup.h)
// note to self - connections for ILI9341 display
// blue wire DC to pin 2 (TFT_DC in User_Setup.h)
// orange wire MOSI to pin 23 (TFT_MOSI in User_Setup.h)
// orange-wit wire SLCK to pin D18 (TFT_SLCK in User_Setup.h)
// green wire CS to pin D15 (TFT_CS in User_Setup.h)
// green-white wire RST to pin D4 (TFT_RST in User_Setup.h)
// yellow wire MISO to pin D19
// also make sure you have the right pins selected in User_Setup.h
// defined in User_Setup.h:
// #define TFT_MISO 19
// #define TFT_MOSI 23
// #define TFT_SCLK 18
// #define TFT_CS 5 // Chip select control pin
// #define TFT_DC 2 // Data Command control pin
// #define TFT_RST 4 // Reset pin (could connect to RST pin)
TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for volume needle
#define DIAL_CENTRE_X 120
#define DIAL_CENTRE_Y 121
uint16_t* tft_buffer;
void createNeedle(void);
// display updates are done in seperate task
// mutex used to avoid simultanious acces to text arrays for display
SemaphoreHandle_t xDisplayMutex;
#define LCD_RADIO_PLAYING 1
#define LCD_RADIO_SELECTING 2
#define LCD_JUKEBOX_PLAYING 3
#define LCD_JUKEBOX_SELECTING 4
#define LCD_JUKEBOX_DEBUG 5
#define LCD_SONOS_RADIO 5 // http mode eigenlijk
char PlayingTitle[556]=""; // die komt na ;<dc:title> en is de radio naam bij radio en de songtitel bij jukebox
char PlayingStreamContent[556]=""; // die komt na <r:streamContent> en is de artiest en tracktitel bij radio en leeg bij jukebox
char OldPlayingTitle[556]=""; // die komt na ;<dc:title> en is de radio naam bij radio en de songtitel bij jukebox
char OldPlayingStreamContent[556]=""; // die komt na <r:streamContent> en is de artiest en tracktitel bij radio en leeg bij jukebox
// eprom to store chosen sonos device
#include <EEPROM.h>
#define EEPROM_SIZE 256
// led animation
#include <FastLED.h>
#define DATA_PIN 17
#define NUM_LEDS 88 // 72 for the selection keys and 16 for the volume knob
CRGB leds[NUM_LEDS];
#include <AiEsp32RotaryEncoder.h>
#define ROTARY_ENCODER_A_PIN 25
#define ROTARY_ENCODER_B_PIN 33
#define ROTARY_ENCODER_BUTTON_PIN 16
#define ROTARY_ENCODER_VCC_PIN -1
#define ROTARY_ENCODER_STEPS 2
#define BLUE_BUTTON 32
#define CANCEL_BUTTON 35
#define ENCODER_BUTTON 16
#define MAXSONOS 4
// Max number of Sonos IP devices our network
struct SonosDevice { // /status/zp details Structure
char Zonename[32]; // ZoneName
char UID[32]; // LocalUID RINCON_xxx
char Serialnumber[32]; // serial number full
char Seriesid[16] ; //SeriesID
} ACTIVE_sonosDV,G_SonosDeviceList[MAXSONOS]; // device array of max 4 Devices in your network
uint8_t SonosLastUsed = -1;
uint8_t ActiveSonos = -1; // 0-3 from the list of max 4 Sonos Devices
int ActualVolumeFromSonos = -1; // initialize as invalid value
int NewVolumeForSonos = -1; // initialize as invalid value
int AsyncVolumeForSonos = -1;
int MinimumVolumeForSonos = SONOSDEFAULTVOLUME;
int SonosSkipRequest = 0;
int ShowVolume10mS = 0;
int Show45RPM10mS = 0;
int ShowRadio10mS = 0;
int ShowArt10mS = 0;
int Actual45RPMShown = 999;
int ActualRadioShown = 999;
int ActualArtShown = 999;
int UpdateTimeOut10mS = 0; // timeout timer after Sonos SetVolume
#define ANALOG_LEFT 39 // rows of switches are wired as voltage divider to save wires
#define ANALOG_RIGHT 36 // rows of switches are wired as voltage divider to save wires
#define ANALOG_CONTROL 34 // wallbox only
#define SOLENOID 21 // latch magnet on rockola jukebox
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(
ROTARY_ENCODER_A_PIN,
ROTARY_ENCODER_B_PIN,
ROTARY_ENCODER_BUTTON_PIN,
ROTARY_ENCODER_VCC_PIN,
ROTARY_ENCODER_STEPS
);
void IRAM_ATTR readEncoderISR()
{ rotaryEncoder.readEncoder_ISR();
}
volatile int interruptCounter;
volatile int MagnetPower10mS = 0;
int MagnetDeadTime10mS = 0;
bool ScrollNow = false;
bool bUpdateDisplay = false;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
#define EEPROM_JBM 0
#define JBM_PLAYQUEUE 1
#define JBM_RADIO 2
#define JBM_MAX 2
#define EEPROM_RADIOSTATION 1
int OldRadioStation = 0;
int NewRadioStation; // fetched from eeprom at boot
#define EEPROM_DeviceType 2
#define EEPROM_SONOSIP1 3 // saved IP addresses from found sonos devices
#define EEPROM_SONOSIP2 7
#define EEPROM_SONOSIP3 11
#define EEPROM_SONOSIP4 15
#define EEPROM_SONOS_LASTUSED 19
char Keys[]="1234"; // last 4 keys pressed, used as secret way to set device type (jukebox or wallbox) 4711J 4711B
volatile byte deb_left_key = 0;
volatile byte deb_right_key = 0;
volatile byte deb_control_key = 0;
int deb_cancel_button = 0;
int deb_blue_button = 0;
int deb_encoder_button = 0;
int blue_buttonf = 1234;
int cancel_buttonf = 1234;
int encoder_buttonf = 1234;
int left_keyf = 0;
int right_keyf = 0;
int control_keyf = 0;
volatile byte cancel_button_long_pressed = 0;
int cancel_button_very_long_pressed = 0;
int encoder_button_very_long_pressed = 0;
int magnet = 0;
int magnetdeadtime = 0;
int selectedsong = 0;
char SonosTrack[256];
byte LastSongAddedToQueue = 16;
int SongsInQueueCnt = 0;
int CancelButtonEnabled = 0;
int BootFase = 0;
void ethConnectError(void) // callback from sonos library
{ static int fail = 0;
char text[32];
// Sonos does not respond to command
Serial.println("Sonos: No connecton...");
Serial.print("Free Heap: ");Serial.println(ESP.getFreeHeap());
fail++;
sprintf(text, "SONOS ERROR");
TFT_line_print(2, text);
sprintf(text, "COM FAIL %d", fail);
TFT_line_print(3, text);
// if(fail>5)
// { //ACTIVE_sonosIP(0);
//BootFase = 1; // start all over
// fail = 0;
// }
}
#include "SonosUPnP.h"
#include "sonosscan.h"
int ActLcdMode = 999;
int NewLcdMode = -1;
#include <MicroXPath_P.h>
#include <ESPAsyncWebServer.h>
#include "webpages.h"
#define FIRMWARE_VERSION "JUKEBOX V0.9"
const String default_ssid = "somessid";
const String default_wifipassword = "mypassword";
const String default_httpuser = "admin";
const String default_httppassword = "admin";
const int default_webserverporthttp = 80;
// configuration structure
struct Config {
String ssid; // wifi ssid
String wifipassword; // wifi password
String httpuser; // username to access web admin
String httppassword; // password to access web admin
int webserverporthttp; // http port number for web admin
};
// variables
Config config; // configuration
bool shouldReboot = false; // schedule a reboot
AsyncWebServer *server; // initialise webserver
void configureWebServer(void);
// function defaults
String listFiles(bool ishtml = false);
#include <DNSServer.h>
// why not
void setupMQTT(void);
void loopMQTT(void);
const int JUKEKEYMODE[SONOS_MAXSOURCE]={
SELECTRADIO, //UNKNOWN_SCHEME,
SELECTRADIO, //SPOTIFY_SCHEME,
SELECTSONG, //FILE_SCHEME,
SELECTSONG, //LIBRARY_SCHEME
SELECTSONG, //HTTP_SCHEME,
SELECTRADIO, //RADIO_SCHEME,
SELECTRADIO, //RADIO_AAC_SCHEME,
SELECTRADIO, //LINEIN_SCHEME,
SELECTRADIO, //MASTER_SCHEME,
SELECTRADIO, //QUEUE_SCHEME ,
SELECTRADIO, //SPOTIFYSTATION_SCHEME,
SELECTSONG, //LOCALHTTP_SCHEME,
SELECTSONG, //LOCALHTTPS_SCHEME
SELECTRADIO, //SPOTIFY_RADIO_SCHEME
};
const uint32_t JUKESCANCOLOR[SONOS_MAXSOURCE]={
0x00FF00, //UNKNOWN_SCHEME,
0xFF0000, //SPOTIFY_SCHEME,
0xFF00FF, //FILE_SCHEME,
0xFF00FF, //LIBRARY_SCHEME
0xFF00FF, //HTTP_SCHEME,
0xFF0000, //RADIO_SCHEME,
0xFF0000, //RADIO_AAC_SCHEME,
0xFF0000, //LINEIN_SCHEME,
0xFF0000, //MASTER_SCHEME,
0xFF0000, //QUEUE_SCHEME ,
0xFF0000, //SPOTIFYSTATION_SCHEME,
0xFF00FF, //LOCALHTTP_SCHEME,
0xFF00FF, //LOCALHTTPS_SCHEME
0xFF0000, //SPOTIFY_RADIO_SCHEME
};
// UpdateLcd2() treats some modes in similar way
const uint32_t JUKELCDMODEOVERIDES[SONOS_MAXSOURCE]={
SONOS_SOURCE_UNKNOWN, //#define SONOS_SOURCE_UNKNOWN 0
SONOS_SOURCE_SPOTIFY, //#define SONOS_SOURCE_SPOTIFY 1
SONOS_SOURCE_FILE, //#define SONOS_SOURCE_FILE 2
SONOS_SOURCE_HTTP, //#define SONOS_SOURCE_HTTP 3
SONOS_SOURCE_RADIO, //#define SONOS_SOURCE_RADIO 4
SONOS_SOURCE_RADIO, //#define SONOS_SOURCE_RADIO_AAC 5
SONOS_SOURCE_RADIO, //#define SONOS_SOURCE_LINEIN 6
SONOS_SOURCE_MASTER, //#define SONOS_SOURCE_MASTER 7
SONOS_SOURCE_QUEUE, //#define SONOS_SOURCE_QUEUE 8
SONOS_SOURCE_SPOTIFYSTATION, //#define SONOS_SOURCE_SPOTIFYSTATION 9
SONOS_SOURCE_FILE, //#define SONOS_SOURCE_LOCALHTTP 10
SONOS_SOURCE_FILE, //#define SONOS_SOURCE_LOCALHTTPS 11
};
//#define SONOS_SOURCE_UNKNOWN 0
//#define SONOS_SOURCE_SPOTIFY 1
//#define SONOS_SOURCE_FILE 2
//#define SONOS_SOURCE_HTTP 3
//#define SONOS_SOURCE_RADIO 4
//#define SONOS_SOURCE_RADIO_AAC 5
//#define SONOS_SOURCE_LINEIN 6
//#define SONOS_SOURCE_MASTER 7
//#define SONOS_SOURCE_QUEUE 8
//#define SONOS_SOURCE_SPOTIFYSTATION 9
//#define SONOS_SOURCE_LOCALHTTP 10
//#define SONOS_SOURCE_LOCALHTTPS 11
int SonosState;
int SonosSecondsStopped100mS =0;
int NewSonosSourceMode = -1;
int SonosSourceMode = -1;
bool PollSonosVolume = false;
bool PollSonosSourceMode = false;
bool PollSonosState = false;
bool Ticker100mS = false;
bool AllSet = false;
bool g_Playlistfound = false;
bool g_SDfallback = false;
static int next;
void IRAM_ATTR onTimer() {
portENTER_CRITICAL_ISR(&timerMux);
interruptCounter++;
if(MagnetPower10mS)
{ digitalWrite(SOLENOID, HIGH);
MagnetPower10mS--;
}
else digitalWrite(SOLENOID, LOW);
if(MagnetDeadTime10mS)MagnetDeadTime10mS--;
if(ShowVolume10mS)ShowVolume10mS--;
if(Show45RPM10mS)Show45RPM10mS--;
if(ShowRadio10mS)ShowRadio10mS--;
if(ShowArt10mS)ShowArt10mS--;
if(UpdateTimeOut10mS)UpdateTimeOut10mS--;
if((interruptCounter % 5)==0)ScrollNow = true; // 50ms ticker for the vertical scrolling
if((interruptCounter % 10)==0)
{ bUpdateDisplay = true; // display refresh max 10 times a second
Ticker100mS = true; // ticker for Sonos communication
}
if((interruptCounter % 100)==0) // each second
{ next++;
switch(next)
{ case 1:
PollSonosVolume = true; // synchroniseer volume control with external volume changes
break;
case 2:
PollSonosSourceMode = true;
break;
case 3:
PollSonosState = true;
break;
default:
next = 0;
break;
}
}
portEXIT_CRITICAL_ISR(&timerMux);
}
char uri[100] = "";
char response[256] = "";
char metaBuffer[2048] = "";
char metaBuffer2[2048] = "";
String lastCmd;
void handleHomePage(AsyncWebServerRequest *request);
void handleCmd();
void handleNotFound();
void handleResponse();
void handleGet();
void handleGt();
void HomePage(AsyncWebServerRequest *request);
void File_Download(AsyncWebServerRequest *request);
void SD_file_download(AsyncWebServerRequest *request, String filename);
void File_Upload(AsyncWebServerRequest *request);
void handleFileUpload(AsyncWebServerRequest *request);
void SD_dir(AsyncWebServerRequest *request);
void printDirectory(AsyncWebServerRequest *request, const char * dirname, uint8_t levels);
void File_Stream(AsyncWebServerRequest *request);
void SD_file_stream(AsyncWebServerRequest *request, String filename);
void File_Delete(AsyncWebServerRequest *request);
void SD_file_delete(String filename);
void SendHTML_Header(AsyncWebServerRequest *request);
void SendHTML_Content(AsyncWebServerRequest *request);
void SendHTML_Stop(AsyncWebServerRequest *request);
void SelectInput(AsyncWebServerRequest *request, String heading1, String command, String arg_calling_name);
void ReportSDNotPresent(AsyncWebServerRequest *request);
void ReportFileNotPresent(AsyncWebServerRequest *request, String target);
void ReportCouldNotCreateFile(AsyncWebServerRequest *request, String target);
void ServePage(AsyncWebServerRequest *request, int page);
void BuildGdxTable(void);
bool WifiConnected; // global status
// This next function will be called during decoding of the jpeg file to
// render each block to the TFT. If you use a different TFT library
// you will need to adapt this function to suit.
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
{
// Stop further decoding as image is running off bottom of screen
if ( y >= tft.height() ) return 0;
// This function will clip the image block rendering automatically at the TFT boundaries
tft.pushImage(x, y, w, h, bitmap);
// Return 1 to decode next block
return 1;
}
void UpdateLCDTask(void * pvParameters)
{ while(1)
{ if(ScrollNow)
{ UpdateLCD2();
}
vTaskDelay(10 / portTICK_PERIOD_MS); // portTICK_PERIOD_MS = 1 ;-)
}
}
AsyncStaticWebHandler* handler;
void setup()
{ int ret;
// read settings
EEPROM.begin(EEPROM_SIZE);
NewRadioStation = EEPROM.read(EEPROM_RADIOSTATION);
if((NewRadioStation<1) || (NewRadioStation>20))
{ NewRadioStation = 1;
EEPROM.write(EEPROM_RADIOSTATION, NewRadioStation);
EEPROM.commit();
}
OldRadioStation = NewRadioStation;
DeviceType = EEPROM.read(EEPROM_DeviceType); // 0 = jukebox, 1 = Wallbox
if((DeviceType<0) || (DeviceType>1))
{ DeviceType = 0;
EEPROM.write(EEPROM_DeviceType, DeviceType);
EEPROM.commit();
}
EEPROM.end();
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
for(int i=0; i<NUM_LEDS; i++){
leds[i].setRGB(0, 0, 255);
}
FastLED.show();
pinMode(19,INPUT_PULLUP);
Serial.print(F("Initializing SD card..."));
pinMode(SD_MISO,INPUT_PULLUP);
pinMode(SD_MOSI,INPUT_PULLUP);
sdSPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
if (!SD.begin(SD_CS, sdSPI))
{
Serial.println(F("Card failed or not present, no SD Card data logging possible..."));
SD_present = false;
}
else
{
Serial.println(F("Card initialised... file access enabled..."));
SD_present = true;
}
P.begin();
P.setIntensity(1);
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
P.setScrollSpacing(1);
// timer for led strip timing en magneet timing
timer = timerBegin(0, 80, true); // prescaler 1MHz
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 10000, true); // elke 10mS
timerAlarmEnable(timer);
xTaskCreatePinnedToCore(
AnimateLedstrip, /* Task function. */
"AnimateLedstrip", /* name of task. */
2000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
NULL, /* Task handle to keep track of created task */
0); /* pin task to core 0 */
xDisplayMutex = xSemaphoreCreateMutex();
// 320x240 ILI9341 display
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setViewport(0, 0, 240, 240, true);
tft.loadFont(Arialnarrow26);
tft.setAttribute(UTF8_SWITCH, false);
tft.setTextSize(2);
tft.setTextWrap(false, false);
tft.setPivot(DIAL_CENTRE_X, DIAL_CENTRE_Y);
createNeedle(); // volume dial needle
// voor de jpeg decoder
// The byte order can be swapped (set true for TFT_eSPI)
TJpgDec.setSwapBytes(true);
// The jpeg image can be scaled by a factor of 1, 2, 4, or 8
TJpgDec.setJpgScale(1);
// The decoder must be given the exact name of the rendering function above
TJpgDec.setCallback(tft_output);
BuildGdxTable();
delay(1000);
xTaskCreatePinnedToCore(
ReadJukeKeys, /* Task function. */
"ReadJukeKeys", /* name of task. */
2000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
NULL, /* Task handle to keep track of created task */
0); /* pin task to core 0 */
xTaskCreatePinnedToCore(
UpdateLCDTask, /* Task function. */
"UpdateLCDTask", /* name of task. */
5000, /* Stack size of task */
NULL, /* parameter of the task */
1, /* priority of the task */
NULL, /* Task handle to keep track of created task */
1); /* pin task to core 0 */
TFT_line_print(0, "SETUP");
TFT_line_print(1, "SD CARD");
delay(1000);
if(SD_present == true)TFT_line_print(1, "SD CARD OK");
else TFT_line_print(1, "NO SD CARD");
delay(1000);
TFT_line_print(0, "WIFI SETUP");
TFT_line_print(1, "");
delay(1000);
setup2();
delay(2000);
if (WiFi.status() != WL_CONNECTED)
{ TFT_line_print(1, "FAIL");
TFT_line_print(2, "NO SSID");
TFT_line_print(3, "NO PASSWORD");
TFT_line_print(4, "");
TFT_line_print(5, "Use Your Smartphone And Connect To JUKEBOX-PORTAL For Your Wifi Setup");
bOpenPortal = true;
loop2();
TFT_line_print(5, "Setup Done --- Will Reboot Now");
delay(2000);
ESP.restart();
}
// configure web server
config.ssid = default_ssid;
config.wifipassword = default_wifipassword;
config.httpuser = default_httpuser;
config.httppassword = default_httppassword;
config.webserverporthttp = default_webserverporthttp;
Serial.println("Configuring Webserver ...");
server = new AsyncWebServer(config.webserverporthttp);
configureWebServer();
handler = &server->serveStatic("/", SD, "/").setCacheControl("max-age=6000");
// startup web server
Serial.println("Starting Webserver ...");
server->begin();
FollowTFTLine = 0;
TFT_line_print(0, "SERVICES");
TFT_line_print(1, "WebServer");
char text[32];
sprintf(text, "%d.%d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
TFT_line_print(2, text);
TFT_line_print(3, "");
TFT_line_print(4, "");
TFT_line_print(5, "");
delay(1000);
if(DeviceType==WALLBOX)
{ sprintf(text, "WALLBOX-%d", WiFi.localIP()[3]);
}
else if(DeviceType==JUKEBOX)
{ sprintf(text, "JUKEBOX-%d", WiFi.localIP()[3]);
}
else
{ sprintf(text, "ESP32-%d", WiFi.localIP()[3]);
}
setupOTA(text);
TFT_line_print(3, "HostName");
TFT_line_print(4, WiFi.getHostname());
delay(2500);
Serial.println(WiFi.localIP());
pinMode(SOLENOID, OUTPUT);
pinMode(ROTARY_ENCODER_BUTTON_PIN, INPUT_PULLUP);
pinMode(ROTARY_ENCODER_A_PIN, INPUT);
pinMode(ROTARY_ENCODER_B_PIN, INPUT);
pinMode(CANCEL_BUTTON, INPUT); // dit is de grote witte knop links, normally closed - zit op D35 en heeft een externe 10K pullup
pinMode(BLUE_BUTTON, INPUT_PULLUP); // dit is de blauwe knop rechts, normally open
pinMode(ENCODER_BUTTON, INPUT_PULLUP); // dit is de volume drukknop links, normally open
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
rotaryEncoder.setBoundaries(0,100,false); // with end stop
rotaryEncoder.setAcceleration(0); // at 25 already questionable behaviour
// mqtt for sending and receiving events to home assistant domotica
// broker ip address defined in mqtt.h
setupMQTT();
BootFase = 1;
// Serial.println("\n\nNetwork Configuration:");
// Serial.println("----------------------");
// Serial.print(" SSID: "); Serial.println(WiFi.SSID());
// Serial.print(" Wifi Status: "); Serial.println(WiFi.status());
// Serial.print("Wifi Strength: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm");
// Serial.print(" MAC: "); Serial.println(WiFi.macAddress());
// Serial.print(" IP: "); Serial.println(WiFi.localIP());
// Serial.print(" Subnet: "); Serial.println(WiFi.subnetMask());
// Serial.print(" Gateway: "); Serial.println(WiFi.gatewayIP());
// Serial.print(" Hostname: "); Serial.println(WiFi.getHostname());
// Serial.print(" DNS 1: "); Serial.println(WiFi.dnsIP(0));
// Serial.print(" DNS 2: "); Serial.println(WiFi.dnsIP(1));
// Serial.print(" DNS 3: "); Serial.println(WiFi.dnsIP(2));
// Serial.println();
Serial.println("Setup done.");
}
extern const char *p_MediaSource[];
extern JukeBoxSong JukeBoxSongs[];
static int songcount=0;
int getSDPlayList(void);
extern TFTline MyDisplay[];
void loop()
{ unsigned long currentMillis; // doorlopende milliseconde timer
static int FoundSonosDevices;
static int Select;
int t;
char c,text[128];
static int CountDown;
int n;
//FullTrackInfo info;
// currentMillis = millis();
// Serial.println(currentMillis);
if(BootFase) // after setup, BootFase == 1
{ if(UpdateTimeOut10mS == 0)
{ Serial.print("Bootfase=");Serial.println(BootFase);
if(BootFase<100 && BootFase!=7) // bootfase 7 is the countdown timer in the top bar - let it go undisturbed - yes clumsy it is
{ sprintf(text, "BOOTFASE: %d", BootFase);
TFT_line_print(0, text);
}
switch(BootFase)
{ case 1:
AllSet = false;
g_SDfallback = false;
g_Playlistfound = false;
NewSonosSourceMode = SONOS_SOURCE_UNKNOWN;
SonosSourceMode = SONOS_SOURCE_UNKNOWN;
ShowVolume10mS = 0;
BootFase++;
TFT_line_print(1, "");
TFT_line_print(2, "");
TFT_line_print(3, "");
TFT_line_print(4, "");
FollowTFTLine = 5;
if(DeviceMode==0)TFT_line_print(5, "JUKEBOX");
else if(DeviceMode==1)TFT_line_print(5, "WALLBOX");
else TFT_line_print(5, "ESP32");
UpdateTimeOut10mS = 100;
break;
case 2:
TFT_line_print(5, "SEARCHING FOR SONOS DEVICES");
BootFase++;
UpdateTimeOut10mS = 100; // one second
break;
case 3:
// connect to a Sonos box or fail
ACTIVE_sonosIP = GetSonosSetup(&FoundSonosDevices);
if(ACTIVE_sonosIP)
{ sprintf(ACTIVE_sonosHeaderHost, HEADER_HOST, ACTIVE_sonosIP[0], ACTIVE_sonosIP[1], ACTIVE_sonosIP[2], ACTIVE_sonosIP[3], UPNP_PORT); // 29 bytes max
BootFase=6;
}
else
{ NewSonosSourceMode = SONOS_SOURCE_UNKNOWN;
SonosSourceMode = SONOS_SOURCE_UNKNOWN;
CountDown = 15;
UpdateTimeOut10mS = 100;
ShowVolume10mS = 0;
TFT_line_print(5, "No Sonos Found --- New Scan Will Start In A Moment");
BootFase++;
}
UpdateTimeOut10mS = 200;
break;
case 4:
UpdateTimeOut10mS = 100;
if(CountDown)
{ sprintf(text, "%d", CountDown);
TFT_line_print(4, text);
CountDown--;
}
else
{ TFT_line_print(4, "0");
TFT_line_print(5, "New Scan Starting");
BootFase++;
UpdateTimeOut10mS = 500;
}
break;
case 5:
TFT_line_print(4, "");
TFT_line_print(5, "");
BootFase = 2;
break;
case 6: // we have wifi and one or more sonos devices found, one selected already
if(FoundSonosDevices>1) // offer oppertuntiy to change selected sonos device
{ CountDown = 150; // 15 seconds time to give user oppertunity to select other sonos device from the list of found ones
TFT_line_print(5, "Select Sonos With Volume Knob Or Just Wait");
sprintf(text, "WAIT %d", CountDown/10);
TFT_line_print(0, text);
UpdateTimeOut10mS = 100;
Select = rotary_loop(SonosLastUsed*5)/5; // sets knob to last sonos used (default choice) and use that for starters
BootFase++;
}
else BootFase = 9; // skip the selection party
break;
case 7: // let user select an other sonos or just timeout
UpdateTimeOut10mS = 10;
if(CountDown)
{ sprintf(text, "WAIT %d", CountDown/10);
TFT_line_print(0, text);
CountDown--;
Select = rotary_loop(-1) / 3;
if(Select>FoundSonosDevices-1)
{ Select = FoundSonosDevices-1;
Select = rotary_loop(Select*3)/3; // limit knob to max choice
}
for(n=0;n<FoundSonosDevices;n++)
{ if(n==Select)
{ //sprintf(text, "<%d> - %s", n+1, G_SonosDeviceList[n].Zonename);
sprintf(text, "%s", G_SonosDeviceList[n].Zonename);
TFT_line_print(n+1, text);
TFT_line_color(n+1, TFT_WHITE);
}
else
{ // sprintf(text, "%d - %s", n+1, G_SonosDeviceList[n].Zonename);
sprintf(text, "%s", G_SonosDeviceList[n].Zonename);
TFT_line_print(n+1, text);
TFT_line_color(n+1, TFT_SILVER);
}
}
}
else
{ TFT_line_print(0, "0");
Serial.print("Select=");Serial.print(Select);Serial.print("SonosLastUsed=");Serial.println(SonosLastUsed);
if(Select != SonosLastUsed)
{ Serial.print("Saved Select=");Serial.print(Select);Serial.print("SonosLastUsed=");Serial.println(SonosLastUsed);
EEPROM.begin(EEPROM_SIZE);
EEPROM.write(EEPROM_SONOS_LASTUSED, Select);
EEPROM.commit();
EEPROM.end();
SonosLastUsed = Select;
}
BootFase++;
}
break;
case 9:
ACTIVE_sonosIP = G_SonosFound_IPList[SonosLastUsed];
sprintf(ACTIVE_sonosHeaderHost, HEADER_HOST, ACTIVE_sonosIP[0], ACTIVE_sonosIP[1], ACTIVE_sonosIP[2], ACTIVE_sonosIP[3], UPNP_PORT); // 29 bytes max
TFT_line_print(1, G_SonosDeviceList[SonosLastUsed].Zonename);
TFT_line_print(2, "MODE");
TFT_line_print(3, "");
TFT_line_print(4, "");
FollowTFTLine = 5;
TFT_line_print(5, "RETRIEVING SONOS MODUS OPERANDI");
// strcpy(newMessage,"RETRIEVING SONOS MODUS OPERANDI --- ");
// newMessageAvailable = true;
runAsyncClient(SONOSGETMODE);
BootFase++;
UpdateTimeOut10mS = 500;
break;
case 10: