Your Sonos can speak to you (TTS), and it’s easy…

I’ve bought a Sonos wireless speaker, and started investigating it…

Discovered a great Python package that can control Sonos. There are libraries for other languages, so the ideas here would be relevant even if you’re not a Pythonist.

This post will develop the required code gradually.

At the end, I will paste the complete code.

First, some ‘games’:

import soco
import time

ip_addr = '192.168.1.108'

## Not working for me. Strange - it used to work before...
# for zone in soco.discover(timeout=5):
#     print zone.player_name
#     print zone

my_zone = soco.SoCo(ip_addr)

## Print interesting information
print my_zone.player_name
print my_zone.status_light
print my_zone.volume
print my_zone.get_speaker_info()
print my_zone.get_current_track_info()

Wow, it actually works 🙂

Strangely, discovery doesn’t work for me, but since I know the ip_addr of my Sonos, I hard-coded it.

 

Text To Speach

‘VoiceRSS’ is a nice web service that freely translates text strings to speech, in several languages. Play with it’s demo a little bit. Register (for free), and get an API Key.

You can open in your browser something like http://api.voicerss.org/?key=9..a&hl=en-us&src=%22Hello%20world%22 (just replace the ‘9..a’ in the middle with your API key) and enjoy it…

Now, in order to send an external URL to the Sonos, there is a play_uri function. The tricky part is that the URL shouldn’t be with http! I’ve Found a post at openHAB forum that tells to use x-rincon-mp3radio instead.

(If someone can point me to some documentation that explains this, it’d be great…)

So, here is our Sonos TTS function:

def tts(message):
    key = "9..a"                # replace with your real key
    lang = "en-us"              # or whatever language
    freq = "44khz_8bit_mono"    # Sonos cannot play the default, this one is working...
    my_zone.play_uri("x-rincon-mp3radio://api.voicerss.org/?key=%s&hl=%s&f=%s&src='%s'" %
                            (key, lang, freq, message),
                            title="Computer speaking")   # You'll see this title in the Sonos controller



Amazing, ha?

Well, our job is not finished here. We still have 2 problems:

  • The text is being played over and over
  • Sonos doesn’t return to the track that was played before we told it to speak

 

Every good thing must come to end…

In order to solve the first problem, we need to calculate the duration of the message, and send a stop command to Sonos.

First, I tried using something like this code, based on mutagen package:

file_path = r"C:\Users\zvika\Downloads\a.mp3"
from mutagen.mp3 import MP3
audio = MP3(file_path)
print audio.info.length

But it needs the file to be downloaded first. I was to lazy to do that, so I resorted to this:

def calc_duration(message):
    # based on https://community.smartthings.com/t/better-an-reloaded-fix-for-tts-texttospeech/22599/22
    # but either it has some mistake - or I misunderstood something.
    # anyway, I've modified it...
    return min(2, len(message) / 3) + 6

6 is the latency before Sonos start playing.
/3   is a rough estimation of the proportion between text length in letters and text length in seconds.
2 is the minimal length of the message itself, in case that the dividing gives a too short length.
(Strangely, they used max instead of min)

And here’s the code that uses this:

duration = calc_duration(message)
print "Sending TTS."
tts(message)
print "Going to sleep for %d seconds" % duration
time.sleep(duration)
my_zone.stop()
print "Stopped TTS"

 

Complete code

Here’s the complete code, which also addresses our second problem – saving Sonos state, and returning to it:

# documentation: http://docs.python-soco.com/en/latest/api/soco.core.html
import soco
import time

ip_addr = '192.168.1.108'

## Not working for me. Strange - it used to work before...
# for zone in soco.discover(timeout=5):
#     print zone.player_name
#     print zone

my_zone = soco.SoCo(ip_addr)

## Print interesting information
# print my_zone.player_name
# print my_zone.status_light
# print my_zone.volume
# print my_zone.get_speaker_info()
print my_zone.get_current_track_info()


## Nice library that calculated MP3 length
## Considered using it for 'duration', but regretted...
# file_path = r"C:\Users\zharamax\Downloads\a.mp3"
# from mutagen.mp3 import MP3
# audio = MP3(file_path)
# print audio.info.length

def calc_duration(message):
    # based on https://community.smartthings.com/t/better-an-reloaded-fix-for-tts-texttospeech/22599/22
    # but either it has some mistake - or I misunderstood something.
    # anyway, I've modified it...
    return min(2, len(message) / 3) + 6

def tts(message):
    key = "9..a"      # REPLACE THIS!
    lang = "en-us"
    freq = "44khz_8bit_mono"
    my_zone.play_uri("x-rincon-mp3radio://api.voicerss.org/?key=%s&hl=%s&f=%s&src='%s'" %
                            (key, lang, freq, message),
                            title="Computer speaking")

def speak(message):
    # save player status, in order to return it at the end
    # afte I coded this, I've discovered 'snapshots' - http://docs.python-soco.com/en/latest/api/soco.snapshot.html
    # however, they recommend not to use it (??)
    current_transport_info =  my_zone.get_current_transport_info()[u'current_transport_state']
    current_position = my_zone.get_current_track_info()[u'position']
    current_uri = my_zone.get_current_track_info()[u'uri']

    # default
    player_source = "QUEUE"
    if my_zone.is_playing_radio:
        player_source = "RADIO"
    elif my_zone.is_playing_line_in:
        player_source = "LINE_IN"
    elif my_zone.is_playing_tv:
        player_source = "TV"

    print "Currently %s, %s" % (current_transport_info, player_source)
    duration = calc_duration(message)

    print "Sending TTS."
    tts(message)
    print "Going to sleep for %d seconds" % duration
    time.sleep(duration)
    my_zone.stop()
    print "Stopped TTS"

    if player_source == "QUEUE":
        if current_transport_info in ["PLAYING", "PAUSED_PLAYBACK"]:
            #TODO: verify that's '0' is always true... maybe we should use  u'playlist_position'+1 ?
            my_zone.play_from_queue(0)

            #TODO: go back few seconds
            my_zone.seek(current_position)

            pause = (current_transport_info == "PAUSED_PLAYBACK")
    else:
        # either RADIO, LINE_IN or TV
        my_zone.play_uri(current_uri)

        #TODO: return also nice title
        # I don't have LINE_IN and TV, hope this covers them as well...

        pause = (current_transport_info == "STOPPED")


    if pause:
        my_zone.pause()
        print "Returned to %s, paused" % player_source
    else:
        print "Playing from %s again" % player_source



speak("Good night everyone")


As you see in the TODO comments, the work is not complete yet, but we’re almost there…

 

Leave a comment

Filed under Specman

Linux Virtualization – Without ROOT privilages

Hi.

There are many virtualization solutions available for Linux, such as KVM, VMWare or VirtualBox (ordered by my personal preferences…)
However, all of them require ‘root’ privileges. 

Or maybe not?

Today I stumbled upon a nice solution – User Mode Linux
Sadly, in order to configure it, with networking, I had to collect information from several resources (*). So here’s what I’ve done:

  1. Download and bunzip kernel and filesystem  image. I chose CentOS6, 64 bit. You can see all options at http://uml.devloop.org.uk/index.html . Just beware to choose kernel that matches your OS.  (**)
  2. Let’s see that it’s working: (change the ‘mem’ if needed)  ./kernel64-3.18.12 ubda=CentOS6.x-AMD64-root_fs mem=1G
  3. Log in as ‘root’, no password. Play around. ‘poweroff’ when finished will return you to the host prompt.
  4. You probably noticed that there is no networking!   (try ifconfig, if you don’t believe me 🙂 ) We’ll use external tool – SLIRP – to aid us.  (There might be another option – VDE, but I haven’t tried it) Unfortunately, SLIRP packaging is a little bit messy…
  5. Download both SLIRP latest full version (1.0.16)   and Patch to version 1.0.17
    tar xzf slirp-1.0.*
    cd slirp-1.0.16/src
    patch -p1 < ../../fix17.patch
    ./configure
  6. The configuration has to be manually changed, so we stop before the obvious ‘make‘! Edit config.h, look for "FULL_BOLT". Add   #define FULL_BOLT
  7. make
    cp slirp ../../../
  8. Now we should add slirp tool to our command: ./kernel64-3.18.12 ubda=CentOS6.x-AMD64-root_fs mem=1G eth0=slirp,,slirp
  9. Now ifconfig shows a network device. If you’re lucky, the builtin slirp DHCP server will give you IP address. In my case it didn’t work, so I had to do:
    ip addr add 192.168.0.1/24 dev eth0
    route add default dev eth0
  10. Finally, there is no DNS server configured, so you should edit /etc/resolv.conf
  11. In my case I also had to configure proxy environment variables.

The only downside (so far) is that I have network connectivity from my guest outside, but not from the external word inside. It seems that VDE should solve it, however I don’t have all its dependencies, so I couldn’t validate that.

* Most of the information was collected from http://user-mode-linux.sourceforge.net/old/networking.html and from http://vincent.bernat.im/en/blog/2011-uml-network-lab.html

** The dhcp client (dhclient) failed with ‘Segmentation fault’. I assume that’s because of kernel/OS mismatch. It seems that CentOS need kernel 2.6.32; however when I tried this UML kernel version it just stuck.

That’s it, enjoy your new CentOS machine! Comments are welcomed.

Leave a comment

Filed under Linux

Wireless bridging for Virtual Machines (e.g. KVM)

Background

Recently I’ve started using KVM (= VMM = libvirt = virt-manager).

I’m used to VirtualBox, where there in an option to define the network to be bridged, i.e. the Virtual Guests appear to be in the same LAN as the Host.

Alas, the official documentation says:

Important Note: Unfortunately, wireless interfaces cannot be attached to a Linux host bridge, so if your connection to the external network is via a wireless interface (“wlanX”), you will not be able to use this mode of networking for your guests.

But, I believe that “In Linux, the impossible just takes a little more time”, and here begins our story.

(If you’re really impatient, jump down to “Putting It All Together” – but then don’t blame me if you don’t understand…)

Possible solutions

(The Quest Begins)

dedoimedo has a nice article about it.

His first solution – to use ‘brctl’ just doesn’t work for me. (That’s probably why the official document says it’s impossible).

His second solution (‘Alternative setup’) is to create a new Network Interface, and route it to the Wireless Interface.

Indeed, this is a good solution for some use cases, but it’s not good for me. This is routing, while I need bridging.

The main difference is that routing operates on Layer 3 (=IP) while bridging operates on Layer 2 (=MAC, Ethernet). That means that with routing we have 2 separate networks, that can speak with each other. But they are 2 different LANs, with different IP networks. While bridging makes them a unified LAN, with all IP addresses on the same subnet.

His third (and last) solution sounds much better (he calls it ‘very dirty hack) –create the Virtual Guests new subnet with IP addresses that belong to the Host’s original network. That sounds interesting, but suffers from a routing problem:

In order to understand the problem, let’s describe the following network:

“External”: Wireless Router – 192.168.1.1

“External”: Printer         – 192.168.1.121

“External”: Virtual Host    – 192.168.1.149

“Internal”: Virtual Guest   – 192.168.1.180

Now, suppose the printer (.121) wants to send an IP packet to the Guest (.180). The printer knows that it’s in the same LAN, so it has to find out what is it “real” Hardware Address (=Layer 2 = MAC Address). In order to find this out, it sends an ARP (=Address Resolution Protocol) broadcast packet, asking “Who has .180 IP Address, please tell me what’s your MAC address”. ARP dictates that only the owner of that IP Address should response, so Virtual Host ignores the packet. Furthermore, it wouldn’t route it to the Virtual Guest, because no one told him to route Broadcast packets – it only routes IP packets send specifically to .180.

Bottom line – members of the “external” network won’t be able to send packets to members of the “internal” network.

(Calling For Help)

At that point, I’ve posted a superuser.com question. MariusMatutiae offered a new solution, of Bodhi Zazen.

Bodhi Zazen, in turn, referred to a very interesting article describing using Proxy ARP.

Actually, I haven’t tried Bodhi Zazen’s solution, because it seemed to be too complicated for my needs. (installing new package, defining tap device, manually allocating IP address and defining routes…)

However, his link was very helpful, so thanks 🙂

The Man In The Middle

When configuring a machine with Proxy ARP it start answering ARP questions for other IPs!

And that can fill the gap of Dedoimedo’s last solution – we just need to tell our Virtual Host to answer ARP questions regarding the Internal Network of the Virtual Guests with its own MAC address.
Thus, when our printer (.121) tries to reach Guest (.180) it sends an ARP question; but now the Proxy ARP (.149) will say – “Hey, It’s me”, and the printer will send packets destined to Guest (.180) with the Proxy’s (.149) MAC address. When the Virtual Host gets the packet, it will see it has Internal Network IP, and it already knows to forward packets to it.

Putting It All Together

To summarize, we have 2 phases here:

  1. Defining a new interface, according to Dedoimedo’s last solution
  2. Creating a Proxy ARP

Defining the new interface

(Before we begin, it’s recommended to shut down the Guest machine)

You can refer to dedoimedo here, but I’ve also put some screenshots:

First, create a new Network using Virt Manager’s GUI.
The Name doesn’t matter; all the other details are critical.
They’ll probably differ according to your network configuration – but the critical point here is that the new internal network is a subnet of your old external network, without interfering with it. 


After this phase, it’s a good practice to verify that we didn’t ruin anything with the host connectivity:

$ ping -c 4 www.google.com

And verify that you get reply. (if not – read a few more lines)

The routing table might also be interesting:

$ route

Kernel IP routing table

Destination Gateway Genmask Flags Metric Ref Use Iface

192.168.1.160 * 255.255.255.224 U 0 0 0 virbr1

192.168.1.0 * 255.255.255.0 U 2 0 0 wlan0

192.168.122.0 * 255.255.255.0 U 0 0 0 virbr0

default 192.168.1.1 0.0.0.0 UG

We can ignore the ‘virbr0’ line – it’s libvirt’s default NAT interface; not interesting for us.
All the other lines are important, and should like somewhat like here:
‘virbr1’ route for the internal network
‘wlan0’ route for the external network
‘default’ route for all others – The Big Internet

Pay attention that if ‘virbr1’ and ‘wlan0’ shouldn’t have the same Destination. If they do, that means that you didn’t define the virtual network correctly, and it might ruin your external connectivity.
If that’s the case, remove the new network, and re-define it with different parameters.

If everything is OK, we can connect remove the old Network device of our Machine, and create a new one using our new Interface.


And The Interesting Part – the PROXY ARP!

How do we define a Proxy ARP?

Well, the first article I read about it suggested this command:

/sbin/arp -i eth1 -Ds ${NETWORK} eth1 netmask ${NETMASK} pub

Which I translated to this:

arp -i wlan0 -Ds 192.168.1.180 wlan0 pub

The ‘netmask’ part is omitted because the Arp Man page says:

NOTE: As of kernel 2.2.0 it is no longer possible to set an ARP entry for an entire subnet. Linux

instead does automagic proxy arp when a route exists and it is forwarding. See arp(7) for details.

I didn’t understand at that point how the “automagic proxy arp” should work, so I ignored it temporarily.

And…

It works!

Hey there, it’s not perfect yet!

I know  🙂

There are 3 minor disadvantages here (that I know of; I’d be glad to hear more)

  1. The command I wrote is not for an entire subnet, it should be issued for each host in the internal network
  2. According to the Arp Man page, this command is being deprecated
  3. (I’m not sure about it, as I haven’t tried it yet) I assume that the ARP table wakes up clean every boot, so our proxy isn’t permanent, and should be added to some startup script

The future is here…

After I finished all of these, I found that Linux Advanced Routing & Traffic Control HOWTO has also an interesting article regarding Proxy ARP (apparently more modern than TLDP’s August 2000 one)

That probably explains the un-understood “Automagic” part mentioned in the Man page:

With Linux 2.4/2.5 (and possibly 2.2), this possibility has been withdrawn and has been replaced by a flag in the /proc directory, called ‘proxy_arp’. The procedure for building a pseudo-bridge is then:

  1. Assign an IP address to both interfaces, the ‘left’ and the ‘right’ one
  2. Create routes so your machine knows which hosts reside on the left, and which on the right
  3. Turn on proxy-ARP on both interfaces, echo 1 > /proc/sys/net/ipv4/conf/ethL/proxy_arp, echo 1 > /proc/sys/net/ipv4/conf/ethR/proxy_arp, where L and R stand for the numbers of your interfaces on the left and on the right side

Sound easy, but I can’t check it now, so hold on for updates.

Leave a comment

Filed under Linux

hdl_what?() – Tale Of Verilog Array

Suppose we have an old eVC, that covers some RTL aspect.
As life go on, the RTL now has multiple instances, so we need to have an array of eVCs.

We just have to update the simple ports connection:
keep nice_evc.nice_port.hdl_path() == appendf("nice_rtl.nice_array[%d]", agent_num);

So simple…

Well, working with VCS simulator, this simple syntax just works.

However, lately I’m trying to migrate my environment unto Cadence IES.

(There were several issues in the process, and Cadence support were very helpful.)

One interesting issue was with my “nice array”.
It appears that IES just didn’t like this declaration.
Cadence suggested switching the simple_port to indexed port.
This could have been a solution – but it required changing the legacy eVC…

After some investigations, it appears that the solution is using both hdl_path() and hdl_expression(), like this:
keep nice_evc.nice_port.hdl_path() == "nice_rtl.nice_array";
keep nice_evc.nice_port.hdl_expression() == appendf("%s, nice_rtl.nice_array[%d]", full_hdl_path(), agent_num);

Pay attention that hdl_expression() isn’t aware of hdl_path() hierarchy tree. I solved this by prepending full_hdl_path().

To be honest, I still don’t understand the problem, but at least we have a solution 🙂

Leave a comment

Filed under Specman

SSH through SOCKS proxy

Hi.

I’ve encountered the need to SSH from my machine to the Internet.
Unfortunately, it had to be through a SOCKS proxy.

Googling gave me a lot of tips how to -create- a SOCKS proxy with SSH, but it was much harder to find information how to -use- a SOCKS proxy with SSH.

Summing it all up:

compile this:
http://savannah.gnu.org/maintenance/connect.c

put it in somewhere in your PATH,

and try something like this:
ssh -v -o ProxyCommand="connect -S my.socks.proxy.com %h %p" git@github.com

(pay attention to NOT use socks://my.socks… format)

if it works, you can make this configuration permanent:
echo .*: >> ~/.ssh/config
echo ProxyCommand="connect -S my.socks.proxy.com %h %p" >> ~/.ssh/config

And now just:
ssh git@github.com

Leave a comment

Filed under Linux

Let VIM locate the file for you…

A very nice feature of VIM is the ‘gf’ command (go to file), which will open the file under cursor.

However, if there is a line like this:

 import my_file

“my_file.e” won’t be opened, because vim looks for “my_file”.

This can be easily solved, by adding this to your .vimrc

:set sua=.e

And to the more interesting part…

Q: If “my_file.e” isn’t located in the current directory, how will VIM find it?

A: The same way Specman does. With ‘specman_path’!

Adding this snippet to your .vimrc will do the trick 🙂

let &path = system(“echo $SPECMAN_PATH | sed ‘s/:/,/g'”)
if strlen(&path) == 0
     let &path = “.”
endif

2 Comments

Filed under Specman

VIM – Specman indentation

For a long time I was using VIM’s Smartindent feature while typing, and was quite satisfied.

However, last week I’ve tried to read an annoying .e file with zigzag lines. Well, I thought, I’m using VI – the Super Editor. It must have a re-indent feature, doesn’t it?

Quick Googling gave me “gg=G” keystroke. It indeed re-indented the whole file, but it made some strange decisions…

It appears that every VIM “filetype” (i.e., supported language) should have supporting indentation file, and Specman doesn’t have such!

Well, now it has 🙂

specman.vim

Just put it under:

~/.vim/indent/

And reload VI.

Of course, feedback is welcome 🙂

5 Comments

Filed under Specman