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中&&是&&的转义,符合语法,但需验证是否匹配设备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
相关产品推荐
相关产品推荐

