Documentation for controlling and preserving Bose SoundTouch devices
This guide provides technical implementation details for integrating with the Bose SoundTouch IoT configuration system. It covers the AWS IoT Core integration, certificate management, and device shadow operations.
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Mobile App │ │ Alexa Voice │ │ Web Interface │
│ │ │ Assistant │ │ │
└─────────┬───────┘ └─────────┬────────┘ └─────────┬───────┘
│ │ │
└──────────────────────┼───────────────────────┘
│
┌────────────▼──────────────┐
│ AWS IoT Core │
│ (MQTT Broker + │
│ Device Shadows) │
└────────────┬──────────────┘
│ MQTT/TLS
┌────────────▼──────────────┐
│ SoundTouch Device │
│ │
│ ┌─────────────────────┐ │
│ │ IoT Service │ │
│ │ (/opt/Bose/IoT) │ │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ BoseApp Service │ │
│ │ (/opt/Bose/BoseApp) │ │
│ └─────────────────────┘ │
└───────────────────────────┘
1. Device Boot
│
▼
2. Read IoT.xml (/mnt/nv/BoseApp-Persistence/1/IoT.xml)
│
▼
3. Load Certificates (/mnt/nv/IoTCerts/)
│
▼
4. Establish MQTT/TLS Connection
│
▼
5. Subscribe to Device Shadow Topics
│
▼
6. Publish Current Device State
│
▼
7. Listen for Delta Messages
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration
clientID="{device-unique-uuid}"
iotEndpoint="{aws-iot-endpoint}"
deployment="{PROD|DEV|TEST}" />
#include <rapidxml/rapidxml.hpp>
#include <fstream>
struct IoTConfig {
std::string clientID;
std::string iotEndpoint;
std::string deployment;
};
IoTConfig loadIoTConfig(const std::string& configPath) {
std::ifstream file(configPath);
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
rapidxml::xml_document<> doc;
doc.parse<0>(&content[0]);
auto configNode = doc.first_node("Configuration");
IoTConfig config;
config.clientID = configNode->first_attribute("clientID")->value();
config.iotEndpoint = configNode->first_attribute("iotEndpoint")->value();
config.deployment = configNode->first_attribute("deployment")->value();
return config;
}
/mnt/nv/IoTCerts/
├── iot-cert.pem.crt # Device client certificate
├── iot-private.pem.key # Device private key
└── default.pem # Additional cert data
/var/lib/iot/
└── rootCA.crt # AWS IoT Root CA
#include <openssl/x509.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
class IoTCertificateManager {
private:
static const std::string CERT_ENDPOINT;
static const std::string CERT_PATH;
static const std::string KEY_PATH;
public:
bool generateCSR() {
// Generate EC key pair
EC_KEY* eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(eckey);
// Create certificate request
X509_REQ* req = X509_REQ_new();
X509_REQ_set_version(req, 0);
// Set subject name
X509_NAME* name = X509_NAME_new();
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(unsigned char*)clientID.c_str(), -1, -1, 0);
X509_REQ_set_subject_name(req, name);
// Set public key
EVP_PKEY* pkey = EVP_PKEY_new();
EVP_PKEY_set1_EC_KEY(pkey, eckey);
X509_REQ_set_pubkey(req, pkey);
// Sign request
X509_REQ_sign(req, pkey, EVP_sha256());
return sendCSRToEndpoint(req, pkey);
}
bool sendCSRToEndpoint(X509_REQ* req, EVP_PKEY* pkey) {
// Send CSR to voice.api.bose.io/alexa/certificate
// Receive certificate response
// Store certificate and private key
return true;
}
};
const std::string IoTCertificateManager::CERT_ENDPOINT =
"https://voice.api.bose.io/alexa/certificate";
const std::string IoTCertificateManager::CERT_PATH =
"/mnt/nv/IoTCerts/iot-cert.pem.crt";
const std::string IoTCertificateManager::KEY_PATH =
"/mnt/nv/IoTCerts/iot-private.pem.key";
#include <aws/iot/MqttClient.h>
#include <aws/iot/ShadowClient.h>
class IoTConnectionManager {
private:
std::unique_ptr<awsiotsdk::MqttClient> mqttClient;
std::unique_ptr<awsiotsdk::Shadow> shadowClient;
IoTConfig config;
public:
awsiotsdk::ResponseCode connect() {
// Setup connection parameters
std::string endpoint = config.iotEndpoint;
uint16_t port = 8883; // MQTT over SSL
// Load certificates
std::string certPath = "/mnt/nv/IoTCerts/iot-cert.pem.crt";
std::string keyPath = "/mnt/nv/IoTCerts/iot-private.pem.key";
std::string rootCaPath = "/var/lib/iot/rootCA.crt";
// Create network connection
auto networkConnection = std::make_shared<awsiotsdk::network::MbedTLSConnection>(
endpoint, port, rootCaPath, certPath, keyPath
);
// Create MQTT client
mqttClient = awsiotsdk::MqttClient::Create(networkConnection);
if (!mqttClient) {
return awsiotsdk::ResponseCode::FAILURE;
}
// Connect with client ID
auto connectPacket = awsiotsdk::mqtt::ConnectPacket::Create(
config.clientID,
true, // cleanSession
awsiotsdk::mqtt::QoS::QOS0,
nullptr // will options
);
return mqttClient->Connect(std::chrono::milliseconds(5000), connectPacket);
}
awsiotsdk::ResponseCode initializeShadow() {
shadowClient = awsiotsdk::Shadow::Create(mqttClient);
if (!shadowClient) {
return awsiotsdk::ResponseCode::FAILURE;
}
// Subscribe to shadow delta
auto deltaHandler = [this](const std::string& thingName,
const std::string& payload) {
handleShadowDelta(thingName, payload);
};
return shadowClient->PerformUpdateAsync(
config.clientID,
"", // jsonString
deltaHandler,
std::chrono::seconds(10)
);
}
};
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
struct DeviceState {
std::string deviceState; // "CONNECTED" | "DISCONNECTED"
std::string powerState; // "ON" | "OFF"
std::string zoneState; // Zone configuration
std::string groupState; // Multi-room group info
};
class ShadowMessageBuilder {
public:
static std::string createReportedState(const DeviceState& state) {
rapidjson::Document doc;
doc.SetObject();
auto& allocator = doc.GetAllocator();
// Create state object
rapidjson::Value stateObj(rapidjson::kObjectType);
rapidjson::Value reportedObj(rapidjson::kObjectType);
// Add reported state fields
reportedObj.AddMember("deviceState",
rapidjson::Value(state.deviceState.c_str(), allocator),
allocator);
reportedObj.AddMember("powerState",
rapidjson::Value(state.powerState.c_str(), allocator),
allocator);
reportedObj.AddMember("zoneState",
rapidjson::Value(state.zoneState.c_str(), allocator),
allocator);
reportedObj.AddMember("groupState",
rapidjson::Value(state.groupState.c_str(), allocator),
allocator);
stateObj.AddMember("reported", reportedObj, allocator);
doc.AddMember("state", stateObj, allocator);
// Serialize to string
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
return buffer.GetString();
}
static DeviceState parseDesiredState(const std::string& json) {
rapidjson::Document doc;
doc.Parse(json.c_str());
DeviceState state;
if (doc.HasMember("state") && doc["state"].HasMember("desired")) {
auto& desired = doc["state"]["desired"];
if (desired.HasMember("powerState")) {
state.powerState = desired["powerState"].GetString();
}
if (desired.HasMember("zoneState")) {
state.zoneState = desired["zoneState"].GetString();
}
if (desired.HasMember("groupState")) {
state.groupState = desired["groupState"].GetString();
}
}
return state;
}
};
class IoTShadowManager {
private:
std::shared_ptr<awsiotsdk::Shadow> shadowClient;
std::string thingName;
DeviceState currentState;
public:
awsiotsdk::ResponseCode updateDeviceState(const DeviceState& newState) {
currentState = newState;
std::string payload = ShadowMessageBuilder::createReportedState(newState);
auto responseHandler = [](const std::string& thingName,
awsiotsdk::ShadowRequestType requestType,
awsiotsdk::ShadowResponseType responseType,
rapidjson::Document& payload) {
if (responseType == awsiotsdk::ShadowResponseType::Accepted) {
// Shadow update successful
std::cout << "Shadow updated successfully" << std::endl;
} else {
// Handle rejection
std::cout << "Shadow update rejected" << std::endl;
}
};
return shadowClient->PerformUpdateAsync(
thingName,
payload,
responseHandler,
std::chrono::seconds(10)
);
}
void handleShadowDelta(const std::string& thingName,
const std::string& payload) {
DeviceState desiredState = ShadowMessageBuilder::parseDesiredState(payload);
// Apply desired state changes to device
if (!desiredState.powerState.empty()) {
applyPowerStateChange(desiredState.powerState);
}
if (!desiredState.zoneState.empty()) {
applyZoneStateChange(desiredState.zoneState);
}
if (!desiredState.groupState.empty()) {
applyGroupStateChange(desiredState.groupState);
}
// Report updated state back to shadow
updateDeviceState(currentState);
}
};
<!-- /opt/Bose/etc/Shepherd-noncore.xml -->
<ShepherdConfig>
<daemon name="STSCertified"/>
<daemon name="IoT">
<env name="IOT_CONFIG_PATH">/mnt/nv/BoseApp-Persistence/1/IoT.xml</env>
<env name="IOT_CERT_PATH">/mnt/nv/IoTCerts</env>
</daemon>
<daemon name="TPDA">
<arg>-c</arg>
<arg>/opt/Bose/etc/Voice.xml</arg>
</daemon>
</ShepherdConfig>
#!/bin/bash
# /etc/init.d/SoundTouch fragment
# Create IoT directories
mkdir -p /mnt/nv/BoseLog /mnt/nv/IoTCerts /mnt/nv/BoseApp-Persistence/1
mkdir -m 700 -p /mnt/nv/BoseApp-Persistence/1/Keys
# Set proper permissions for certificate storage
chmod 700 /mnt/nv/IoTCerts
chown iot:iot /mnt/nv/IoTCerts
# Start shepherd daemon manager
shepherdd --config-dir /opt/Bose/etc --run-dir /var/run/shepherd
class ConnectionRetryManager {
private:
int maxRetries = 10;
int retryDelaySeconds = 5;
public:
awsiotsdk::ResponseCode connectWithRetry(IoTConnectionManager& manager) {
for (int attempt = 1; attempt <= maxRetries; ++attempt) {
std::cout << "Connection attempt " << attempt
<< " to MQTT port at host " << config.iotEndpoint << std::endl;
auto result = manager.connect();
if (result == awsiotsdk::ResponseCode::SUCCESS) {
std::cout << "Successfully connected to MQTT server" << std::endl;
return result;
}
std::cout << "MQTT port not available. Retrying in "
<< retryDelaySeconds << " seconds" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(retryDelaySeconds));
retryDelaySeconds *= 2; // Exponential backoff
}
std::cerr << "Failed to connect after " << maxRetries << " attempts" << std::endl;
return awsiotsdk::ResponseCode::FAILURE;
}
};
class IoTLogger {
public:
static void logConnectionStatus(const std::string& status) {
std::cout << "[IoT] Connection status: " << status << std::endl;
}
static void logShadowResponse(awsiotsdk::ShadowResponseType response,
const std::string& payload) {
if (response == awsiotsdk::ShadowResponseType::Accepted) {
std::cout << "[IoT] Shadow response: accepted. Payload: " << payload << std::endl;
} else {
std::cout << "[IoT] Shadow response: rejected" << std::endl;
}
}
static void logCertificateStatus(bool success) {
if (success) {
std::cout << "[IoT] Certificate generated successfully" << std::endl;
} else {
std::cerr << "[IoT] Failed to generate iot certificate" << std::endl;
}
}
};
#include <gtest/gtest.h>
class IoTConfigTest : public ::testing::Test {
protected:
void SetUp() override {
// Create test configuration file
std::ofstream file("/tmp/test_iot.xml");
file << R"(<?xml version="1.0" encoding="UTF-8" ?>
<Configuration clientID="test-client-id"
iotEndpoint="test.iot.amazonaws.com"
deployment="TEST" />)";
file.close();
}
};
TEST_F(IoTConfigTest, LoadConfiguration) {
auto config = loadIoTConfig("/tmp/test_iot.xml");
EXPECT_EQ(config.clientID, "test-client-id");
EXPECT_EQ(config.iotEndpoint, "test.iot.amazonaws.com");
EXPECT_EQ(config.deployment, "TEST");
}
TEST_F(IoTConfigTest, ShadowMessageBuilder) {
DeviceState state;
state.deviceState = "CONNECTED";
state.powerState = "ON";
std::string json = ShadowMessageBuilder::createReportedState(state);
// Verify JSON contains expected fields
EXPECT_TRUE(json.find("\"deviceState\":\"CONNECTED\"") != std::string::npos);
EXPECT_TRUE(json.find("\"powerState\":\"ON\"") != std::string::npos);
}
# Check certificate validity
openssl x509 -in /mnt/nv/IoTCerts/iot-cert.pem.crt -text -noout
# Verify private key matches certificate
openssl x509 -noout -modulus -in /mnt/nv/IoTCerts/iot-cert.pem.crt | openssl md5
openssl rsa -noout -modulus -in /mnt/nv/IoTCerts/iot-private.pem.key | openssl md5
# Test MQTT connectivity
mosquitto_pub -h a2bhvr9c4wn4ya.iot.us-east-1.amazonaws.com \
-p 8883 --cafile /var/lib/iot/rootCA.crt \
--cert /mnt/nv/IoTCerts/iot-cert.pem.crt \
--key /mnt/nv/IoTCerts/iot-private.pem.key \
-t '$aws/things/test/shadow/update' \
-m '{"state":{"reported":{"test":"value"}}}'
# Check service status
ps aux | grep IoT
# Monitor system logs
tail -f /mnt/nv/BoseLog/IoT.log
# Check Shepherd status
shepherdd --status
With device certificates and private keys available from firmware backups, it’s technically possible to monitor MQTT traffic:
# Subscribe to your device's shadow events only
CLIENT_ID="577ecfcc-2db3-4989-92c9-76d7704f9fb3" # Your device's UUID
mosquitto_sub -h a2bhvr9c4wn4ya.iot.us-east-1.amazonaws.com \
-p 8883 --cafile /var/lib/iot/rootCA.crt \
--cert /mnt/nv/IoTCerts/iot-cert.pem.crt \
--key /mnt/nv/IoTCerts/iot-private.pem.key \
-t "\$aws/things/$CLIENT_ID/shadow/update/accepted"
Device certificates are bound to restrictive policies:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:Connect",
"Resource": "arn:aws:iot:us-east-1:*:client/${iot:ClientId}"
},
{
"Effect": "Allow",
"Action": ["iot:Publish", "iot:Subscribe", "iot:Receive"],
"Resource": [
"arn:aws:iot:us-east-1:*:topic/$aws/things/${iot:ClientId}/shadow/*"
]
}
]
}
Limitations:
+ or #)# Capture MQTT traffic patterns without authentication
tcpdump -i eth0 -s0 -w soundtouch_iot.pcap host a2bhvr9c4wn4ya.iot.us-east-1.amazonaws.com
# Monitor connection patterns in real-time
tcpdump -i eth0 -n -A "host a2bhvr9c4wn4ya.iot.us-east-1.amazonaws.com and port 8883"
# Extract timing and packet size information
tcpdump -i eth0 -ttt -s0 "host a2bhvr9c4wn4ya.iot.us-east-1.amazonaws.com"
# Set up local Mosquitto broker
sudo apt-get install mosquitto mosquitto-clients
# Configure TLS (optional)
cat > /etc/mosquitto/conf.d/tls.conf << EOF
port 8883
cafile /path/to/ca.crt
certfile /path/to/server.crt
keyfile /path/to/server.key
require_certificate true
use_identity_as_username true
EOF
# Test local shadow operations
mosquitto_pub -h localhost -p 8883 \
-t '$aws/things/test-device/shadow/update' \
-m '{"state":{"reported":{"deviceState":"CONNECTED"}}}'
Expected shadow message patterns:
// Power state transitions
{
"state": {
"reported": {
"deviceState": "CONNECTED",
"powerState": "ON|OFF"
}
},
"timestamp": 1703875200
}
// Audio control updates
{
"state": {
"reported": {
"volume": 25,
"muted": false,
"source": "SPOTIFY"
}
}
}
// Multi-room coordination
{
"state": {
"reported": {
"zoneState": "master|slave",
"groupMembers": ["device1", "device2"],
"groupName": "Living Room"
}
}
}
Important Warnings:
Recommended Usage:
class IoTResearchMonitor {
private:
std::string deviceClientId;
std::ofstream messageLog;
public:
void captureMessagePatterns() {
// Subscribe only to owned device topics
std::string topic = "$aws/things/" + deviceClientId + "/shadow/update/accepted";
auto messageHandler = [this](const std::string& topic, const std::string& payload) {
// Log message structure for analysis
messageLog << "Topic: " << topic << std::endl;
messageLog << "Payload: " << payload << std::endl;
messageLog << "Timestamp: " << getCurrentTimestamp() << std::endl;
messageLog << "---" << std::endl;
// Parse and document state transitions
documentStateTransition(payload);
};
// WARNING: Only use with your own device certificates
connectToAWSIoT(messageHandler);
}
void documentStateTransition(const std::string& json) {
// Analyze JSON structure for local implementation
rapidjson::Document doc;
doc.Parse(json.c_str());
if (doc.HasMember("state") && doc["state"].HasMember("reported")) {
// Document field types and value ranges
auto& reported = doc["state"]["reported"];
for (auto& field : reported.GetObject()) {
std::cout << "Field: " << field.name.GetString()
<< ", Type: " << getJSONType(field.value) << std::endl;
}
}
}
};
This implementation guide provides the foundation for integrating with the Bose SoundTouch IoT system using AWS IoT Core, certificate-based authentication, and device shadow operations. The monitoring capabilities should be used responsibly and only for research purposes to develop local alternatives.