#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
HTTPClient http;
String clientCredsBase64 = ""
String refresh_token = ""; // TODO: do auth code flow
String targetPlayerName = "Alpine";
String access_token = "";
String mhhid = "";
String groupId = "";
String responseBody = "";
void setup() {
Serial.begin(9600);
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println(" Connected!");
getHouseholds();
getGroups();
playNpr();
setVolume();
}
void setVolume() {
doRequest("https://api.ws.sonos.com/control/api/v1/groups/" + groupId + "/groupVolume",
"{\"volume\":\"20\"}");
}
void playNpr() {
doRequest("https://api.ws.sonos.com/control/api/v1/groups/" + groupId + "/favorites",
"{\"favoriteId\":\"167\",\"playOnCompletion\": true}");
}
void getGroups() {
if(doRequest("https://api.ws.sonos.com/control/api/v1/households/" + mhhid + "/groups") == 200) {
DynamicJsonDocument doc(5120);
DeserializationError error = deserializeJson(doc, responseBody);
if (error) {
Serial.print("Error parsing JSON data: ");
Serial.println(error.c_str());
} else {
// Find our player's group id
for(int i = 0; i < doc["groups"].size(); i++){
String name = doc["groups"][i]["name"].as<String>();
Serial.println("group name: " + name);
if(name.indexOf(targetPlayerName) >= 0){
groupId = doc["groups"][i]["id"].as<String>();
Serial.println("found: " + targetPlayerName + " groupId: " + groupId);
break;
}
}
}
}
}
void getHouseholds() {
if(doRequest("https://api.ws.sonos.com/control/api/v1/households") == 200){
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, responseBody);
if (error) {
Serial.print("Error parsing JSON data: ");
Serial.println(error.c_str());
} else {
mhhid = doc["households"][0]["id"].as<String>();;
Serial.println("mhhid: " + mhhid);
}
}
}
int doRequest(String url){
return doRequest(url, "");
}
int doRequest(String url, String requestBody) {
byte tries = 0;
int httpResponseCode = 0;
do {
http.begin(url);
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Bearer " + access_token);
if(requestBody.length() == 0) {
httpResponseCode = http.GET();
} else {
httpResponseCode = http.POST(requestBody);
}
if (httpResponseCode > 0) {
Serial.print("HTTP ");
Serial.println(httpResponseCode);
responseBody = http.getString();
Serial.println(responseBody);
} else {
Serial.print("Error ");
Serial.println(httpResponseCode);
}
http.end();
if(httpResponseCode == 401){
Serial.println("Refreshing token...");
if(!tokenRefresh()){
break; // refresh failed, don't try again
}
}
} while(tries++ < 1 && httpResponseCode == 401); // retry on failed tokens
return httpResponseCode;
}
bool tokenRefresh() {
http.begin("https://api.sonos.com/login/v3/oauth/access");
http.addHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
http.addHeader("Authorization","Basic " + clientCredsBase64);
String body = "grant_type=refresh_token&refresh_token=" + refresh_token;
int httpResponseCode = http.POST(body);
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
String payload = http.getString();
Serial.println(payload);
// Parse JSON data
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Error parsing JSON data: ");
Serial.println(error.c_str());
} else {
access_token = doc["access_token"].as<String>();;
Serial.println("access_token: " + access_token);
}
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
http.end();
return (httpResponseCode == 200);
}
void loop() {
delay(100); // TODO: Build something amazing!
}