-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e35316d
Showing
21 changed files
with
20,261 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
 | ||
|
||
**ESP32-Sonos-Jukebox** | ||
|
||
This project is all about pimping up an old jukebox or wallbox to use it with a Sonos mediaplayer. | ||
You can use the jukebox to select and play songs from a stored playlist or select a radiostation. | ||
I use the Arduino IDE for this project. | ||
|
||
If you plan to use it, it probably needs work to adapt to your own creation. | ||
|
||
This package uses various other github repositories and libraries. | ||
Many thanks to all creators of these fanstastic resources! | ||
I would never got this far without it. | ||
|
||
**Some of the Libraries used:** | ||
|
||
* TFT_eSPI by Bodmer for the LCD display | ||
* ESP_WiFiManager by Khoih for setting up wifi credentials | ||
* Fastled library to drive the pixelled stuff | ||
* Arduino OTA to upload code over Wifi rather than USB cable | ||
|
||
**It uses an ESP32 Dev Kit module and has the following features:** | ||
|
||
* It reads the selection keys of the jukebox. Two rows of 10 keys are read by just 2 analog inputs. | ||
For this, there are voltage diviver resistors mounted on the switches. This reduces the number of wires to the ESP32. | ||
* It uses a TFT display to give some feedback about the song playing, radio station, volume setting etc. | ||
* It uses a encoder knob to control the volume for the Sonos. | ||
* It uses pixelleds to illuminate the keys, just for fun. | ||
* It uses an SD card to store some artwork and music files, but as a music source a sonos playlist is recommended. | ||
* It has a webserver that can produce printable jukebox 1" by 3" strips to insert in the slots of your jukebox or wallbox | ||
|
||
It is a work in progress. It is quite a mess really and needs fixes in many places. | ||
Provided as is. | ||
|
||
Enjoy! | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
#include <ESPmDNS.h> | ||
|
||
#include <WiFiUdp.h> | ||
#include <ArduinoOTA.h> | ||
|
||
// listener for skect upload invitation from arduino ide | ||
void ota_handle( void * parameter ) { | ||
for (;;) { | ||
ArduinoOTA.handle(); | ||
vTaskDelay(3500 / portTICK_PERIOD_MS); | ||
} | ||
} | ||
|
||
// prototype | ||
void WiFiEvent(WiFiEvent_t event); | ||
|
||
void setupOTA(const char* nameprefix) //, const char* ssid, const char* password) | ||
{ | ||
// Configure the hostname | ||
uint16_t maxlen = strlen(nameprefix) + 7; | ||
char *fullhostname = new char[maxlen]; | ||
uint8_t mac[6]; | ||
|
||
WiFi.macAddress(mac); | ||
snprintf(fullhostname, maxlen, "%s-%02x%02x%02x", nameprefix, mac[3], mac[4], mac[5]); | ||
ArduinoOTA.setHostname(fullhostname); | ||
delete[] fullhostname; | ||
|
||
Serial.println("OTA Hostname set..."); | ||
|
||
WiFi.setHostname(nameprefix); | ||
|
||
// Examples of different ways to register wifi events | ||
WiFi.onEvent(WiFiEvent); | ||
|
||
Serial.println("setupOTA - connected"); | ||
|
||
ArduinoOTA.onStart([]() { | ||
//NOTE: make .detach() here for all functions called by Ticker.h library - not to interrupt transfer process in any way. | ||
String type; | ||
if (ArduinoOTA.getCommand() == U_FLASH) | ||
type = "sketch"; | ||
else // U_SPIFFS | ||
type = "filesystem"; | ||
|
||
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() | ||
Serial.println("Start updating " + type); | ||
}); | ||
|
||
ArduinoOTA.onEnd([]() { | ||
Serial.println("\nEnd"); | ||
}); | ||
|
||
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | ||
Serial.printf("Progress: %u%%\r", (progress / (total / 100))); | ||
}); | ||
|
||
ArduinoOTA.onError([](ota_error_t error) { | ||
Serial.printf("Error[%u]: ", error); | ||
if (error == OTA_AUTH_ERROR) Serial.println("\nAuth Failed"); | ||
else if (error == OTA_BEGIN_ERROR) Serial.println("\nBegin Failed"); | ||
else if (error == OTA_CONNECT_ERROR) Serial.println("\nConnect Failed"); | ||
else if (error == OTA_RECEIVE_ERROR) Serial.println("\nReceive Failed"); | ||
else if (error == OTA_END_ERROR) Serial.println("\nEnd Failed"); | ||
}); | ||
|
||
Serial.println("setupOTA - about to begin"); | ||
ArduinoOTA.begin(); | ||
Serial.println("OTA Initialized"); | ||
|
||
xTaskCreate( | ||
ota_handle, /* Task function. */ | ||
"OTA_HANDLE", /* String with name of task. */ | ||
10000, /* Stack size in bytes. */ | ||
NULL, /* Parameter passed as input of the task */ | ||
1, /* Priority of the task. */ | ||
NULL); /* Task handle. */ | ||
Serial.println("OTA task created"); | ||
|
||
} | ||
|
||
void WiFiEvent(WiFiEvent_t event) { | ||
Serial.printf("[WiFi-event] event: %d\n", event); | ||
|
||
switch (event) { | ||
case SYSTEM_EVENT_WIFI_READY: | ||
Serial.println("WiFi interface ready"); | ||
break; | ||
case SYSTEM_EVENT_SCAN_DONE: | ||
Serial.println("Completed scan for access points"); | ||
break; | ||
case SYSTEM_EVENT_STA_START: | ||
Serial.println("WiFi client started"); | ||
break; | ||
case SYSTEM_EVENT_STA_STOP: | ||
Serial.println("WiFi clients stopped"); | ||
break; | ||
case SYSTEM_EVENT_STA_CONNECTED: | ||
Serial.println("Connected to access point"); | ||
break; | ||
case SYSTEM_EVENT_STA_DISCONNECTED: | ||
Serial.println("Disconnected from WiFi access point"); | ||
// WiFi.begin(copy_ssid, copy_password); | ||
// WiFi.begin(); | ||
break; | ||
case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: | ||
Serial.println("Authentication mode of access point has changed"); | ||
break; | ||
case SYSTEM_EVENT_STA_GOT_IP: | ||
Serial.print("Obtained IP address: "); | ||
Serial.println(WiFi.localIP()); | ||
break; | ||
case SYSTEM_EVENT_STA_LOST_IP: | ||
Serial.println("Lost IP address and IP address is reset to 0"); | ||
break; | ||
case SYSTEM_EVENT_STA_WPS_ER_SUCCESS: | ||
Serial.println("WiFi Protected Setup (WPS): succeeded in enrollee mode"); | ||
break; | ||
case SYSTEM_EVENT_STA_WPS_ER_FAILED: | ||
Serial.println("WiFi Protected Setup (WPS): failed in enrollee mode"); | ||
break; | ||
case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT: | ||
Serial.println("WiFi Protected Setup (WPS): timeout in enrollee mode"); | ||
break; | ||
case SYSTEM_EVENT_STA_WPS_ER_PIN: | ||
Serial.println("WiFi Protected Setup (WPS): pin code in enrollee mode"); | ||
break; | ||
case SYSTEM_EVENT_AP_START: | ||
Serial.println("WiFi access point started"); | ||
break; | ||
case SYSTEM_EVENT_AP_STOP: | ||
Serial.println("WiFi access point stopped"); | ||
break; | ||
case SYSTEM_EVENT_AP_STACONNECTED: | ||
Serial.println("Client connected"); | ||
break; | ||
case SYSTEM_EVENT_AP_STADISCONNECTED: | ||
Serial.println("Client disconnected"); | ||
break; | ||
case SYSTEM_EVENT_AP_STAIPASSIGNED: | ||
Serial.println("Assigned IP address to client"); | ||
break; | ||
case SYSTEM_EVENT_AP_PROBEREQRECVED: | ||
Serial.println("Received probe request"); | ||
break; | ||
case SYSTEM_EVENT_GOT_IP6: | ||
Serial.println("IPv6 is preferred"); | ||
break; | ||
case SYSTEM_EVENT_ETH_START: | ||
Serial.println("Ethernet started"); | ||
break; | ||
case SYSTEM_EVENT_ETH_STOP: | ||
Serial.println("Ethernet stopped"); | ||
break; | ||
case SYSTEM_EVENT_ETH_CONNECTED: | ||
Serial.println("Ethernet connected"); | ||
break; | ||
case SYSTEM_EVENT_ETH_DISCONNECTED: | ||
Serial.println("Ethernet disconnected"); | ||
break; | ||
case SYSTEM_EVENT_ETH_GOT_IP: | ||
Serial.println("Obtained IP address"); | ||
break; | ||
default: break; | ||
}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
//#define DEBUG_X 1 | ||
|
||
// this is a mess | ||
|
||
void SonosCheckDeviceList(void); | ||
void SortIPList(IPAddress *List,int Listsize); | ||
|
||
|
||
IPAddress GetSonosSetup(int *FoundSonosDevices) | ||
{ | ||
int n, n2; | ||
byte buf[4]; | ||
IPAddress ip; | ||
ActiveSonos = 0; | ||
|
||
G_Sonos = SonosUPnP(G_Sonosclient, ethConnectError); | ||
|
||
EEPROM.begin(EEPROM_SIZE); | ||
SonosLastUsed = EEPROM.read(EEPROM_SONOS_LASTUSED); | ||
EEPROM.end(); | ||
|
||
if(SonosLastUsed>(MAXSONOS-1))SonosLastUsed = 0; // SonosLastUsed ranges from 0 to MAXSONOS-1 | ||
Serial.print("******************* SonosLastUsed=");Serial.println(SonosLastUsed); | ||
|
||
|
||
// do a search for sonos devices and return a list with unique IP addresses and perhaps blank 0.0.0.0 addresses | ||
// shows addresses on display as they are found | ||
*FoundSonosDevices = G_Sonos.CheckUPnP(&G_SonosFound_IPList[0], MAXSONOS); | ||
|
||
if(*FoundSonosDevices<1)G_SonosFound_IPList[0]; // it makes no sense to continue, return with 0.0.0.0 | ||
|
||
// sort list from low to high and display again | ||
SortIPList(&G_SonosFound_IPList[0], MAXSONOS); | ||
|
||
// read list of stored IP addresses | ||
for(n=0;n<MAXSONOS;n++) | ||
{ EEPROM.begin(EEPROM_SIZE); | ||
for(n2=0;n2<4;n2++) | ||
{ ip[n2] = (byte)EEPROM.read(EEPROM_SONOSIP1+(n*4)+n2); | ||
} | ||
G_SonosIPList[n] = ip; | ||
if(ip!=0)Serial.print("* Read IP # from eeprom");Serial.print(", IP:");Serial.println(G_SonosIPList[n]); | ||
EEPROM.end(); | ||
} | ||
|
||
if(G_SonosIPList[SonosLastUsed]!= G_SonosFound_IPList[SonosLastUsed]) // situation changed, either new sonos devices or devices gone, new ip addresses assigned, whatever | ||
{ for(n=0;n<MAXSONOS;n++) // check if last sonos used is still member of the current family of found devices | ||
{ if(G_SonosIPList[SonosLastUsed] == G_SonosFound_IPList[n]) | ||
{ SonosLastUsed = n; // yes, still there | ||
break; | ||
} | ||
} | ||
// last used sonos could be gone, then use the first on on the list | ||
if(G_SonosIPList[SonosLastUsed] != G_SonosFound_IPList[SonosLastUsed]) | ||
{ // SonosLastUsed=0; // better not - prefer a rescan until the thing turns back on again | ||
} | ||
// update eeprom list as the situation changed | ||
for(n=0;n<MAXSONOS;n++) | ||
{ ip = G_SonosFound_IPList[n]; | ||
EEPROM.begin(EEPROM_SIZE); | ||
Serial.print("* Write IP # to eeprom");Serial.print(", IP:");Serial.println(ip); | ||
for(n2=0;n2<4;n2++) | ||
{ if(ip) | ||
{ EEPROM.write(EEPROM_SONOSIP1+(n*4)+n2, (int)ip[n2]); | ||
EEPROM.commit(); | ||
} | ||
} | ||
Serial.println("!"); | ||
EEPROM.end(); | ||
} | ||
EEPROM.begin(EEPROM_SIZE); | ||
EEPROM.write(EEPROM_SONOS_LASTUSED, SonosLastUsed); | ||
EEPROM.commit(); | ||
EEPROM.end(); | ||
} | ||
|
||
// check the found ones and put their names on the display | ||
SonosCheckDeviceList(); // check if these devices are alive and more important, get their actual names and serialnumbers | ||
|
||
Serial.print("Lastused:");Serial.print(SonosLastUsed);Serial.print(" ");Serial.println(G_SonosDeviceList[SonosLastUsed].Zonename); | ||
|
||
if(G_SonosDeviceList[SonosLastUsed].Zonename[0]!=0) // last used Sonos device is active - should always be the case | ||
{ | ||
// 00-0E-58-28-22-40:5 | ||
|
||
for(n2=0,n=0;n<17;n++) | ||
{ if(G_SonosDeviceList[SonosLastUsed].Serialnumber[n]!='-')ACTIVE_sonosSerialnumber[n2++]=G_SonosDeviceList[SonosLastUsed].Serialnumber[n]; | ||
ACTIVE_sonosSerialnumber[n2]=0; | ||
} | ||
Serial.print("UID:");Serial.println(ACTIVE_sonosSerialnumber); | ||
|
||
return G_SonosFound_IPList[SonosLastUsed]; // let's use it | ||
} | ||
return G_SonosFound_IPList[0]; | ||
} | ||
|
||
|
||
|
||
|
||
/**** Sonosroutine *****/ | ||
//void SonosCheck(IPAddress SAdress, SonosDevice* devicetocheck) | ||
void SonosCheckDeviceList(void) | ||
{ int n, t; | ||
SonosInfo info; | ||
char text[64]; | ||
//struct SonosInfo // JV new, pass text info as Char string | ||
//{ | ||
// uint16_t number; | ||
// char *uid; // Rincon-xxxx 32 bytes | ||
// char *serial; // 16 bytes serialnumber short - no '-' | ||
// char *seriesid; // Series ID or Sonos Type - 16bytes | ||
// char *zone; // Zone name - 32 bytes | ||
// char *medium; // medium - network, linein etc | ||
// char *status; // Status - play/stop/pause etc | ||
// char *playmode; // playmode, see SONOS_PLAY_MODE definitions | ||
// char *source; // source, defined in URI , see SONOS_SOURCE definitions | ||
// }; | ||
|
||
//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 | ||
// } | ||
|
||
for(n=0; n<MAXSONOS;n++) | ||
// for(n=0; n<1;n++) | ||
{ // assume and make empty | ||
G_SonosDeviceList[n].Zonename[0]=0; | ||
G_SonosDeviceList[n].UID[0]=0; | ||
G_SonosDeviceList[n].Serialnumber[0]=0; | ||
G_SonosDeviceList[n].Seriesid[0]=0; | ||
if(G_SonosFound_IPList[n]!=0) // no test on empty ip addresses | ||
{ //Serial.print("* Checking n: ");Serial.println(n); | ||
//Serial.print("* Checking Sonos Device at IP: ");Serial.println(G_SonosFound_IPList[n]); | ||
//tft.print("Chk IP:");tft.println(G_SonosIPList[n]); | ||
info = G_Sonos.getSonosInfo(G_SonosFound_IPList[n]); | ||
if(info.zone[0]!=0) | ||
{ for(t=0;info.zone[t]!=0;++t) {G_SonosDeviceList[n].Zonename[t]=info.zone[t];} G_SonosDeviceList[n].Zonename[t]=0; | ||
for(t=0;info.uid[t]!=0;++t) {G_SonosDeviceList[n].UID[t]=info.uid[t];} G_SonosDeviceList[n].UID[t]=0; | ||
for(t=0;info.serial[t]!=0;++t) {G_SonosDeviceList[n].Serialnumber[t]=info.serial[t];} G_SonosDeviceList[n].Serialnumber[t]=0; | ||
for(t=0;info.seriesid[t]!=0;++t) {G_SonosDeviceList[n].Seriesid[t]=info.seriesid[t];} G_SonosDeviceList[n].Seriesid[t]=0; | ||
// Serial.print("Zonename: ");Serial.println(G_SonosDeviceList[n].Zonename); | ||
// Serial.print("UID: ");Serial.println(G_SonosDeviceList[n].UID); | ||
// Serial.print("Serialnumber: ");Serial.println(G_SonosDeviceList[n].Serialnumber); | ||
// Serial.print("Seriesid: ");Serial.println(G_SonosDeviceList[n].Seriesid); | ||
// if(n==SonosLastUsed)sprintf(text, "<%d> - %s", n+1, G_SonosDeviceList[n].Zonename); | ||
if(n==SonosLastUsed)sprintf(text, "%s", G_SonosDeviceList[n].Zonename); | ||
else sprintf(text, "%s", G_SonosDeviceList[n].Zonename); | ||
TFT_line_print(n+1, text); | ||
if(n!=SonosLastUsed)TFT_line_color(n+1, TFT_SILVER); | ||
MyDisplay[n+1].refresh = true; | ||
|
||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
void SortIPList(IPAddress *List, int Listsize) | ||
{ int n, n2; | ||
char text[32]; | ||
IPAddress swap; | ||
swap[0]=0; | ||
for(n=0; n<Listsize-1;n++) | ||
{ for(n2=0; n2<Listsize-1;n2++) | ||
{ sprintf(text, "swaptest1 %d.%d.%d.%d", List[n2][0], List[n2][1], List[n2][2], List[n2][3]); | ||
Serial.println(text); | ||
sprintf(text, "swaptest2 %d.%d.%d.%d", List[n2+1][0], List[n2+1][1], List[n2+1][2], List[n2+1][3]); | ||
Serial.println(text); | ||
if(List[n2][3]>List[n2+1][3] && List[n2+1][3]!=0) // only swap if both are valid ip | ||
{ swap = List[n2]; | ||
List[n2] = List[n2+1]; | ||
sprintf(text, "%d.%d.%d.%d", List[n2][0], List[n2][1], List[n2][2], List[n2][3]); | ||
TFT_line_print(n2+1, text); | ||
List[n2+1]=swap; | ||
sprintf(text, "%d.%d.%d.%d", List[n2+1][0], List[n2+1][1], List[n2+1][2], List[n2+1][3]); | ||
TFT_line_print(n2+2, text); | ||
} | ||
} | ||
} | ||
for(n=0; n<Listsize;n++) | ||
{ sprintf(text, "%d.%d.%d.%d", List[n][0], List[n][1], List[n][2], List[n][3]); | ||
Serial.println(text); | ||
} | ||
} |
Oops, something went wrong.