Alexa Announcements

Posted on
Wed Nov 06, 2019 9:50 pm
whmoorejr offline
User avatar
Posts: 762
Joined: Jan 15, 2013
Location: Houston, TX

Re: Alexa Announcements

autolog wrote:
I have a modified version of the send-alexa-command.js file

For what you are doing... sounds like you are populating a variable then triggering off changes to that variable to run the modified.sh which points to modified.js. (how am I doing so far?). But how are you getting that variable into modified.js?

Bill
My Plugin: My People

Posted on
Thu Nov 07, 2019 9:16 am
autolog offline
Posts: 3988
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Alexa Announcements

whmoorejr wrote:
autolog wrote:
I have a modified version of the send-alexa-command.js file

For what you are doing... sounds like you are populating a variable then triggering off changes to that variable to run the modified.sh which points to modified.js. (how am I doing so far?). But how are you getting that variable into modified.js?

Yes, you are spot on. :D

Here is the Indigo script (external) which is triggered on a variable change. The trigger has a conditional that only fires the action for the external script (see below) if the variable isn't blank.
Code: Select all
import indigo
import subprocess
import time

debug = False
announcement = indigo.variables[12345678].value  # Variable which contains text to speak
echo_name = '<YOUR ECHO NAME>'

indigo.server.log (u'NODEJS [Announcement] = {}'.format(announcement))
command = u'node ~/Desktop/Alexa/send-alexa-command.js "ssml" "{}" "{}"'.format(announcement, echo_name)
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

if debug:   
    try:
        # Filter stdout
        for line in iter(p.stdout.readline, ''):
            sys.stdout.flush()
            # Print status
            indigo.server.log (">>> " + line.rstrip())
            sys.stdout.flush()
    except:
        sys.stdout.flush()

# Wait until process terminates (without using p.wait())
loop_count = 0
while p.poll() is None:
    # Process hasn't exited yet, let's wait some
    time.sleep(0.5)
    loop_count += 1
indigo.server.log (u'NODEJS Poll Loop Count = {}, Return Code = {}'.format(loop_count, p.returncode))
Substitute your variable id and change the echo name in lines 6 & 7. If you leave the echo name blank, it will send to all your echos.

Here is the revised .js file which I have in an Alexa desktop folder:
Code: Select all
const fs = require("fs");
let Alexa = require('alexa-remote2');
let alexa = new Alexa();

/***************************************************************/
// Default speech settings for creating SSML
// Amazon's SSML Reference: https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html
var defaultVoice = "Brian";
var defaultLang = "en-UK";

/***************************************************************/
// see: https://www.gehrig.info/alexa/Alexa.html
// cookie starts with x-amzn-dat and ends with =" csrf=12345780
let cookie = {};

var cookieFile = "/Users/<USERNAME>/Desktop/Alexa/my-cookie.json";
var deviceFile = "/Users/<USERNAME>/Desktop/Alexa/my-devices.json";

// Load last cookie from file
if (fs.existsSync(cookieFile)) cookie = JSON.parse(fs.readFileSync(cookieFile));


alexa.init({
        cookie: cookie,  // cookie if already known, else can be generated using email/password
        email: '...',    // optional, amazon email for login to get new cookie
        password: '...', // optional, amazon password for login to get new cookie
        proxyOnly: true,
        proxyOwnIp: 'localhost',
        proxyPort: 3001,
        proxyLogLevel: 'info',
        bluetooth: true,
        logger: console.log, // optional
        alexaServiceHost: 'alexa.amazon.co.uk', // optional, e.g. "pitangui.amazon.com" for amazon.com, default is "layla.amazon.de"
        //userAgent: '...', // optional, override used user-Agent for all Requests and Cookie determination
        acceptLanguage: 'en-UK', // optional, override Accept-Language-Header for cookie determination
        amazonPage: 'amazon.co.uk', // optional, override Amazon-Login-Page for cookie determination and referer for requests
        useWsMqtt: true, // optional, true to use the Websocket/MQTT direct push connection
        cookieRefreshInterval: 7*24*60*1000 // optional, cookie refresh intervall, set to 0 to disable refresh
    },
    function (err) {
        if (err) {
            console.log(err);
            return;
        }

        argCommand = ''
        argDetail = ''
        argDevice = ''

        console.log('>>>>START<<<<<')
        for (let j = 0; j < process.argv.length; j++) {
            if (j == 2) {
                argCommand = process.argv[j]
            }

            if (j == 3) {
                argDetail = process.argv[j]
            }

            if (j == 4) {
                argDevice = process.argv[j]
            }

            console.log(j + ' -> ' + (process.argv[j]));
        }
        console.log('>>>>END<<<<<')



        // Save cookie to file so it can be used next time, as long as it is still valid.
        saveCookie();


        if (argCommand !== '') {
            if (argDetail !== '') {
                if (argDevice !== '') {
                    sendCommand(argCommand, argDetail, [argDevice]);
                } else {
                    sendCommand(argCommand, argDetail)
                }
            }
        }

        // Change the sendCommand call below to send a command to one or more of your Alexa devices.
        //sendCommand("ssml", "This is an english test from Indigo.");

        // Here are a few other example calls.
         
       
        //sendCommand("speak", "This is a test to one device only.", ["Study Echo Dot"]);

        //sendCommand("singasong", "", "SERIALNUMBER");

        //sendCommand("volume", 100);

        //sendCommand("weather", "", "Family Room");

        /*
       
        //Send custom SSML string
        let customSSML = "<speak>" +
            "<lang xml:lang='en-UK'>" +
            "<say-as interpret-as='interjection'>hiya. guess what.</say-as>" +
            "I wanna tell you a secret" +
            "<break time='500ms'/>" +
            "<amazon:effect name='whispered'>I can see your browser history.</amazon:effect>" +
            "<break time='1s'/>" +
            "<say-as interpret-as='interjection'>schwing. oh my.</say-as>" +
            "<break time='1500ms'/>" +
            "<say-as interpret-as='interjection'>ha ha. gotcha.</say-as>." +
            '</lang>' +
            '</speak>';
        sendCommand("ssml", customSSML, ["Study Echo Dot"]);
        /* */

        // Pause so the command has a chance to be sent. Then exit.
        setTimeout(function(){
            process.exit();
        },5000);
    }
);


function saveCookie() {
    // Save cookie (and device data for reference).
    console.log("Saving cookie to file: " + cookieFile);
    try {
        // Save device data
        fs.writeFileSync(deviceFile, JSON.stringify(alexa.serialNumbers, null,4));

        // Save cookie
        if (alexa.cookieData) {
            if (alexa.cookieData.csrf) {
                fs.writeFileSync(cookieFile, JSON.stringify(alexa.cookieData,null,4));
            } else {
                throw new Error("alexa.cookieData is invalid or missing 'csrf' property.");
            }
        } else {
            throw new Error("alexa.cookieData is null or empty.");
        }
    } catch (e) {
        console.log("Error saving cookie and/or list of devices: " + e.message);
    }
}

function sendCommand(command, value, deviceIDs) {
    try {
        // See createSequenceNode function in alexa-remote.js for a list of commands.

        // If no devices are specified, the command will be sent to ALL devices.
        if (!deviceIDs) deviceIDs = getAllDeviceSerialNumbers();
        if (!Array.isArray(deviceIDs)) deviceIDs = [deviceIDs];

        // Build list of commands
        // The 'ssml' and 'announcement' commands support sending to multiple devices at once. The other commands do not.
        let allCommands = [];
        if (['ssml','announcement'].includes(command)) {
            let thisCommand = {};
            thisCommand.command = command;
            thisCommand.value = (command == 'ssml') ? stringToSSML(value) : value;
            allCommands.push(thisCommand);
        } else {
            deviceIDs.forEach(function(name) {
                let thisCommand = {};
                thisCommand.command = command;
                thisCommand.value = value;
                thisCommand.device = name;
                allCommands.push(thisCommand);
            });
        }

        console.log("Sending command(s):\n" + JSON.stringify({
            devices: deviceIDs,
            commands: allCommands,
        },null,4));

        alexa.sendMultiSequenceCommand(deviceIDs, allCommands);

        return;

    } catch (e) {
        console.log("Error sending command: \n" + JSON.stringify(e, null, 4));
    }
}

function stringToSSML(input) {
    if (input.startsWith('<speak>')) {
        // Input is already in SSML format.
        return input;
    } else {
        // Encapsulate string in XML using default voice/lang values, if provided.
        let output;
        output = input;
        if (defaultLang) output = '<lang xml:lang="' + defaultLang + '">' + output + '</lang>';
        if (defaultVoice) output = '<voice name="' + defaultVoice + '">' + output + '</voice>';
        output = '<speak>' + output + '</speak>';
        return output;
    }
}

function getAllDeviceSerialNumbers() {
    let serials = [];
    Object.values(alexa.serialNumbers).forEach(function(dev){
        serials.push(dev.serialNumber);
    });
    return serials;
}
Change lines 16 and 17 <USERNAME> to your Mac username.

The script is set-up for UK usage but you should be able to check the changed details with the original .js file.
The modified script checks for incoming arguments:
  1. SSML - fixed, non-user amendable
  2. announcement - passed through from the Indigo variable
  3. Echo name - optional, may be blank
. The script sends accordingly (see lines 74 thru 82.

Hope this helps. :)

Posted on
Thu Nov 07, 2019 10:40 am
whmoorejr offline
User avatar
Posts: 762
Joined: Jan 15, 2013
Location: Houston, TX

Re: Alexa Announcements

Thank you so much, I'm having so much fun! Your scripts are working great and I'm sending random audio quotes through my house now. (Nothing automated yet, just changing the variable and letting the trigger take over from there).


autolog wrote:
The script is set-up for UK usage but you should be able to check the changed details with the original .js file.


I left the default Brian & en-UK (lines 8 & 9).... well, I just like Brian.... he sounds like he knows what he's talking about and I wanted a completely different voice from the standard "Alexa".

For US usage.... I changed lines 33, 35, & 36 ( 'pitangui.amazon.com' , 'en-US' , 'amazon.com' )


Next I think I'm going to work on incorporating sounds https://developer.amazon.com/docs/custom-skills/ask-soundlibrary.html to categorize my announcements. (add a doorbell chime to the announcement "someone rang the doorbell", or a footstep sound when certain motion is detected.... <- don't have this part worked out quite yet.... but basically a tone type to preempt the announcement by category (weather, motion, music/media, etc.) and by importance/priority (a doorbell ring is more important than audio track change).

I was hoping for a southern accent (since I'm in Texas), but I guess I'll just throw in a few y'all , howdy , I reckon, and dog-gone lines into the mix.
https://youtu.be/q3j6708kzEY <-- Southern Alexa Parody

Bill
My Plugin: My People

Posted on
Thu Nov 07, 2019 11:21 am
autolog offline
Posts: 3988
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Alexa Announcements

I am pleased it works OK for you. :)

I am using it to announce track changes from my Roon plugin. I just happen to have Echos in each room that my Roon players (bluesound) reside and so on a track change for a specific player, Alexa just announces the track, artist and album on the Echo nearest the Roon player.. Can be quite useful. :wink:

Posted on
Thu Nov 07, 2019 12:56 pm
siclark offline
Posts: 1960
Joined: Jun 13, 2017
Location: UK

Re: Alexa Announcements

Nice I'm using the announcements and Sonos plugin for my announcing current track but I like the idea of using the Alexa as it simplifies my trigger as the Sonos track doesn't change with the announcement playing.



Sent from my iPhone using Tapatalk

Posted on
Fri Nov 08, 2019 4:23 am
autolog offline
Posts: 3988
Joined: Sep 10, 2013
Location: West Sussex, UK [GMT aka UTC]

Re: Alexa Announcements

I have noticed that the ampersand character, &, isn't processed correctly by the announcement process - not sure where the issue is. :?

The net result is that if any announcement contains that character, the announcement doesn't happen. This was quite a common occurrence when announcing music tracks. My work-around solution is to replace any occurrence of & with and.

My Roon plugin now handles this prior to updating the variables. :)

Posted on
Fri Nov 08, 2019 10:12 am
ckeyes888 offline
Posts: 2424
Joined: Nov 26, 2009
Location: Kalispell, MT

Re: Alexa Announcements

Love to get this working but I'm not able to get the .sh to execute.

Code: Select all
#!/bin/bash

import indigo
import subprocess
import time

debug = False
announcement = indigo.variables[1929982815].value  # Variable which contains text to speak
echo_name = '<Kitchen>'

indigo.server.log (u'NODEJS [Announcement] = {}'.format(announcement))
command = u'node ~/Desktop/Alexa/send-alexa-command.js "ssml" "{}" "{}"'.format(announcement, echo_name)
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

if debug:   
    try:
        # Filter stdout
        for line in iter(p.stdout.readline, ''):
            sys.stdout.flush()
            # Print status
            indigo.server.log (">>> " + line.rstrip())
            sys.stdout.flush()
    except:
        sys.stdout.flush()

# Wait until process terminates (without using p.wait())
loop_count = 0
while p.poll() is None:
    # Process hasn't exited yet, let's wait some
    time.sleep(0.5)
    loop_count += 1
indigo.server.log (u'NODEJS Poll Loop Count = {}, Return Code = {}'.format(loop_count, p.returncode))


Returns this error:
Action Collection Error Couldn't run script - make sure the script is marked executable and contains a valid shebang on the first line

I added the shebang but I'm guessing there's something else I need to do?

Thanks,

Carl

Posted on
Fri Nov 08, 2019 10:32 am
jay (support) offline
Site Admin
User avatar
Posts: 18212
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Alexa Announcements

You need to make the script executable:

Code: Select all
chmod +x /path/to/script.file

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Fri Nov 08, 2019 11:04 am
ckeyes888 offline
Posts: 2424
Joined: Nov 26, 2009
Location: Kalispell, MT

Re: Alexa Announcements

Thanks. Did that before posting.

Ran:
chmod +x /Users/TV/Desktop/Alexa/alexakitchen.sh

No feedback after running though.
Still same error.

Carl

Posted on
Fri Nov 08, 2019 12:20 pm
whmoorejr offline
User avatar
Posts: 762
Joined: Jan 15, 2013
Location: Houston, TX

Re: Alexa Announcements

ckeyes888 wrote:
Love to get this working but I'm not able to get the .sh to execute.



I did the same thing at first.... save that file as a .py file and then action execute script, choose file and select that .py file.

Bill
My Plugin: My People

Posted on
Fri Nov 08, 2019 12:36 pm
jay (support) offline
Site Admin
User avatar
Posts: 18212
Joined: Mar 19, 2008
Location: Austin, Texas

Re: Alexa Announcements

LOL - sorry, I didn't realize that was actually a Python script. You don't execute Indigo Python scripts from the Run Shell Script action - you run them from the Execute Script action. And they aren't bash shell scripts, they are Python scripts. They don't need a shebang, though if you put one you should use:

Code: Select all
#!/usr/bin/env python


and they don't need to be marked as executable.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Fri Nov 08, 2019 1:32 pm
ckeyes888 offline
Posts: 2424
Joined: Nov 26, 2009
Location: Kalispell, MT

Re: Alexa Announcements

Thanks. Still not working.

I get this in the log:

Script NODEJS [Announcement] = testing 1 2
Script NODEJS Poll Loop Count = 2, Return Code = 1

edit: Curious what voice is being used. Is Brian in this case an Alexa voice or system voice?

Appreciate any ideas.

Thanks,

Carl

Posted on
Fri Nov 08, 2019 2:46 pm
whmoorejr offline
User avatar
Posts: 762
Joined: Jan 15, 2013
Location: Houston, TX

Re: Alexa Announcements

ckeyes888 wrote:
Thanks. Still not working.

Carl


In short, Brian is one of the Alexa British Male voices.... other voice choices on Alexa:

from https://developer.amazon.com/docs/custom-skills/speech-synthesis-markup-language-ssml-reference.html
The following voices are supported for their respective languages:

English, American (en-US): Ivy, Joanna, Joey, Justin, Kendra, Kimberly, Matthew, Salli
English, Australian (en-AU): Nicole, Russell
English, British (en-GB): Amy, Brian, Emma
English, Indian (en-IN): Aditi, Raveena
German (de-DE): Hans, Marlene, Vicki
Spanish, Castilian (es-ES): Conchita, Enrique
Hindi (hi-IN): Aditi
Italian (it-IT): Carla, Giorgio
Japanese (ja-JP): Mizuki, Takumi
French (fr-FR): Celine, Lea, Mathieu

Tried (successfully) in my environment... this is my log:
Code: Select all
   Trigger                         send-alexa-variable
   Action Group                    send-alexa-variable
   Script                          NODEJS [Announcement] = Trying this for Carl
   Script                          NODEJS Poll Loop Count = 15, Return Code = 0

I guess Return Code -= 1 is bad, 0 is good.? That's the extent of my knowledge on how that script actually works... 0 good, 1 bad.

Bill
My Plugin: My People

Posted on
Fri Nov 08, 2019 3:06 pm
ckeyes888 offline
Posts: 2424
Joined: Nov 26, 2009
Location: Kalispell, MT

Re: Alexa Announcements

Thanks. Appreciate the info.

I did the test code from the original post, "This is a test." and that works fine.

This is all that shows in the log.
Code: Select all
Trigger                         Announce Kitchen Alexa
   Action Group                    Alexa Announce Kitchen
   Script                          NODEJS [Announcement] = Testing 1 2


Odd it doesn't throw an error when running.
Hoping someone might have an idea what to try next.

Carl

Posted on
Fri Nov 08, 2019 3:26 pm
whmoorejr offline
User avatar
Posts: 762
Joined: Jan 15, 2013
Location: Houston, TX

Re: Alexa Announcements

Other differences....

My code, alexa-variable.py has no shebang stuff at the top, begins at "import indigo"

Line 7, I think echo_name is supposed to be in " " not ' '. My line 7 is
Code: Select all
echo_name = ""  # Leave echo name blank to send to all devices

Line 10 I changed to point to my new js code saved as Alexa-variable.js

Line 32 added
Code: Select all
indigo.variable.updateValue(1862150451, "") #set announce variable back to null

So after it all runs, it sets the variable back to "no value"


On the java code, I made the following changes from autolog...

Line 16+17 : changed to my username-
Code: Select all
var cookieFile = "/Users/williammoore/Desktop/Alexa/my-cookie.json";
var deviceFile = "/Users/williammoore/Desktop/Alexa/my-devices.json";


Lines 33-36: changed for US use.
Code: Select all
 alexaServiceHost: 'pitangui.amazon.com', // optional, e.g. "pitangui.amazon.com" for amazon.com, default is "layla.amazon.de"
        //userAgent: '...', // optional, override used user-Agent for all Requests and Cookie determination
        acceptLanguage: 'en-US', // optional, override Accept-Language-Header for cookie determination
        amazonPage: 'amazon.com', // optional, override Amazon-Login-Page for cookie determination and referer for requests


I hope one of those gets you working.

Bill
My Plugin: My People

Who is online

Users browsing this forum: No registered users and 0 guests

cron