Showing posts with label Sonos. Show all posts
Showing posts with label Sonos. Show all posts

Sunday, April 26, 2015

Vintage radio Sonos hack


Introducing the Tandberg Sølvsuper 10 radio, a product of the Scandinavian Hi-Fi golden age. Sadly, after decades in storage, the huge variable capacitor inside has seized from corrosion and the radio was beyond repair.

So, what to do? Can this piece of 60s design be refurbished and made useful in the world of Internet of Things?

TANDBERG Sølvsuper 10, stereo for kendere


Lighting

To not mess up the original look too much I choose to keep the original front panel light bulbs, but two problems had to be tackled:

  1. The high voltage vacuum tubes had to be replaced with a modern LED array.
  2. LEDs to indicate which button was last pressed had to be fitted.

LED bar graph replaces same color radio tube

To connect a total of 22 LEDs I ended up using three 74HC595N 8-bit shift registers. The incandescent light bulbs were connected using transistors that could handle somewhat larger currents.


Rotary Encoders

I decided to keep the original logarithmic volume pots and make crude readings using AD converters. Because the pots are logarithmic, the accuracy at the lower volume levels is limited when reading using a linear ADC. I managed to get reliable readings of the values 0-10 using the Arduino built-in 10 bit ADCs.

For the tuning, bass and treble dials I wanted to use rotary encoders. I removed the mechanical wire drive connecting the tuning dial, variable capacitor and frequency display needle and connected the dial directly to a rotary encoder using a piece of wood. For the bass and treble dials I made plastic adapters that allowed the rotary encoders to fit against the back of the original pots. Then I broke of the potentiometer wipers to allow free rotation.



Original tuner dial weight and shaft removed

Stepper Motor

To drive the frequency display needle I'm using a NMB-MAT PG35L048 stepper with gearbox, that I found on eBay. I secured the stepper to the circuit board using a white metal bar from some old IKEA drapes. The original wheel fit on the stepper shaft using a piece of cardboard as a spacer.


Snortrekk diagram from the TANDBERG Sølvsuper 10 service manual



Mainboard

To make sure that there is no lag when operating the front panel dials and buttons, most user input is detected using interrupts and the stepper and speaker is driven using timers. I choose to use two Arduinos because of the number of interrupts required and because I need quite a lot of real estate given my limited soldering skills. Here's how the hardware was connected:

  • Front panel buttons connect to Arduino Mega using interrupts PCINT17-23.
  • Tuning dial rotary encoder connects to Arduino Mega, using interrupts INT5 and PCINT16. Rotary encoder push button connects to D13 using interrupt PCINT17.
  • IR and RF receiver connects to Arduino Mega using interrupts INT4 and INT2.
  • Ethernet board connects to Arduino Mega.
  • Bass and treble dial rotary encoders connect to Arduino Uno using interrupts INT0, INT1, PCINT10 and PCINT20.
  • Stepper connects to Arduino Uno using Timer 2 for stepping.
  • Speaker connects to Arduino Uno using Timer 2 for tone generation.
  • LEDs and light bulbs connect to Arduino Uno and are dimmable using PWM.

Sensor and RTC clock headers and 434MHz RF receiver daughter board



Ethernet and USB

The intertubes would not be possible were it not for the RJ45 jack. In addition to an Ethernet connector I added a back panel USB socket and a reset button for convenience.


USB, Ethernet and recessed reset button in original back panel
Read More...

Saturday, January 17, 2015

SonosUPnP library for Arduino

The UPnP protocol was invented by Microsoft and saw the light of day in the late 1990s. The purpose of UPnP is to allow devices on a network to automatically discover and interface with each other. Microsoft chose to base UPnP on the hottest standards at that time, namely XML and SOAP (both introduced in 1998). XML and SOAP have since been criticized for their verbosity and complexity.

Project goals:
  • Making it possible to both control and read the state of my home speaker system using UPnP.
  • Keeping the binary size and RAM usage low enough for the library to run on Arduino boards using the ATmega328 chip.
  • Learning more about writing parsers, using PROGMEM and the UPnP protocol.

Reverse engineering with Wireshark:

It isn't feasible to implement the entire UPnP protocol on a device like the Arduino, and reading protocol specifications can be tedious. Therefore, for small projects like Sonos speaker integration, it's sometimes faster to use a tool like Wireshark to get an understanding of and replicate some of the messages being sent over the wire.

To get started with Wireshark: download, install, select capture interface and click start. Tip: Use the filter function.

Using PROGMEM (Flash) instead of RAM for storing strings:

The Arduino Uno and Duemilanove boards use the ATmega328 chip with only 2048 bytes of SRAM. Normally when you use strings (char arrays) in your code, all the strings will get copied to the heap on startup and waste your precious RAM.

The SonosUPnP library contains almost 4KB of string data and it would not be possible to run on the ATmega328 without using PROGMEM for strings. More information about using PROGMEM:

Sending UPnP messages seems simple enough, but what about receiving and parsing?

One of my goals when writing the SonosUPnP library was to support reading state in addition to controlling the Sonos speakers. This capability is also what sets my library apart from the other Sonos Arduino libraries I've seen so far.

Again the challenge is the limited amount of RAM: How do you parse and read the values you are interested in from an XML file while only using a few bytes of RAM? To accomplish this I decided to write a library for this particular purpose: MicroXPath

Downloads:

Read More...

Wednesday, December 31, 2014

MicroXPath library for Arduino

There are probably hundreds of C++ XML parsers out there, but reinventing the wheel is fun. MicroXPath is a state machine which purpose is to enable XML navigation on the Arduino platform while keeping the memory footprint as small as possible. With only 2048 bytes of SRAM on the Arduino Uno conserving memory is quite important. The PROGMEM version of MicroXPath uses no more than 9 bytes of RAM. The length of the search strings is irrelevant, but two bytes are consumed (to store a PROGMEM pointer) for each XPath level when calling setPath. This means that no more than 15 bytes of RAM is used when searching for an XPath that is three levels deep.

Why XML?

Sadly XML is widely used for data exchange on the internet and it’s likely that you, at some point, will find yourself creating a project where reading data from an XML data source is useful. For me it was a project that I’m currently working on, where I need to read the status of my Sonos speakers, that triggered the need. The Sonos speaker system uses UPnP which in turn uses SOAP over HTTP.

Usage:

The library needs no configuration. There is one preprocessor directive called XML_PICO_MODE which is on by default and disables XML validation and error tracking.

When using the library, start by setting the XML search path by calling the “setPath” function. Don’t forget to specify the path depth (length of the search path string array). If the path is “menu”, “food”, “name”, the pathSize should be set to 3. When you have configured the path you can start passing characters to the parser using the “findValue” function. “findValue” returns true when the specified path is matched and the parser reaches the element end tag character. This means that the next character is the first character of the XML element content.

If you would simply like to get the text content of the XML element matching the specified XML path you can use the “getValue” function. The “getValue” function works in the same way that “findValue” does, but it takes a pointer to an output buffer and an output buffer size in addition to the character being parsed. It will return true when it reaches the end tag of the matched XML element. Text content will be written to the output buffer as long as there is room.

When searching for several paths within one XML stream you must search for them in the same order that they occur in the XML and you must change the path by calling “setPath” as soon as you are done reading content. Given the following XML:

<menu>
  <food>
    <english>
      <name>Toast</name>
    </ english >
    <price>$5.95</price>
  </food>
</menu>

If you are getting both the name “Toast” and the price “$5.95” from the XML stream, your code should look something like this:

char result[10] = “”;
// Set path of the first element
xPath.setPath((const char *[]){ " menu", "food", "english", "name" }, 4);
// Read until the matching element end tag is found or end of stream
while (client.available() && !xPath.getValue(client.read(), result, sizeof(result)));
Serial.print("Name: ");
Serial.println(result);
// Set path of the second element
xPath.setPath((const char *[]){ "menu", "food", "price" }, 3);
// Read until the matching element end tag is found or end of stream
while (client.available() && !xPath.getValue(client.read(), result, sizeof(result)));
Serial.print("Price: ");
Serial.println(result);

How does it work?

The parser is a state machine which cycles to a set of predefined states while reading the XML file character by character. For example: When at the root level and a < (less than) character is read, the state changes from XML_PARSER_ROOT to XML_PARSER_START_TAG. In addition to the parser state, to be able to match on a specific XML path, the parser needs to keep track of the current node level in the XML node tree, the match node level, the element name character position and the element name match position. This is all the state that is needed: 5 bytes + the single character being read.

Limitations:
  • The library does currently not support finding or reading XML attributes. I did not need this for my Sonos project so I decided not to spend time on it. The feature can be easily added. If you need it or would like to contribute: please contact me by emailing me (use the email address included in license of all source and example files).
  • Because the parser can only parse the XML stream once, top-down, you must know the order of the elements on beforehand when searching for multiple paths within the same stream.

Downloads:

Tool used to calculate RAM usage:
Read More...