Carl Bergan
Published

Tivoli OnionRadio

Adding an omega2 to an old Tivoli PAL radio to get web radio with minimal external modifications

IntermediateWork in progress3,200
Tivoli OnionRadio

Things used in this project

Hardware components

ATtiny85
Microchip ATtiny85
×1
DC-DC Buck Converter (12V->5V)
×1
USB Audio Card
×1
USB Bluetooth Audio Receiver
×1
Omega2
Onion Corporation Omega2
×1
RGB Diffused Common Anode
RGB Diffused Common Anode
×1
3 mm LED : White
×1
Resistor 100k ohm
Resistor 100k ohm
×2
Linear Regulator (Low Dropout)
Linear Regulator (Low Dropout)
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Tuner knob adapter

This transfers rotation from the tuner knob to the potentiometer

Schematics

PCB Schematic

Wiring instructions for the components

PCB Fabrication Files (Gerber)

File missing, please reupload.

Code

Main python program

Python
This code runs continuously on the Omega
#!/usr/bin/python
import onionGpio
import time
import subprocess
import serial
import socket
import logging

serialName = '/dev/ttyS1'
baudRate = 9600
mySerial = serial.Serial(serialName, baudRate, timeout=1)

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

formatter = logging.Formatter('%(asctime)s:%(levelname)s:%(message)s')
file_handler = logging.FileHandler('/root/radioErrors.log')
file_handler.setFormatter(formatter)

logger.addHandler(file_handler)

gpioR = onionGpio.OnionGpio(19)
gpioG = onionGpio.OnionGpio(15)
gpioB = onionGpio.OnionGpio(17)

status = gpioR.setOutputDirection(0)
status = gpioG.setOutputDirection(0)
status = gpioB.setOutputDirection(0)

def getChannelList():   # Imports channel list from text file
        myFile = open("/root/channels.txt", "r")
        myFile.seek(0,0)
        channels = myFile.readlines()
        myFile.close()
        channels = [channel.rstrip("\n") for channel in channels]
        channels = channels + channels
        return channels

def updateChannelNumbers(ser, channelList):     # sends the new number of channels to the ATtiny
        nChannels = len(channelList)
        n = str(nChannels)
        ser.write(n)
        x = ser.readline()
        if(x):
                chan = int(x)-1
        else:
                chan = 0
        return chan


def is_connected():                     # checks for internet connectivity
        try:
                socket.create_connection(("www.google.com", 80))
                return True
        except:
                blink()
        return False

def reconnect():                        # Attempts to restart the wifi
        print "attempting to reconnect"
        netProc = subprocess.Popen(["wifimanager"], shell=False)
        netProc.wait()

def setColor(color):                    # changes the color of the RGB LED
        if (color=='red'):
                        gpioR.setValue(1)
                        gpioG.setValue(0)
                        gpioB.setValue(0)
        elif (color=='blue'):
                        gpioR.setValue(0)
                        gpioG.setValue(0)
                        gpioB.setValue(1)
        elif (color=='green'):
                        gpioR.setValue(0)
                        gpioG.setValue(1)
                        gpioB.setValue(0)
        else:
                        gpioR.setValue(0)
                        gpioG.setValue(0)
                        gpioB.setValue(0)

def blink():                            # blink red light. (not in use)
        setColor('red')
        time.sleep(100)
        setColor('off')
        time.sleep(200)
        setColor('red')
        time.sleep(100)
        setColor('off')
        time.sleep(200)

channels = getChannelList()
chan = updateChannelNumbers(mySerial, channels)
P1 = channels[chan]

oldproc = subprocess.Popen(["mpg123", "-q", "-m", P1], shell=False)
subprocess.call(["kill", "-9", "%d" %oldproc.pid])
oldproc.wait()


oldButtonPos = 0;
buttonPos = 0;

nTries = 0
setColor('blue')
while (is_connected()!=True):
        reconnect()
        nTries += 1
        if(nTries>3):
                #raise Exception('failed to connect to the internet')
                break
if(nTries>3):
        setColor('red')
else:
        setColor('green')

while True:

        try:
                x = mySerial.readline()
                #interpret data from the ATtiny (button change + tuner change)
                if(x):
                        print "Received data: " + x.replace('\r\n', '')
                        if x=='BT\r\n':
                                buttonPos=2;

                        elif x=='OF\r\n':
                                buttonPos=0;

                        elif x=='FM\r\n':
                                buttonPos=1;
                        else:
                                chan = int(x)-1
                                print "Channel: " + str(chan)
                                P1 = channels[chan]
                                proc = subprocess.Popen(["mpg123", "-q", "-m", P1], shell=False);
                                time.sleep(1)
                                subprocess.call(["kill", "-9", "%d" % oldproc.pid]);
                                oldproc.wait()
                                oldproc = proc

                else:
                        #print "timeout"
                        pass
                # Perform actions when the button has changed position

                if(oldButtonPos!=buttonPos):
                        oldButtonPos = buttonPos;

                        print "Button position: " + str(buttonPos)
                        if(buttonPos==0):
                                print "Radio has been swithed off"
                                print "Changing to red"
                                setColor('red')
                                subprocess.call(["killall", "mpg123"]);
                                oldproc.wait()
                                #subprocess.call(["killall", "shairport-sync"]);
                        elif(buttonPos==1):
                                print "Radio mode"
                                print "Changing to green"
                                setColor('green');
                                P1 = channels[chan]
                                oldproc = subprocess.Popen(["mpg123", "-q", "-m", P1], shell=False);
                                #subprocess.call(["killall", "shairport-sync"]);
                        else:
                                print "Bluetooth mode"
                                print "Changing to blue"
                                setColor('blue')
                                subprocess.call(["kill", "-9", "%d" % oldproc.pid]);
                                oldproc.wait()
                                #subprocess.call(["shairport-sync", "-d"]);
                                print "loading new channel list"
                                channels = getChannelList()
                                chan = updateChannelNumbers(mySerial, channels)
                                print "channel list updated"

        except IOError:
                logger.debug("IOError")
        except IndexError:
                logger.debug("IndexError")
        except ValueError:
                logger.debug("ValueError")
        except KeyboardInterrupt:
                setColor('off')
                if (buttonPos==1):
                        subprocess.call(["kill", "-9", "%d" % oldproc.pid]);
                        oldproc.wait()
                gpioR._freeGpio
                gpioG._freeGpio
                gpioB._freeGpio
                print "Good bye"
                logger.debug("Safe exit")
                exit()
        except:
                setColor('off')
                if (buttonPos==1):
                        subprocess.call(["kill", "-9", "%d" % oldproc.pid]);
                        oldproc.wait()
                gpioR._freeGpio
                gpioG._freeGpio
                gpioB._freeGpio
                #open('/root/radioErrors.log', 'w').close()
                logger.exception("well, this is embarrassing...")

ATtiny-code

Arduino
This runs on the ATtiny. It handles both analog inputs (from the tuner knob) and digital inputs (power button positions)
#include <SoftwareSerial.h>

#define RX 3
#define TX 4

SoftwareSerial Serial(RX, TX);  // RX, TX according to
                                // http://arduino.cc/en/Reference/SoftwareSerial


int adc1;
int zones = 1;
int oldZone = 1;
int newZone = 1;
int oldPos = 0;
int newPos = 0;
int tempPos = 0;
int nStable = 0;
int nZones = 8;
int reps = 0;
char data;

float adcFrac;

void setup() {
Serial.begin(9600);
Serial.println("Initializing...");
}

int determineButtonPos(){             // Reads the position of the power button
  int BT = not(digitalRead(1));
  int FM = not(digitalRead(0));

    if(BT){
      return 2;
    }
    else if(FM){
      return 1;
    }
    else {
      return 0;
    }
}

void loop() {

  newPos = determineButtonPos();
  
  if (newPos != oldPos) {             // This will only execute if the button position has changed
    
    /* --- To avoid jitter in the button switching, check if the new position remains the same -- */
    nStable = 0;                                                                                  //
    while (nStable<3){                                                                            //
       delay(100);                                                                                //
       tempPos = determineButtonPos();                                                            //
       if(tempPos==newPos){                                                                       //
        nStable++;                                                                                //        
       }                                                                                          //
       else{                                                                                      //
        nStable=0;                                                                                //
        newPos = tempPos;                                                                         //
       }                                                                                          //
    }                                                                                             //
    /* --------------- New button position is now stable ---------------------------------------  */

    oldPos = newPos;

    /* ---------------------------- Output the button position to the serial buffer --------------*/
    if(newPos==1){                                                                                //
      Serial.println("FM");                                                                       //
      reps = 0;                                                                                   //
    }                                                                                             //
    else if(newPos == 2){                                                                         //
      Serial.println("BT");                                                                       //
    }                                                                                             //
    else{                                                                                         //
      Serial.println("OF");                                                                       //
    }                                                                                             //
    /* ---------------------------- Button position has now been communicated to serial ----------*/
  }
  

  while (newPos == 1){            // Executes as long as the power button is in the "FM"-position

    newPos = determineButtonPos();  // Detect if the button has changed away from the "FM"-position

    
    adc1 = analogRead(1);
    adcFrac = (1023-float(adc1)) / 1023;
    
    /* ---------------- Check if the Onion has said anything about the number of zones -----------*/
    if(Serial.available()) {                                                                      //
       nZones = Serial.parseInt();                                                                //
       newZone = (int) (adcFrac * nZones);                                                        //
       oldZone = newZone;                                                                         //
       Serial.println(newZone);                                                                   //
    }                                                                                             //
    else {                                                                                        //
     newZone = (int) (adcFrac * nZones)+1;                                                        //
    }                                                                                             //
    /* -------------------------------------------------------------------------------------------*/

    /* --------------------- If the zone has changed, write it to serial -------------------------*/
    if (newZone != oldZone){                                                                      //
      reps+=1;                                                                                    //
      if (reps>=10){                  // Check if the zone change is stable (avoid jitter)        //
        Serial.println(newZone);                                                                  //
        oldZone = newZone;                                                                        //
      }                                                                                           //
    }                                                                                             //
    else {                                                                                        //
      reps = 0;                                                                                   //
    }                                                                                             //
    /* -------------------------------------------------------------------------------------------*/
    delay(20);
  }
  delay(50);
}

Credits

Carl Bergan

Carl Bergan

1 project • 0 followers

Comments