The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Tag: Improvements

Making the world a better place, one piece at a time

  • Screw Cutting Fixture: Not Quite Right

    A stack of PCB proto boards arrived from halfway around the planet and, in a rare preemptive strike, I made some holders before I need them:

    PCB Protoboard - in holder
    PCB Protoboard – in holder

    That called for trimming eight 4-40 screws, so I also tried a first pass at a screw cutting fixture:

    Screw cutting fixture - 4-40 insert
    Screw cutting fixture – 4-40 insert

    That’s an ordinary 4-40 brass insert epoxied inside a drilled hole, with a snug 4-40 clearance hole on the other side.

    If I needed slightly longer screws, they’d get a jam nut on the inside, but in this case I just tightened it firmly, ran the lathe in reverse, and cut against the far side to keep the screw from working loose:

    Screw cutting fixture - in use
    Screw cutting fixture – in use

    The insert is on the left side of the fixture, just under the screw head.

    Unfortunately, my faith in epoxy bonding led me astray: there’s just not enough griptivity to lock the insert inside that drilled aluminum hole. Despite my taking sissy cuts, the cutting forces pushed the insert out of its hole. I completed the mission by cutting the last four screws by hand.

    The original idea for the fixture would have me turning the fixture from steel and tapping the screw hole. That puts a lot of labor into something that may get chewed up fairly quickly, so I wondered if a brass insert would suffice.

    Not in that orientation, it doesn’t. Putting the insert on the other side of the fixture (to the right, away from the chuck) would have the cutting forces pushing it into the fixture, which should have been obvious from the start. So it goes.

    When I must cut a larger screw, I’ll redrill that fixture, put the insert on the other side, and see how that plays.

    If I turned the fixture from steel with similar drilled similar holes, I could braze / silver solder the insert into the hole: that would prevent it from turning, even if the jam nut wasn’t quite up to the task.

  • Kenmore 158 Foot Pedal: Fine Tuning

    After a week of use, Mary decided the single additional graphite disk in each stack produced a too-high initial speed when the sewing machine started up; this being a matter of how it feels injects some of trial-and-error into the repair.

    Shaving a graphite disk down from 0.8 to 0.4 mm seemed entirely too messy, so I snipped squares from 0.40 mm = 16 mil brass shim stock, nibbled the edges into a polygon, and filed the resulting vertexes to produce a (rough) circle:

    Kenmore 158 Foot Pedal - 0.40 mm brass shims
    Kenmore 158 Foot Pedal – 0.40 mm brass shims

    Each stack looks like this:

    • 1.5 mm graphite disk (double-thick)
    • 0.30 mm brass (original part)
    • 0.79 mm graphite disk
    • 0.40 brass (new part)
    • The rest of the stack

    Protip: dump those shards onto a strip of wide masking tape, fold gently until it’s all corners, and drop in the trash. Otherwise, you’ll pull those things out of your shoes and fingers for months…

    You can get cheaper nibbling tools nowadays; I’ve had mine for decades.

  • 60 kHz Preamp: Board Holder

    A cleaned up version of my trusty circuit board holder now keeps the 60 kHz preamp off what passes for a floor in the attic:

    Preamp in attic
    Preamp in attic

    The solid model became slightly taller than before, due to a serious tangle of wiring below the board, with a narrower flange that fits just as well in the benchtop gripper:

    Proto Board - 80x110
    Proto Board – 80×110

    Tidy brass inserts epoxied in the corners replace the previous raw screw holes in the plastic:

    Proto Board Holder - 4-40 inserts and screws
    Proto Board Holder – 4-40 inserts and screws

    The screws standing on their heads have washers epoxied in place, although that’s certainly not necessary; the dab of left-over epoxy called out for something. The screws got cut down to 7 mm after curing.

    The preamp attaches to a lumpy circle of loop antenna hung from the rafters and returns reasonable results:

    WWVB - morning - 2017-01-16
    WWVB – morning – 2017-01-16

    The OpenSCAD source code as a GitHub Gist:

    // Test support frame for proto boards
    // Ed Nisley KE4ZNU – Jan 2017
    ClampFlange = true;
    Channel = false;
    //- Extrusion parameters – must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1;
    HoleWindage = 0.2;
    //- Screw sizes
    inch = 25.4;
    Tap4_40 = 0.089 * inch;
    Clear4_40 = 0.110 * inch;
    Head4_40 = 0.211 * inch;
    Head4_40Thick = 0.065 * inch;
    Nut4_40Dia = 0.228 * inch;
    Nut4_40Thick = 0.086 * inch;
    Washer4_40OD = 0.270 * inch;
    Washer4_40ID = 0.123 * inch;
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Insert = [3.9,4.6,5.8];
    //- PCB sizes
    PCBSize = [110.0,80.0,1.5];
    PCBShelf = 2.0;
    Clearance = 2*[ThreadWidth,ThreadWidth,0];
    WallThick = 5.0;
    FrameHeight = 10.0;
    ScrewOffset = 0.0 + Clear4_40/2;
    ScrewSites = [[-1,1],[-1,1]]; // -1/0/+1 = left/mid/right and bottom/mid/top
    OAHeight = FrameHeight + Clearance[2] + PCBSize[2];
    FlangeExtension = 3.0;
    FlangeThick = IntegerMultiple(2.0,ThreadThick);
    Flange = PCBSize
    + 2*[ScrewOffset,ScrewOffset,0]
    + 2*[Washer4_40OD,Washer4_40OD,0]
    + [2*FlangeExtension,2*FlangeExtension,(FlangeThick – PCBSize[2])]
    ;
    echo("Flange: ",Flange);
    NumSides = 4*5;
    WireChannel = [Flange[0],15.0,3.0 + PCBSize[2]];
    WireChannelOffset = [Flange[0]/2,25.0,(FrameHeight + PCBSize[2] – WireChannel[2]/2)];
    //- Adjust hole diameter to make the size come out right
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    //- Build it
    difference() {
    union() { // body block
    translate([0,0,OAHeight/2])
    cube(PCBSize + Clearance + [2*WallThick,2*WallThick,FrameHeight],center=true);
    for (x=[-1,1], y=[-1,1]) { // screw bosses
    translate([x*(PCBSize[0]/2 + ScrewOffset),
    y*(PCBSize[1]/2 + ScrewOffset),
    0])
    cylinder(r=Washer4_40OD,h=OAHeight,$fn=NumSides);
    }
    if (ClampFlange) // flange for work holder
    linear_extrude(height=Flange[2])
    hull()
    for (i=[-1,1], j=[-1,1]) {
    translate([i*(Flange[0]/2 – Washer4_40OD/2),j*(Flange[1]/2 – Washer4_40OD/2)])
    circle(d=Washer4_40OD,$fn=NumSides);
    }
    }
    for (x=[-1,1], y=[-1,1]) { // screw position indexes
    translate([x*(PCBSize[0]/2 + ScrewOffset),
    y*(PCBSize[1]/2 + ScrewOffset),
    -Protrusion])
    rotate(x*y*180/(2*6))
    PolyCyl(Clear4_40,(OAHeight + 2*Protrusion),6); // screw clearance holes
    translate([x*(PCBSize[0]/2 + ScrewOffset),
    y*(PCBSize[1]/2 + ScrewOffset),
    OAHeight – PCBSize[2] – Insert[LENGTH]])
    rotate(x*y*180/(2*6))
    PolyCyl(Insert[OD],Insert[LENGTH] + Protrusion,6); // inserts
    translate([x*(PCBSize[0]/2 + ScrewOffset),
    y*(PCBSize[1]/2 + ScrewOffset),
    OAHeight – PCBSize[2]])
    PolyCyl(1.2*Washer4_40OD,(PCBSize[2] + Protrusion),NumSides); // washers
    }
    translate([0,0,OAHeight/2]) // through hole below PCB
    cube(PCBSize – 2*[PCBShelf,PCBShelf,0] + [0,0,2*OAHeight],center=true);
    translate([0,0,(OAHeight – (PCBSize[2] + Clearance[2])/2 + Protrusion/2)]) // PCB pocket on top
    cube(PCBSize + Clearance + [0,0,Protrusion],center=true);
    if (Channel)
    translate(WireChannelOffset) // opening for wires from bottom side
    cube(WireChannel + [0,0,Protrusion],center=true);
    }
  • Kenmore 158: Presser Foot Tweak

    After watching Mary fiddle with the shrunken presser foot screw, I tapered the tip as a guide into the hole:

    Presser Foot Screw - tapered tip
    Presser Foot Screw – tapered tip

    A hint-and-tip (which I cannot, alas, find again) suggested making bushings to simplify trimming screws in the lathe. A rim on the bushing aligns it with the front of the jaws, the screw threads into the central hole with a jam nut locking it in place, then you can turn / shape / file the end of the screw just beyond bushing with great support and a total lack of drama.

    For the moment, I just aligned the screw in the tailstock drill chuck, crunched the three-jaw spindle chuck on the screw head, backed off the tailstock, took unsupported sissy cuts, and it was all good:

    Presser Foot Screw - chuck alignment
    Presser Foot Screw – chuck alignment

    Gotta make those bushings!

  • Raspberry Pi vs. Avahi

    It turns out that the various Avahi daemons performing the magick between whatever.local names and dotted-quad 192.168.1.101 addresses for Raspberry Pi descend into gibbering madness when confronted with:

    • One name corresponding to multiple IP addresses
    • One IP address used for multiple MAC addresses
    • Multiple names for one IP address
    • Multiple names for one MAC address
    • Multiple IP addresses for one MAC address
    • Multiple MAC addresses for one IP address
    • Any and all combinations of the above at various times

    The least of the confusion involved an incorrect IP address linked to a familiar name pulled from deep history by a baffled daemon doing the best it can with what it thinks it knows. Despite what I concluded, rather early in the process, there’s no real error, other than my performing what amounted to a self-inflicted fast-flux nameserver attack.

    Anyhow, I devoted the better part of an afternoon to sorting out the mess, which involved labeling all the streaming radio players with their MAC addresses and rebooting them one-by-one to allow all the daemons time to recognize the current situation:

    Raspberry Pi 3 - WiFi MAC address
    Raspberry Pi 3 – WiFi MAC address

    That label corresponds to the Pi 3’s on-board WiFi adapter.

    For Pi 2 boxen, the MAC address travels with the WiFi adapter jammed into a USB port:

    SunFounder WiFi Adapter - MAC address
    SunFounder WiFi Adapter – MAC address

    I didn’t label the (unused) Ethernet jacks, figuring I’d solve that problem after it trips me up.

  • Raspberry Pi Streaming Radio Player: Room Customization

    Sometimes you (well, I) want a bit of late-night music, which is now one button press away. However, I initially set things up so the Raspberry Pi’s startup code executed a Python script on a network share from the file server in the basement, which shuts down around midnight after the daily backup.

    Keeping a local copy meant having to update that copy whenever I tweak the code, a nuisance not to be tolerated. This Bash (or whatever) code in /etc/rc.local figures out if the server is up and, if so, updates the local copy from the server. If the server isn’t up, then it just runs with what it has:

    #!/bin/sh
    # was !/bin/sh -e
    
    ... snippage ...
    
    server=192.168.1.4
    
    ping -c 1 $server
    if [ $? -eq 0 ]
    then
      mount -o ro ${server}:/mnt/bulkdata/Project\ Files/Streaming\ Media\ Player/Firmware/ /mnt/part
      rsync -auv /mnt/part/Streamer.py /home/pi
      umount /mnt/part
    fi
    
    sudo -u pi sh -c 'python /home/pi/Streamer.py any' &
    

    N.B.: you must remove the -e from the shebang, because otherwise the script jams to a stop when the ping fails. Took me a while to figure that out, yup.

    Use raspi-config to force the startup sequence to wait until the network is available. Turns out that the DHCP process can stall for half a minute, so fixed timeouts don’t work.

    Hardcoding the server IP address eliminates a whole bunch of mysterious failures apparently due to whatever handles the translation from mollusk.local to the dotted quad. Maybe that’s not really a problem, but I’ll run with it.

    Now the streamers fetch the Latest and Greatest version whenever they’re on during the day and run their local copy, with the room parameter telling it where it lives.

    Life is good!

  • Raspberry Pi Streaming Radio Player: Command Line Parsing

    Some experience suggested different default stations & volume settings for the streamers in various rooms, so the Python code now parses its command line to determine how to configure itself:

    import argparse as args
    
    cmdline = args.ArgumentParser(description='Streaming Radio Player',epilog='KE4ZNU - http://softsolder.com')
    cmdline.add_argument('Loc',help='Location: BR1 BR2 ...',default='any',nargs='?')
    args = cmdline.parse_args()
    

    I should definitely pick a different variable name to avoid the obvious clash.

    With that in hand, the customization takes very effort:

    CurrentKC = 'KEY_KP7'
    MuteDelay = 8.5         # delay before non-music track; varies with buffering
    UnMuteDelay = 7.5       # delay after non-music track
    MixerVol = '15'         # mixer gain
    
    Location = vars(args)['Loc'].upper()
    print 'Player location: ',Location
    logging.info('Player setup for: ' + Location)
    
    if Location == 'BR1':
      CurrentKC = 'KEY_KPDOT'
      MixerVol = '10'
    elif Location == 'BR2':
      MuteDelay = 6.0
      UnMuteDelay = 8.0
    MixerVol = '5'
    

    The Location = vars() idiom returns a dictionary of all the variables and their values, of which there’s only one at the moment. The rest of the line extracts the value and normalizes it to uppercase.

    Now we can poke the button and get appropriate music without having to think very hard.

    Life is good!

    The Python source code, which remains in dire need of refactoring, as a GitHub Gist:

    from evdev import InputDevice,ecodes,KeyEvent
    import subprocess32 as subp
    import select
    import re
    import sys
    import time
    import logging
    import os.path
    import argparse as args
    cmdline = args.ArgumentParser(description='Streaming Radio Player',epilog='KE4ZNU – http://softsolder.com')
    cmdline.add_argument('Loc',help='Location: BR1 BR2 …',default='any',nargs='?')
    args = cmdline.parse_args()
    Media = {'KEY_KP7' : ['Classical',False,['mplayer','–quiet','-playlist','http://stream2137.init7.net/listen.pls'%5D%5D,
    'KEY_KP8' : ['Jazz',False,['mplayer','–quiet','-playlist','http://stream2138.init7.net/listen.pls'%5D%5D,
    'KEY_KP9' : ['WMHT',False,['mplayer','–quiet','http://wmht.streamguys1.com/wmht1'%5D%5D,
    'KEY_KP4' : ['Classic 1000',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/1000classicalhits.m3u'%5D%5D,
    'KEY_KP5' : ['DCNY 911',False,['mplayer','–quiet','-playlist','http://www.broadcastify.com/scripts/playlists/1/12305/-5857889408.m3u'%5D%5D,
    'KEY_KP6' : ['WAMC',False,['mplayer','–quiet','http://pubint.ic.llnwd.net/stream/pubint_wamc'%5D%5D,
    'KEY_KP1' : ['60s',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/all60sallthetime-keepfreemusiccom.m3u'%5D%5D,
    'KEY_KP2' : ['50-70s',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/golden-50-70s-hits.m3u'%5D%5D,
    'KEY_KP3' : ['Soft Rock',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/softrockradio.m3u'%5D%5D,
    'KEY_KP0' : ['Zen',True,['mplayer','–quiet','-playlist','http://listen.radionomy.com/zen-for-you.m3u'%5D%5D,
    'KEY_KPDOT' : ['Ambient',False,['mplayer','–quiet','http://185.32.125.42:7331/maschinengeist.org.mp3'%5D%5D
    }
    Controls = {'KEY_KPSLASH' : '//////',
    'KEY_KPASTERISK' : '******',
    'KEY_KPENTER' : ' ',
    'KEY_KPMINUS' : '<',
    'KEY_KPPLUS' : '>',
    'KEY_VOLUMEUP' : '*',
    'KEY_VOLUMEDOWN' : '/'
    }
    MuteStrings = ["TargetSpot","[Unknown]","Advert:","+++","—","SRR","Srr","ZEN FOR","Intro of","Jingle – ","*bumper*"]
    Muted = False # keep track of muted state
    MixerChannel = 'PCM' # which amixer thing to use
    logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s',filename='/tmp/Streamer.log',level=logging.INFO)
    logger = logging.getLogger()
    # Set up defaults based on where we are
    CurrentKC = 'KEY_KP7'
    MuteDelay = 8.5 # delay before non-music track; varies with buffering
    UnMuteDelay = 7.5 # delay after non-music track
    MixerVol = '15' # mixer gain
    Location = vars(args)['Loc'].upper()
    print 'Player location: ',Location
    logging.info('Player setup for: ' + Location)
    if Location == 'BR1':
    CurrentKC = 'KEY_KPDOT'
    MixerVol = '10'
    elif Location == 'BR2':
    MuteDelay = 6.0
    UnMuteDelay = 8.0
    MixerVol = '5'
    # set up event inputs and polling objects
    # This requires some udev magic to create the symlinks
    k = InputDevice('/dev/input/keypad')
    k.grab()
    kp = select.poll()
    kp.register(k.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    # if volume control knob exists, then set up its events
    VolumeDevice = '/dev/input/volume'
    vp = select.poll()
    if os.path.exists(VolumeDevice):
    v = InputDevice(VolumeDevice)
    v.grab()
    vp.register(v.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    # set up file for output tracing
    lw = open('/tmp/mp.log','w') # mplayer piped output
    # set the mixer output low enough that the initial stream won't wake the dead
    subp.call(['amixer','sset',MixerChannel,MixerVol])
    # Start the player with the default stream, set up for polling
    print 'Starting mplayer on',Media[CurrentKC][0],' -> ',Media[CurrentKC][-1][-1]
    logging.info('Starting mplayer on %s -> %s',Media[CurrentKC][0],Media[CurrentKC][-1][-1])
    p = subp.Popen(Media[CurrentKC][-1],
    stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
    pp = select.poll() # this may be valid for other invocations, but is not pretty
    pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    print ' … running'
    #——————–
    #— Play the streams
    while True:
    # pluck next line from mplayer and decode it
    if [] != pp.poll(10):
    text = p.stdout.readline()
    if 'ICY Info: ' in text: # these lines may contain track names
    lw.write(text)
    lw.flush()
    trkinfo = text.split(';') # also splits at semicolon embedded in track name
    # logging.info('Raw split line: %s', trkinfo)
    for ln in trkinfo:
    if 'StreamTitle' in ln: # this part probably contains the track name
    NeedMute = False # assume a listenable track
    trkhit = re.search(r"StreamTitle='(.*)'",ln) # extract title if possible
    if trkhit: # regex returned valid result?
    TrackName = trkhit.group(1) # get string between two quotes
    else:
    print ' … regex failed for line: ', ln
    logging.info('Regex failed for line: [' + ln + ']')
    TrackName = "Invalid StreamTitle format"
    print 'Track name: [', TrackName, ']'
    logging.info('Track name: [%s]', TrackName)
    if Media[CurrentKC][1] and ( (len(TrackName) == 0) or any(m in TrackName for m in MuteStrings) ) :
    NeedMute = True
    if NeedMute:
    print ' … muting:',
    if Media[CurrentKC][1] and not Muted:
    time.sleep(MuteDelay) # brute-force assumption about buffer leadtime
    subp.call(['amixer','-q','sset',MixerChannel,'mute'])
    Muted = True
    print 'done'
    logging.info('Track muted')
    else:
    print ' … unmuting:',
    if Muted:
    if Media[CurrentKC][1]:
    time.sleep(UnMuteDelay) # another brute-force timing assumption
    Muted = False
    subp.call(['amixer','-q','sset',MixerChannel,'unmute'])
    print 'done'
    logging.info('Track unmuted')
    elif 'Exiting.' in text: # mplayer just imploded
    lw.write(text)
    lw.flush()
    print 'Got EOF / stream cutoff: []',text,']'
    logging.info('EOF or stream cutoff')
    print ' … killing dead mplayer'
    pp.unregister(p.stdout.fileno())
    p.terminate() # p.kill()
    p.wait()
    # print ' … flushing pipes'
    # lw.truncate(0)
    print ' … discarding keys'
    while [] != kp.poll(0):
    kev = k.read
    time.sleep(10)
    print ' … restarting mplayer: ',Media[CurrentKC][0]
    logging.info('Restarting mplayer')
    p = subp.Popen(Media[CurrentKC][-1],
    stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
    pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    print ' … running'
    logging.info(' … running')
    # accept pending events from volume control knob
    if [] != vp.poll(10):
    vev = v.read()
    lw.write('Volume')
    lw.flush()
    for e in vev:
    if e.type == ecodes.EV_KEY:
    kc = KeyEvent(e).keycode
    # print 'Volume kc: ',kc
    if kc in Controls:
    print 'Vol Control: ',kc
    try:
    p.stdin.write(Controls[kc])
    except Exception as e:
    print "Can't send control: ",e
    print ' … restarting player: ',Media[CurrentKC][0]
    logging.info('Error sending volume, restarting player: ' + str(e))
    try:
    pp.unregister(p.stdout.fileno())
    except Exception as e:
    print 'Trouble unregistering stdout: ',e
    logging.info('Cannot unregister stdout: ' + str(e))
    time.sleep(2)
    p = subp.Popen(Media[CurrentKC][-1],
    stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
    pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    print ' … running'
    logging.info(' … running')
    # accept pending events from keypad
    if [] != kp.poll(10):
    kev = k.read()
    lw.write("Keypad")
    lw.flush()
    for e in kev:
    if e.type == ecodes.EV_KEY:
    kc = KeyEvent(e).keycode
    if kc == 'KEY_NUMLOCK': # discard these, as we don't care
    continue
    # print 'Got: ',kc
    if (kc == 'KEY_BACKSPACE') and (KeyEvent(e).keystate == KeyEvent.key_hold):
    if True:
    print 'Backspace = shutdown!'
    p.kill()
    logging.shutdown()
    q = subp.call(['sudo','shutdown','-P','now'])
    q.wait()
    time.sleep(5)
    print "Oddly, we did not die…"
    else:
    print 'BS = bail from main!'
    logging.shutdown()
    sys.exit(0)
    break
    if KeyEvent(e).keystate != KeyEvent.key_down: # discard key up & other rubbish
    continue
    if kc in Controls:
    print 'Control:', kc
    logging.info('Control: ' + kc)
    try:
    p.stdin.write(Controls[kc])
    except Exception as e:
    print "Can't send control: ",e
    print ' … restarting player: ',Media[CurrentKC][0]
    logging.info('Error sending controls, restarting player: ' + str(e))
    try:
    pp.unregister(p.stdout.fileno())
    except Exception as e:
    print 'Trouble unregistering stdout: ',e
    logging.info('Cannot unregister stdout: ' + str(e))
    p.terminate() # p.kill()
    p.wait()
    time.sleep(2)
    p = subp.Popen(Media[CurrentKC][-1],
    stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
    pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    print ' … running'
    logging.info(' … running')
    if kc in Media:
    print 'Switching stream to ',Media[kc][0],' -> ',Media[kc][-1][-1]
    logging.info('Switching stream: ' + Media[kc][0] + ' -> ' + Media[kc][-1][-1])
    CurrentKC = kc
    print ' … halting player'
    try:
    pp.unregister(p.stdout.fileno())
    except Exception as e:
    print 'Trouble unregistering stdout: ',e
    logging.info('Cannot unregister stdout: ' + str(e))
    try:
    p.communicate(input='q')
    except Exception as e:
    print 'Perhaps mplayer already died? ',e
    logging.info('Already died? ' + str(e))
    try:
    p.terminate() # p.kill()
    p.wait()
    except Exception as e:
    print 'Trouble with terminate or wait: ',e
    logging.info('Trouble with terminate or wait: ' + str(e))
    time.sleep(2)
    print ' … restarting player: ',Media[CurrentKC][0]
    p = subp.Popen(Media[CurrentKC][-1],
    stdin=subp.PIPE,stdout=subp.PIPE,stderr=subp.STDOUT)
    pp.register(p.stdout.fileno(),select.POLLIN + select.POLLPRI + select.POLLERR)
    print ' … running'
    logging.info(' … running')
    print 'Out of loop!'
    logging.shutdown()
    view raw Streamer.py hosted with ❤ by GitHub