Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Frank-Bemelman committed Aug 1, 2022
0 parents commit e35316d
Show file tree
Hide file tree
Showing 21 changed files with 20,261 additions and 0 deletions.
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
![This is an image](images/IMG_1366.JPG)

**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!

2,188 changes: 2,188 additions & 0 deletions code/45rpm240.h

Large diffs are not rendered by default.

2,427 changes: 2,427 additions & 0 deletions code/Arialnarrow.h

Large diffs are not rendered by default.

1,240 changes: 1,240 additions & 0 deletions code/ConfigOnSwitch.h

Large diffs are not rendered by default.

165 changes: 165 additions & 0 deletions code/OTA.h
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;
}}
186 changes: 186 additions & 0 deletions code/SonosScan.ino
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);
}
}
Loading

0 comments on commit e35316d

Please sign in to comment.