Alexa Announcements

Ask questions about integrating other services (internet and otherwise) and applications with Indigo.
User avatar
whmoorejr
Posts: 844
Joined: Tue Jan 15, 2013 11:23 am
Location: Houston, TX

Re: Alexa Announcements

Post by whmoorejr »

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
autolog
Posts: 4077
Joined: Tue Sep 10, 2013 3:07 am
Location: West Sussex, UK
Contact:

Re: Alexa Announcements

Post by autolog »

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. :)
User avatar
whmoorejr
Posts: 844
Joined: Tue Jan 15, 2013 11:23 am
Location: Houston, TX

Re: Alexa Announcements

Post by whmoorejr »

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/custo ... brary.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
autolog
Posts: 4077
Joined: Tue Sep 10, 2013 3:07 am
Location: West Sussex, UK
Contact:

Re: Alexa Announcements

Post by autolog »

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:
siclark
Posts: 2356
Joined: Tue Jun 13, 2017 5:08 am
Location: UK

Re: Alexa Announcements

Post by siclark »

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
autolog
Posts: 4077
Joined: Tue Sep 10, 2013 3:07 am
Location: West Sussex, UK
Contact:

Re: Alexa Announcements

Post by autolog »

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. :)
ckeyes888
Posts: 2438
Joined: Thu Nov 26, 2009 2:41 pm
Location: Kalispell, MT

Re: Alexa Announcements

Post by ckeyes888 »

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
User avatar
jay (support)
Site Admin
Posts: 19232
Joined: Wed Mar 19, 2008 11:52 am
Location: Austin, Texas
Contact:

Re: Alexa Announcements

Post by jay (support) »

You need to make the script executable:

Code: Select all

chmod +x /path/to/script.file
Jay (Indigo Support)
Twitter | Facebook | LinkedIn
ckeyes888
Posts: 2438
Joined: Thu Nov 26, 2009 2:41 pm
Location: Kalispell, MT

Re: Alexa Announcements

Post by ckeyes888 »

Thanks. Did that before posting.

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

No feedback after running though.
Still same error.

Carl
User avatar
whmoorejr
Posts: 844
Joined: Tue Jan 15, 2013 11:23 am
Location: Houston, TX

Re: Alexa Announcements

Post by whmoorejr »

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
User avatar
jay (support)
Site Admin
Posts: 19232
Joined: Wed Mar 19, 2008 11:52 am
Location: Austin, Texas
Contact:

Re: Alexa Announcements

Post by jay (support) »

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
ckeyes888
Posts: 2438
Joined: Thu Nov 26, 2009 2:41 pm
Location: Kalispell, MT

Re: Alexa Announcements

Post by ckeyes888 »

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
User avatar
whmoorejr
Posts: 844
Joined: Tue Jan 15, 2013 11:23 am
Location: Houston, TX

Re: Alexa Announcements

Post by whmoorejr »

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/custo ... rence.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
ckeyes888
Posts: 2438
Joined: Thu Nov 26, 2009 2:41 pm
Location: Kalispell, MT

Re: Alexa Announcements

Post by ckeyes888 »

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
User avatar
whmoorejr
Posts: 844
Joined: Tue Jan 15, 2013 11:23 am
Location: Houston, TX

Re: Alexa Announcements

Post by whmoorejr »

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
Post Reply

Return to “Integrating Services/Applications”