You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alexa Skills Kit小部件安装失败求助:Echo Show 10报错

Alexa Widget安装错误排查方案

问题描述

开发了基于APL的简易Alexa Skills Kit小部件,尝试安装到Echo Show 10设备时,弹出标题为“Problem installing widget”、内容为“There were problems in your install widget request”并附带时间戳的错误弹窗。

相关代码

APL代码

{
    "type": "APL",
    "version": "2023.3",
    "license": "Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: LicenseRef-.amazon.com.-AmznSL-1.0\nLicensed under the Amazon Software License  http://aws.amazon.com/asl/",
    "import": [
        {
            "name": "alexa-layouts",
            "version": "1.7.0"
        }
    ],
    "extensions": [
        {
            "uri": "alexaext:datastore:10",
            "name": "DataStore"
        } 
   ],
    "settings": {
        "DataStore": {
            "dataBindings": [
                {
                    "namespace": "widget_bin_datastore",
                    "key": "binData", 
                   "dataBindingName": "dsBinData",
                    "dataType": "OBJECT"
                }
            ]
        }
    },
    "onDisplayStateChange": [
        {
            "type": "Sequential",
            "sequencer": "displaySequencer",
            "commands": [
                {
                    "type": "SendEvent",
                    "arguments": [
                        "${event.displayState}"
                    ],
                    "flags": {
                        "interactionMode": "STANDARD"
                    }
                }
            ]
        }
    ],
    "mainTemplate": {
        "parameters": [
            "payload"
        ],
        "items": [
            {
                "type": "AlexaPhoto",
                "headerTitle": "Salford Bins",
                "imageSource": "https://i.imgur.com/B90CXAo.png",
                "primaryText": "${payload.dsBinData.bin_text}",
                "secondaryText": "${payload.dsBinData.postcode}",
                "buttonText": "Refresh",
                "imageHideScrim": true,
                "theme": "dark",
                "primaryAction": [
                    {
                        "type": "SendEvent",
                        "arguments": [
                            "widgetRefresh"
                        ]
                    }
                ]
            }
        ]
    }
}

部件包清单

{
    "manifest": {
        "id": "widget",
        "version": "1.0.0",
        "installStateChanges": "INFORM",
        "updateStateChanges": "INFORM",
        "presentationDefinitions": [
            {
                "url": "presentations/default.tpl"
            }
        ],
        "appliesTo": "${viewport.mode == 'HUB' && location == 'FAVORITE'}"
    },
    "packageVersion": "1.0",
    "packageType": "APL_PACKAGE",
    "publishingInformation": {
        "schemaVersion": "1.0",
        "locales": {
            "en-GB": [
                {
                    "targetViewport": "WIDGET_M",
                    "metadata": {
                        "name": "Widget name",
                        "description": "Widget description",
                        "keywords": [
                            "keyword1",
                            "keyword2"
                        ],
                        "iconUri": "https://d3ozx4qyxcxwzd.cloudfront.net/default_icon.png",
                        "previews": [
                            "https://d3ozx4qyxcxwzd.cloudfront.net/default_preview.png"
                        ]
                    }
                }
            ]
        }
    }
}

请求处理函数

def get_access_token():
    return requests.post("https://api.amazon.com/auth/o2/token", {
        "grant_type": "client_credentials",
        "client_id": "[redacted]",
        "client_secret": "[redacted]",
        "scope": "alexa::datastore"
        }).json()['access_token']

def _post_put_namespace(datastore_namespace, access_token=None):
    if access_token == None:
        access_token = get_access_token()
    requests.post(
        url='https://api.eu.amazonalexa.com/v1/datastore/commands',
        headers={'access_token': get_access_token(),
            'type': 'PUT_NAMESPACE',
            'namespace': datastore_namespace
            }
        )

def _post_put_object(namespace, key, content, access_token=None):
    if access_token == None:
        access_token = get_access_token()
    requests.post(
        url='https://api.eu.amazonalexa.com/v1/datastore/commands',
        headers={"access_token": get_access_token(),
                'type': 'PUT_OBJECT',
                'namespace': namespace,
                'key': key,
                'content': content
            }
        )

def put_bin_data_to_datastore(postcode, bin_colours):
    access_token = get_access_token()
    _post_put_namespace('widget_bin_datastore', access_token=access_token)
    _post_put_object(
        namespace='widget_bin_datastore', 
        key='binData', 
        content=str({
            "postcode": postcode,
            "bin_text": ", ".join(bin_colours)
        })
        )

def _check_user_events(handler_input, arguments_possibilites):
    for arguments in arguments_possibilites:
        if is_request_type("Alexa.Presentation.APL.UserEvent")(handler_input):
            user_event = handler_input.request_envelope.request
            if user_event.arguments == arguments:
                return True
    
    return False

WidgetRefreshHandler代码

class WidgetRefreshHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return _check_user_events(
            handler_input,
            [
                ['widgetRefresh'],
                ['foreground'],
                ['background']
            ]
            ) or (
                is_request_type("Alexa.DataStore.PackageManager.UsagesInstalled")(handler_input)
            ) or (
                is_request_type("Alexa.DataStore.PackageManager.UpdateRequest")(handler_input)
            ) or (
                is_intent_name('WidgetRefreshIntent')(handler_input)
                )
    
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        attr = handler_input.attributes_manager.persistent_attributes
        postcode = attr.get('postcode')
        saved_collections = attr.get('saved_collections')
        timestamp_now = datetime.datetime.now().timestamp()
        for saved_collection_day in saved_collections:
            collection_timestamp = datetime.datetime.strptime(saved_collection_day, "%A %d %B %Y  %H:%M:%S").timestamp()
            if timestamp_now < collection_timestamp:
                bin_colours = saved_collections[saved_collection_day]
                break
        
        put_bin_data_to_datastore(postcode=postcode, bin_colours=bin_colours)
        
        return handler_input.response_builder.response

排查方案

1. 修正DataStore API请求格式

当前代码将PUT_NAMESPACE和PUT_OBJECT的参数放在HTTP Headers中,不符合Alexa DataStore API要求。正确做法是将参数放在请求体中,Headers仅包含授权和内容类型:

def _post_put_namespace(datastore_namespace, access_token=None):
    if access_token is None:
        access_token = get_access_token()
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    }
    payload = {
        "type": "PUT_NAMESPACE",
        "namespace": datastore_namespace
    }
    requests.post(
        url='https://api.eu.amazonalexa.com/v1/datastore/commands',
        headers=headers,
        json=payload
    )

def _post_put_object(namespace, key, content, access_token=None):
    if access_token is None:
        access_token = get_access_token()
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    }
    payload = {
        "type": "PUT_OBJECT",
        "namespace": namespace,
        "key": key,
        "content": content
    }
    requests.post(
        url='https://api.eu.amazonalexa.com/v1/datastore/commands',
        headers=headers,
        json=payload
    )

注意:不要重复调用get_access_token(),直接使用传入的access_token参数,避免不必要的token请求。

2. 修正Content序列化方式

put_bin_data_to_datastore中用str()转换字典会生成带单引号的字符串,不符合JSON格式,需改用json.dumps():

import json

def put_bin_data_to_datastore(postcode, bin_colours):
    access_token = get_access_token()
    _post_put_namespace('widget_bin_datastore', access_token=access_token)
    content = json.dumps({
        "postcode": postcode,
        "bin_text": ", ".join(bin_colours)
    })
    _post_put_object(
        namespace='widget_bin_datastore', 
        key='binData', 
        content=content,
        access_token=access_token
    )

3. 处理Persistent Attributes空值

WidgetRefreshHandler中未判断postcode或saved_collections为空的情况,会导致后续代码报错,需添加空值检查:

def handle(self, handler_input):
    attr = handler_input.attributes_manager.persistent_attributes
    postcode = attr.get('postcode')
    saved_collections = attr.get('saved_collections')
    
    # 空值检查
    if not postcode or not saved_collections:
        return handler_input.response_builder.response
    
    timestamp_now = datetime.datetime.now().timestamp()
    bin_colours = []
    for saved_collection_day in saved_collections:
        try:
            collection_timestamp = datetime.datetime.strptime(saved_collection_day, "%A %d %B %Y  %H:%M:%S").timestamp()
            if timestamp_now < collection_timestamp:
                bin_colours = saved_collections[saved_collection_day]
                break
        except ValueError:
            continue
    
    if bin_colours:
        put_bin_data_to_datastore(postcode=postcode, bin_colours=bin_colours)
    
    return handler_input.response_builder.response

4. 验证部件包清单配置

  • 确认appliesTo表达式语法正确性:JSON中&amp;&amp;是&&的转义,符合语法,但需验证是否匹配设备viewport;
  • 检查presentationDefinitions中的URL是否可访问,确保APL模板文件路径正确;
  • 确认targetViewport指定的WIDGET_M适配Echo Show 10的widget尺寸。

5. 检查权限与区域一致性

  • 确保client_id和client_secret拥有alexa::datastore权限;
  • 验证token请求和DataStore API的区域一致性:若使用欧盟区域设备,token请求可尝试使用https://api.eu.amazon.com/auth/o2/token;
  • 确认技能部署区域与Echo Show 10所在区域匹配。

6. 排查APL兼容性问题

  • 验证alexa-layouts 1.7.0与APL 2023.3版本的兼容性,可尝试升级alexa-layouts到最新兼容版本;
  • 检查AlexaPhoto组件的属性是否符合对应版本文档要求,比如imageHideScrim等属性是否存在。

7. 安装事件数据初始化

触发UsagesInstalled事件时,确保DataStore中已存在有效数据,避免widget因数据为空渲染失败。可在处理该事件时先初始化默认数据。

内容的提问来源于stack exchange,提问作者CauseYNot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 22:37:32