/*
* Alexa TTS Manager
*
* https://raw.githubusercontent.com/ogiewon/Hubitat/master/AlexaTTS/Apps/alexa-tts-manager.src/alexa-tts-manager.groovy
*
*
* Copyright 2018 Daniel Ogorchock - Special thanks to Chuck Schwer for his tips and prodding me
* to not let this idea fall through the cracks!
* Copyright 2018 Gabriele - Automatic cookie refresh with Apollon77/Alexa-Cookie
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Change History:
*
* Version Date Who What
* ------- ---- --- ----
* v0.1.0 2018-10-20 Dan Ogorchock Original Creation - with help from Chuck Schwer!
* v0.1.1 2018-10-21 Dan Ogorchock Trapped an error when invalid data returned from Amazon due to cookie issue
* v0.2.0 2018-10-21 Stephan Hackett Modified to include more Alexa Devices for selection
* v0.3.0 2018-10-22 Dan Ogorchock Added support for Canada and United Kingdom, and ability to rename the app
* v0.4.0 2018-11-18 Stephan Hackett Added support for Virtual Container
* v0.4.1 2018-11-18 Dan Ogorchock Optimized multi-country support code and added Notification support for errors
* v0.4.2 2018-11-27 Dan Ogorchock Improved error handling for notifications when cookie expires (via live logging and optoinally, via push notification)
* v0.4.3 2018-12-07 Dan Ogorchock Prevent sending empty string TTS messages to Amazon.
* v0.4.4 2018-12-10 Dan Ogorchock Detect and notify via logging and notification, message rate exceeded errors to avoid confusion with cookie expiration errors.
* v0.4.5 2018-12-14 Stephan Hackett Added ability to paste in the entire Raw Cookie. No manual editing required. Improved setup page flow.
* v0.4.6 2018-12-23 Dan Ogorchock Added support for Italy. Thank you @gabriele!
* v0.5.0 2019-01-02 Gabriele Added support for automatic cookie refresh with external NodeJS webserver
* v0.5.1 2019-02-12 Dan Ogorchock Corrected contentType to prevent errors in response parsing
* v0.5.2 2019-04-04 Thomas Howard Added get/set Volume Control (not working currently - Dan O 4/6/19)
* v0.5.3 2019-04-16 Gabriele Added app events to have some historic logging
* v0.5.4 2019-06-24 Dan Ogorchock Attempt to add Australia
* v0.5.5 2019-07-18 Dan Ogorchock Reduced Debug Logging
* v0.5.6 2020-01-02 Dan Ogorchock Add support for All Echo Device Broadcast
* v0.5.7 2020-01-02 Bob Butler Add an override switch that disables all voice messages when off
* v0.5.8 2020-01-07 Marco Felicio Added support for Brazil
* v0.5.9 2020-01-26 Dan Ogorchock Changed automatic cookie refresh time to 1am to avoid hub maintenance window
* v0.6.0 2020-10-25 lg kahn add mesg if cookie updated sucessfully, also add setvolume command called from each indiv. device.
* v0.6.1 2020-11-04 Dan Ogorchock Add support for Ecobee Thermostat with Alexa builtin. Thank you Greg Veres!
* v0.6.2 2020-11-04 Dan Ogorchock Added timeout parameter to all http calls
* v0.6.3 2020-11-05 Dan Ogorchock Minor tweak to pull request from @yototogblo to allow user defined ownerID. This is needed to use "All Echos"
* if there are multiple Echo devices on the account that have different owners.
* v0.6.4 2020-11-07 Dan Ogorchock Improved error notifications if option notification device defined.
*/
definition(
name: "Alexa TTS Manager",
namespace: "ogiewon",
author: "Dan Ogorchock",
description: "Manages your Alexa TTS Child Devices",
iconUrl: "",
iconX2Url: "")
preferences {
page(name: "pageOne")
page(name: "pageTwo")
}
def pageOne(){
dynamicPage(name: "pageOne", title: "Alexa Cookie and Country selections", nextPage: "pageTwo", uninstall: true) {
section("Please Enter your alexa.amazon.com 'cookie' file string here (end with a semicolon)") {
input("alexaCookie", "text", title: "Raw or edited Cookie", submitOnChange: true, required: true)
}
if(alexaCookie != null && alexaCookie.contains("Cookie: ")){
def finalForm
def preForm = alexaCookie.split("Cookie: ")
if(preForm.size() > 1) finalForm = preForm[1]?.replace("\"", "") + ";"
app.updateSetting("alexaCookie",[type:"text", value: finalForm])
}
section("Please enter settings for automatic cookie refresh with NodeJS") {
input("alexaRefreshURL", "text", title: "NodeJS service URL", required: false)
input("alexaRefreshUsername", "text", title: "NodeJS service Username (not Amazon one)", required: false)
input("alexaRefreshPassword", "password", title: "NodeJS service Password (not Amazon one)", required: false)
input("alexaRefreshOptions", "text", title: "Alexa cookie refresh options", required: false, submitOnChange: true)
input("alexaRefresh", "bool", title: "Force refresh now? (Procedure will require 5 minutes)", submitOnChange: true)
}
if(alexaRefreshOptions == null) {
unschedule()
}
else {
// Schedule automatic update
unschedule()
schedule("0 0 1 1/6 * ? *", refreshCookie) // Check for updates every 6 days at 1:00 AM
//Extract cookie from options if cookie is empty
if(alexaCookie == null){
app.updateSetting("alexaCookie",[type:"text", value: getCookieFromOptions(alexaRefreshOptions)])
}
}
if(alexaRefresh) {
refreshCookie()
app.updateSetting("alexaRefresh",[type:"bool", value: false])
}
section("Please choose your country") {
input "alexaCountry", "enum", multiple: false, required: true, options: getURLs().keySet().collect()
}
section("Alexa Owner ID (Optional)") {
input("ownerID", "text", title: "Optionally enter an Owner ID. This is useful for Amazon Household accounts where the Alexa devices have different owners", submitOnChange: true, required: false)
}
section("Notification Device") {
paragraph "Optionally assign a device for error notifications (like when the cookie is invalid or refresh fails)"
input "notificationDevice", "capability.notification", multiple: false, required: false
}
section("Override Switch") {
paragraph "Optionally assign a switch that will disable voice when turned off"
input "overrideSwitch", "capability.switch", multiple: false, required: false
}
section("App Name") {
label title: "Optionally assign a custom name for this app", required: false
}
}
}
def pageTwo(){
dynamicPage(name: "pageTwo", title: "Amazon Alexa Device Selection", install: true, uninstall: true) {
section("Please select devices to create Alexa TTS child devices for") {
input "alexaDevices", "enum", multiple: true, required: false, options: getDevices()
}
section("") {
paragraph "Warning!!\nChanging the option below will delete any previously created child devices!!\n"+
"Virtual Container driver v1.1.20181118 or higher must be installed on your hub!!"+
" [driver] "+
" [notes] "
input "alexaVC", "bool", title: "Add Alexa TTS child devices to a Virtual Container?"
}
}
}
def speakMessage(String message, String device) {
def errorMessage
if (overrideSwitch != null && overrideSwitch.currentSwitch == 'off') {
log.info "${overrideSwitch} is off, AlexaTTS will not speak message '${message}'"
return
}
log.debug "Sending '${message}' to '${device}'"
sendEvent(name:"speakMessage", value: message, descriptionText: "Sending message to '${device}'")
if (message == '' || message.length() == 0) {
log.warn "Message is empty. Skipping sending request to Amazon"
}
else {
atomicState.alexaJSON.devices.any {it->
if ((it.accountName == device) || (device == "All Echos")) {
//log.debug "${it.accountName}"
//log.debug "${it.deviceType}"
//log.debug "${it.serialNumber}"
//log.debug "${it.deviceOwnerCustomerId}"
try{
def SEQUENCECMD = "Alexa.Speak"
def DEVICETYPE = "${it.deviceType}"
def DEVICESERIALNUMBER = "${it.serialNumber}"
def MEDIAOWNERCUSTOMERID = "${it.deviceOwnerCustomerId}"
if (ownerID) { MEDIAOWNERCUSTOMERID = ownerID }
def LANGUAGE = getURLs()."${alexaCountry}".Language
def command = ""
if (device == "All Echos") {
//command = "{\"behaviorId\":\"PREVIEW\",\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"operationPayload\\\":{\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\",\\\"expireAfter\\\":\\\"PT5S\\\",\\\"content\\\":[{\\\"locale\\\":\\\"${LANGUAGE}\\\",\\\"display\\\":{\\\"title\\\":\\\"AlexaTTS\\\",\\\"body\\\":\\\"${message}\\\"},\\\"speak\\\":{\\\"type\\\":\\\"text\\\",\\\"value\\\":\\\"${message}\\\"}}],\\\"target\\\":{\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\"}},\\\"type\\\":\\\"AlexaAnnouncement\\\"}}\",\"status\":\"ENABLED\"}"
command = "{\"behaviorId\":\"PREVIEW\",\
\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\
\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\
\\\"operationPayload\\\":{\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\",\
\\\"expireAfter\\\":\\\"PT5S\\\",\
\\\"content\\\":[{\\\"locale\\\":\\\"${LANGUAGE}\\\",\
\\\"display\\\":{\\\"title\\\":\\\"AlexaTTS\\\",\
\\\"body\\\":\\\"${message}\\\"},\
\\\"speak\\\":{\\\"type\\\":\\\"text\\\",\
\\\"value\\\":\\\"${message}\\\"}}],\
\\\"target\\\":{\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\"}},\
\\\"type\\\":\\\"AlexaAnnouncement\\\"}}\",\
\"status\":\"ENABLED\"}"
}
else {
//command = "{\"behaviorId\":\"PREVIEW\",\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\\\"type\\\":\\\"${SEQUENCECMD}\\\",\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"${DEVICETYPE}\\\",\\\"deviceSerialNumber\\\":\\\"${DEVICESERIALNUMBER}\\\",\\\"locale\\\":\\\"${LANGUAGE}\\\",\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\"${TTS}}}}\",\"status\":\"ENABLED\"}"
command = "{\"behaviorId\":\"PREVIEW\",\
\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\
\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\
\\\"type\\\":\\\"${SEQUENCECMD}\\\",\
\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"${DEVICETYPE}\\\",\
\\\"deviceSerialNumber\\\":\\\"${DEVICESERIALNUMBER}\\\",\
\\\"locale\\\":\\\"${LANGUAGE}\\\",\
\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\",\
\\\"textToSpeak\\\":\\\"${message}\\\"}}}\",\
\"status\":\"ENABLED\"}"
}
def csrf = (alexaCookie =~ "csrf=(.*?);")[0][1]
def params = [uri: "https://" + getURLs()."${alexaCountry}".Alexa + "/api/behaviors/preview",
headers: ["Cookie":"""${alexaCookie}""",
"Referer": "https://" + getURLs()."${alexaCountry}".Amazon + "/spa/index.html",
"Origin": "https://" + getURLs()."${alexaCountry}".Amazon,
"csrf": "${csrf}",
"Connection": "keep-alive",
"DNT":"1"],
//requestContentType: "application/json",
contentType: "text/plain",
timeout: 20,
body: command
]
//log.debug "Command = ${params}"
httpPost(params) { resp ->
//log.debug resp.contentType
//log.debug resp.status
//log.debug resp.data
if (resp.status != 200) {
log.error "'speakMessage()': httpPost() resp.status = ${resp.status}"
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
}
}
catch (groovyx.net.http.HttpResponseException hre) {
//Noticed an error in parsing the http response. For now, catch it to prevent errors from being logged
if (hre.getResponse().getStatus() != 200) {
errorMessage = new String("${hre.getResponse().getData()}")
log.warn "'speakMessage()': Error making Call (Data): ${errorMessage}"
log.warn "'speakMessage()': Error making Call (Status): ${hre.getResponse().getStatus()}"
log.warn "'speakMessage()': Error making Call (getMessage): ${hre.getMessage()}"
if ((hre.getResponse().getStatus() == 400) || (hre.getResponse().getStatus() == 429)) {
//log.warn errorMessage
notifyIfEnabled("Alexa TTS: ${errorMessage}")
}
else {
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
}
}
catch (e) {
log.error "'speakMessage()': error = ${e}"
//log.error "'speakMessage()': httpPost() resp.contentType = ${e.response.contentType}"
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
return true
}
}
}
}
def getDevices() {
if (alexaCookie == null) {log.debug "No cookie yet"
return}
try{
def csrf = (alexaCookie =~ "csrf=(.*?);")[0][1]
def params = [uri: "https://" + getURLs()."${alexaCountry}".Alexa + "/api/devices-v2/device?cached=false",
headers: ["Cookie":"""${alexaCookie}""",
"Referer": "https://" + getURLs()."${alexaCountry}".Amazon + "/spa/index.html",
"Origin": "https://" + getURLs()."${alexaCountry}".Amazon,
"csrf": "${csrf}",
"Connection": "keep-alive",
"DNT":"1"],
requestContentType: "application/json; charset=UTF-8",
timeout: 20
]
httpGet(params) { resp ->
//log.debug resp.contentType
//log.debug resp.status
//log.debug resp.data
if ((resp.status == 200) && (resp.contentType == "application/json")) {
def validDevices = ["All Echos"]
atomicState.alexaJSON = resp.data
//log.debug state.alexaJSON.devices.accountName
atomicState.alexaJSON.devices.each {it->
if (it.deviceFamily in ["ECHO", "ROOK", "KNIGHT", "THIRD_PARTY_AVS_SONOS_BOOTLEG", "TABLET"]) {
//log.debug "${it.accountName} is valid"
validDevices << it.accountName
}
else if (it.deviceFamily == "THIRD_PARTY_AVS_MEDIA_DISPLAY" && it.capabilities.contains("AUDIBLE")) {
validDevices << it.accountName
}
else if (it.deviceFamily == "UNKNOWN" && it.capabilities.contains("AUDIO_PLAYER")) {
validDevices << it.accountName
}
}
log.debug "getDevices(): validDevices = ${validDevices}"
return validDevices
}
else {
log.error "Encountered an error. http resp.status = '${resp.status}'. http resp.contentType = '${resp.contentType}'. Should be '200' and 'application/json'. Check your cookie string!"
notifyIfEnabled("Alexa TTS: Please check your cookie!")
return "error"
}
}
}
catch (e) {
log.error "getDevices: error = ${e}"
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
}
private void createChildDevice(String deviceName) {
log.debug "'createChildDevice()': Creating Child Device '${deviceName}'"
try {
def child = addChildDevice("ogiewon", "Child Alexa TTS", "AlexaTTS${app.id}-${deviceName}", null, [name: "AlexaTTS-${deviceName}", label: "AlexaTTS ${deviceName}", completedSetup: true])
} catch (e) {
log.error "Child device creation failed with error = ${e}"
}
}
def installed() {
log.debug "'Installed()' called with settings: ${settings}"
updated()
}
def uninstalled() {
log.debug "'uninstalled()' called"
childDevices.each { deleteChildDevice(it.deviceNetworkId) }
}
def getURLs() {
def URLs = ["United States": [Alexa: "pitangui.amazon.com", Amazon: "alexa.amazon.com", Language: "en-US"],
"Canada": [Alexa: "alexa.amazon.ca", Amazon: "alexa.amazon.ca", Language: "en-US"],
"United Kingdom": [Alexa: "layla.amazon.co.uk", Amazon: "amazon.co.uk", Language: "en-GB"],
"Italy": [Alexa: "alexa.amazon.it", Amazon: "alexa.amazon.it", Language: "it-IT"],
"Australia": [Alexa: "alexa.amazon.com.au", Amazon: "alexa.amazon.com.au", Language: "en-AU"],
"Brazil": [Alexa: "alexa.amazon.com.br", Amazon: "alexa.amazon.com.br", Language: "pt-BR"]]
return URLs
}
def updated() {
log.debug "'updated()' called"
//log.debug "'Updated' with settings: ${settings}"
//log.debug "AlexaJSON = ${atomicState.alexaJSON}"
//log.debug "Alexa Devices = ${atomicState.alexaJSON.devices.accountName}"
def devicesToRemove
if(alexaVC) {
devicesToRemove = getChildDevices().findAll{it.typeName == "Child Alexa TTS"}
if(devicesToRemove) purgeNow(devicesToRemove)
settings.alexaDevices.each {alexaName->
createContainer(alexaName)
}
}
else {
devicesToRemove = getChildDevices().findAll{it.typeName == "Virtual Container"}
if(devicesToRemove) purgeNow(devicesToRemove)
try {
settings.alexaDevices.each {alexaName->
def childDevice = null
if(childDevices) {
childDevices.each {child->
if (child.deviceNetworkId == "AlexaTTS${app.id}-${alexaName}") {
childDevice = child
//log.debug "Child ${app.label}-${alexaName} already exists"
}
}
}
if (childDevice == null) {
createChildDevice(alexaName)
log.debug "Child ${app.label}-${alexaName} has been created"
}
}
}
catch (e) {
log.error "Error in updated() routine, error = ${e}"
}
}
}
def purgeNow(devices){
log.debug "Purging: ${devices}"
devices.each { deleteChildDevice(it.deviceNetworkId) }
}
def createContainer(alexaName){
def container = getChildDevices().find{it.typeName == "Virtual Container"}
if(!container){
log.info "Creating Alexa TTS Virtual Container"
try {
container = addChildDevice("stephack", "Virtual Container", "AlexaTTS${app.id}", null, [name: "AlexaTTS-Container", label: "AlexaTTS Container", completedSetup: true])
} catch (e) {
log.error "Container device creation failed with error = ${e}"
}
createVchild(container, alexaName)
}
else {createVchild(container, alexaName)}
}
def createVchild(container, alexaName){
def vChildren = container.childList()
if(vChildren.find{it.data.vcId == "${alexaName}"}){
log.info alexaName + " already exists...skipping"
}
else {
log.info "Creating TTS Device: " + alexaName
try{
container.appCreateDevice("AlexaTTS ${alexaName}", "Child Alexa TTS", "ogiewon", "${alexaName}")
}
catch (e) {
log.error "Child device creation failed with error = ${e}"
}
}
}
def initialize() {
log.debug "'initialize()' called"
}
private def getCookieFromOptions(options) {
try
{
def cookie = new groovy.json.JsonSlurper().parseText(options)
if (!cookie || cookie == "") {
log.error("'getCookieFromOptions()': wrong options format!")
notifyIfEnabled("Alexa TTS: Error parsing cookie, see logs for more information!")
return ""
}
cookie = cookie.localCookie.replace('"',"")
if(cookie.endsWith(",")) {
cookie = cookie.reverse().drop(1).reverse()
}
cookie += ";"
log.info("Alexa TTS: new cookie parsed succesfully")
return cookie
}
catch(e)
{
log.error("'getCookieFromOptions()': error = ${e}")
notifyIfEnabled("Alexa TTS: Error parsing cookie, see logs for more information!")
return ""
}
}
def refreshCookie() {
log.info("Alexa TTS: starting cookie refresh procedure")
try {
def authHeaders = ""
if(alexaRefreshUsername != "")
authHeaders = "Basic " + (alexaRefreshUsername + ":" + alexaRefreshPassword).bytes.encodeBase64().toString() + "}"
def params =[
uri: alexaRefreshURL,
headers: [
"Authorization":"${authHeaders}",
"Connection": "keep-alive",
"DNT":"1"
],
requestContentType: "application/json; charset=UTF-8",
timeout: 20,
body: alexaRefreshOptions
]
httpPost(params) { resp ->
if ((resp.status == 200)) {
//log.debug resp.contentType
//log.debug resp.status
//log.debug resp.data
def respGuid = resp.data.toString()
log.info("Alexa TTS: Request for new cookie sent succesfully, guid: " + respGuid)
runIn(60*5, getCookie, [data: [guid: respGuid]])
}
else {
log.error "Encountered an error. http resp.status = '${resp.status}'. http resp.contentType = '${resp.contentType}'. Should be '200' and 'application/json; charset=utf-8'"
notifyIfEnabled("Alexa TTS: Error sending request for cookie refresh, see logs for more information!")
return "error"
}
}
}
catch (groovyx.net.http.HttpResponseException hre) {
// Noticed an error in parsing the http response
if (hre.getResponse().getStatus() != 200) {
log.error "'refreshCookie()': Error making Call (Data): ${hre.getResponse().getData()}"
log.error "'refreshCookie()': Error making Call (Status): ${hre.getResponse().getStatus()}"
log.error "'refreshCookie()': Error making Call (getMessage): ${hre.getMessage()}"
if (hre.getResponse().getStatus() == 400) {
notifyIfEnabled("Alexa TTS: ${hre.getResponse().getData()}")
}
else {
notifyIfEnabled("Alexa TTS: Error sending request for cookie refresh, see logs for more information!")
}
}
}
catch (e) {
log.error "'refreshCookie()': error = ${e}"
notifyIfEnabled("Alexa TTS: Error sending request for cookie refresh, see logs for more information!")
}
}
def getCookie(data){
log.info("Alexa TTS: starting cookie download procedure")
if(!data.guid || data.guid == "") {
log.error "'getCookie()': error = guid not provided!"
notifyIfEnabled("Alexa TTS: Error downloading cookie, see logs for more information!")
return "error"
}
try {
def authHeaders = ""
if(alexaRefreshUsername != "")
authHeaders = "Basic " + (alexaRefreshUsername + ":" + alexaRefreshPassword).bytes.encodeBase64().toString() + "}"
def params =[
uri: alexaRefreshURL,
headers: [
"Authorization":"${authHeaders}",
"Connection": "keep-alive",
"DNT":"1"
],
requestContentType: "application/json; charset=UTF-8",
timeout: 20,
query: [guid: data.guid]
]
httpGet(params) { resp ->
//log.debug resp.contentType
//log.debug resp.status
//log.debug resp.data
if ((resp.status == 200) && (resp.contentType == "application/json")) {
//If saved directly as resp.data then double quotes are stripped
def newOptions = new groovy.json.JsonBuilder(resp.data).toString()
app.updateSetting("alexaRefreshOptions",[type:"text", value: newOptions])
log.info("Alexa TTS: cookie downloaded succesfully")
app.updateSetting("alexaCookie",[type:"text", value: getCookieFromOptions(newOptions)])
sendEvent(name:"GetCookie", descriptionText: "New cookie downloaded succesfully")
notifyIfEnabled("Alexa TTS Cookie succesfully refreshed")
}
else {
log.error "Encountered an error. http resp.status = '${resp.status}'. http resp.contentType = '${resp.contentType}'. Should be '200' and 'application/json; charset=utf-8'"
notifyIfEnabled("Alexa TTS: Error downloading cookie, see logs for more information!")
return "error"
}
}
}
catch (groovyx.net.http.HttpResponseException hre) {
// Noticed an error in parsing the http response
if (hre.getResponse().getStatus() != 200) {
log.error "'getCookie()': Error making Call (Data): ${hre.getResponse().getData()}"
log.error "'getCookie()': Error making Call (Status): ${hre.getResponse().getStatus()}"
log.error "'getCookie()': Error making Call (getMessage): ${hre.getMessage()}"
if (hre.getResponse().getStatus() == 400) {
notifyIfEnabled("Alexa TTS: ${hre.getResponse().getData()}")
}
else {
notifyIfEnabled("Alexa TTS: Error downloading cookie, see logs for more information!")
}
}
}
catch (e) {
log.error "'getCookie()': error = ${e}"
notifyIfEnabled("Alexa TTS: Error dowloading cookie, see logs for more information!")
}
}
def notifyIfEnabled(message) {
if (notificationDevice) {
notificationDevice.deviceNotification(message)
}
}
/* lgk new setvolume fx */
def setVolume(Integer newVolume, String device)
{
log.debug "Setting volume level '${newVolume}' to '${device}'"
// find the device
atomicState.alexaJSON.devices.any {it->
if (it.accountName == device) {
//log.debug "${it.accountName}"
//log.debug "${it.deviceType}"
//log.debug "${it.serialNumber}"
//log.debug "${it.deviceOwnerCustomerId}"
try{
def SEQUENCECMD = "Alexa.DeviceControls.Volume"
def DEVICETYPE = "${it.deviceType}"
def DEVICESERIALNUMBER = "${it.serialNumber}"
def MEDIAOWNERCUSTOMERID = "${it.deviceOwnerCustomerId}"
if (ownerID) { MEDIAOWNERCUSTOMERID = ownerID }
def LANGUAGE = getURLs()."${alexaCountry}".Language
def TTS= ",\\\"value\\\":\\\"${newVolume}\\\"" //volume can be 0-100
//log.debug "TTS = $TTS"
def command = "{\"behaviorId\":\"PREVIEW\",\
\"sequenceJson\":\"{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.Sequence\\\",\
\\\"startNode\\\":{\\\"@type\\\":\\\"com.amazon.alexa.behaviors.model.OpaquePayloadOperationNode\\\",\
\\\"type\\\":\\\"${SEQUENCECMD}\\\",\
\\\"operationPayload\\\":{\\\"deviceType\\\":\\\"${DEVICETYPE}\\\",\
\\\"deviceSerialNumber\\\":\\\"${DEVICESERIALNUMBER}\\\",\
\\\"locale\\\":\\\"${LANGUAGE}\\\",\
\\\"customerId\\\":\\\"${MEDIAOWNERCUSTOMERID}\\\"${TTS}}}}\",\
\"status\":\"ENABLED\"}"
// log.debug "Command = $command"
// log.debug "urls = $getURLs()"
// log.debug "country = $alexaCountry"
// log.debug "cookie = $alexaCookie"
def csrf = (alexaCookie =~ "csrf=(.*?);")[0][1]
//log.debug "csrf = $csrf"
def params = [uri: "https://" + getURLs()."${alexaCountry}".Alexa + "/api/behaviors/preview",
headers: ["Cookie":"""${alexaCookie}""",,
"Referer": "https://" + getURLs()."${alexaCountry}".Amazon + "/spa/index.html",
"Origin": "https://" + getURLs()."${alexaCountry}".Amazon,
"csrf": "${csrf}",
"Connection": "keep-alive",
"DNT":"1"],
//requestContentType: "application/json",
contentType: "text/plain",
timeout: 20,
body: command
]
//log.debug "parms = ${params}"
httpPost(params) { resp ->
//log.debug resp.contentType
//log.debug resp.status
//log.debug resp.data
if (resp.status != 200) {
log.error "'setVolume()': httpPost() resp.status = ${resp.status}"
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
}
}
catch (groovyx.net.http.HttpResponseException hre) {
//Noticed an error in parsing the http response. For now, catch it to prevent errors from being logged
if (hre.getResponse().getStatus() != 200) {
log.error "'setVolume()': Error making Call (Data): ${hre.getResponse().getData()}"
log.error "'setVolume()': Error making Call (Status): ${hre.getResponse().getStatus()}"
log.error "'setVolume()': Error making Call (getMessage): ${hre.getMessage()}"
if (hre.getResponse().getStatus() == 400) {
notifyIfEnabled("Alexa TTS: ${hre.getResponse().getData()}")
}
else {
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
}
}
catch (e) {
log.error "'setVolume()': error = ${e}"
//log.error "'setVolume()': httpPost() resp.contentType = ${e.response.contentType}"
notifyIfEnabled("Alexa TTS: Please check your cookie!")
}
return true
}
}
}