Category Archives: Specman

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…

 

Advertisements

Leave a comment

Filed under Specman

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

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