【征文计划】基于Rokid 眼镜 的AI天气应用+GPS定位+AI旅游规划

【征文计划】基于Rokid 眼镜 的AI天气应用+GPS定位+AI旅游规划

文章目录

本文我将解决这三件事,将天气应用升级为AI旅游规划助手,基于Rokid 眼镜 的AI天气应用+GPS定位+AI旅游规划的实现。🙏
在这里插入图片描述

本文选用的技术包括:

GPS 自动定位:说「这里天气」自动获取位置,不用报城市名
多轮对话:说「上海呢」「那边呢」「再查一次」接续上轮查询
AI 旅游规划:接入 Claude API,天气播报后自动生成个性化旅游建议和行程规划
可直接复制的 Kotlin 代码(LocationHelper、ConversationContext、AiTravelPlanHelper)
踩坑经验:直辖市 adcode、续播语义识别、LLM 延迟控制


一、主要流程

本篇在 AiWeatherActivity(AI 语音查天气)基础上扩展,整体数据流如下:

新增三个辅助类,原有文件做对应改造:

新建文件职责
LocationHelper.ktGPS + 高德逆地理编码
ConversationContext.kt多轮对话上下文(含5分钟TTL)
AiTravelPlanHelper.ktClaude API 旅游规划
文件创新点
AiIntentParser.kt+ GPS触发词 + 续播意图解析 + 城市库扩充
WeatherViewHelper.kt+ tv_travel_plan 控件 + generateTravelPlanUpdateJson()
AiWeatherActivity.kt串联 GPS / Context / TravelPlan 完整调用链
---

二、功能 A:GPS 自动定位

2.1 实现路径

用户说完“这里的天气”不想等 5 秒。缓存位置最多偏差几公里,对天气查询完全够用。

2.2 核心代码:LocationHelper.kt

classLocationHelper(privateval context: Context){interface LocationCallback {funonCityCode(adcode: String, cityName: String, districtName: String)funonError(reason: String)}fungetCurrentCityCode(callback: LocationCallback){if(!hasLocationPermission()){ callback.onError("缺少定位权限")return}val manager = context.getSystemService(Context.LOCATION_SERVICE)as LocationManager val lastKnown =getLastKnownLocation(manager)if(lastKnown !=null){reverseGeocode(lastKnown.latitude, lastKnown.longitude, callback)}else{requestSingleUpdate(manager, callback)}}@SuppressLint("MissingPermission")privatefungetLastKnownLocation(manager: LocationManager): Location?=listOf(GPS_PROVIDER, NETWORK_PROVIDER, PASSIVE_PROVIDER).mapNotNull{ runCatching { manager.getLastKnownLocation(it)}.getOrNull()}.maxByOrNull{ it.time }privatefunreverseGeocode(lat: Double, lon: Double, callback: LocationCallback){// 注意:高德 API 格式是 "经度,纬度"(lon在前)val url ="$REGEO_URL?location=$lon,$lat&key=$API_KEY&extensions=base&output=JSON"// OkHttp 调用 ...// 解析响应val component = json .getJSONObject("regeocode").getJSONObject("addressComponent")val adcode = component.optString("adcode")// 坑:直辖市 city 字段为空,需取 provinceval city = component.optString("city").ifEmpty{ component.optString("province")}val district = component.optString("district") callback.onCityCode(adcode, city, district)}}

2.3 意图识别:我们添加 GPS 的关键词

在 AiIntentParser 里加一批触发词,识别「天气」类意图:

privateval LOCATION_KEYWORDS =listOf("这里","附近","当前","我这","这边","当前位置","我在哪","这里的")// 返回特殊常量 INTENT_LOCATION,交给 Activity 分支处理constval INTENT_LOCATION ="__LOCATION__"funisLocationIntent(text: String): Boolean {val hasLocation = LOCATION_KEYWORDS.any{ text.contains(it)}val hasWeather = text.contains("天气")|| WEATHER_KEYWORDS.any{ text.contains(it)}return hasLocation && hasWeather }

Activity 侧处理分支:

privatefunprocessRecognizedText(text: String){val intent = intentParser.parseWeatherIntent(text, conversationContext)when{ intent ==null->{updateStatus("未识别到查询意图,请说「XXX天气」或「这里天气」")notifyAiError()} intent == AiIntentParser.INTENT_LOCATION ->handleLocationIntent()else->queryWeather(intent, intentParser.getCityNameByCode(intent))}}privatefunhandleLocationIntent(){ checkLocationPermission { locationHelper.getCurrentCityCode(object: LocationHelper.LocationCallback{overridefunonCityCode(adcode: String, cityName: String, districtName: String){val name =if(districtName.isNotBlank())"$cityName$districtName"else cityName queryWeather(adcode, name)}overridefunonError(reason: String){notifyAiError()}})}}

三、功能 B:对话上下文工程

3.1 核心数据结构

dataclassConversationContext(val lastCityCode: String?=null,val lastCityName: String?=null,val turnCount: Int =0,val lastQueryTimeMs: Long =0L){companionobject{privateconstval CONTEXT_TTL_MS =5*60*1000L// 5分钟}funisValid(): Boolean = lastCityCode !=null &&(System.currentTimeMillis()- lastQueryTimeMs) < CONTEXT_TTL_MS funadvance(cityCode: String, cityName: String): ConversationContext =copy( lastCityCode = cityCode, lastCityName = cityName, turnCount = turnCount +1, lastQueryTimeMs = System.currentTimeMillis())}

为什么设 5 分钟 TTL? 其实就是经验估计:5 分钟内的续问大概率是连续对话;超过 5 分钟放下手机再拿起来,基本是新话题,不应复用旧上下文。

3.2 续播意图的两种形态

privateval CONTINUATION_KEYWORDS =listOf("那呢","那边","那里呢","那边呢","再查","继续","再来一次","重新查")privatefunparseContinuationIntent(text: String, ctx: ConversationContext): String?{// 形态1:续播词 → 直接复用上次城市if(CONTINUATION_KEYWORDS.any{ text.contains(it)})return ctx.lastCityCode // 形态2:只有城市名,没有天气关键词(「福州呢」)→ 切换城市val hasWeather = WEATHER_KEYWORDS.any{ text.contains(it)}if(!hasWeather){val cityCode =extractCityCode(text)if(cityCode !=null)return cityCode }returnnull}

三种典型场景对照:

用户说解析结果
「福州呢」形态2:切换到福州
「那边呢」形态1:复用上次城市
「再查一次」形态1:同城市重查
「明天北京天气」正常解析:北京(不走续播)

Activity 侧每次成功查询后更新上下文:

// queryWeather 成功回调中: conversationContext = conversationContext.advance(cityCode, cityName)

四、功能 C:AI 旅游规划

4.1 为什么用 LLM, 而不是规则

用规则也能生成简单建议:

if(temp <10)"适合室内景点"elseif(weather.contains("雨"))"建议带伞,推荐室内博物馆"else"户外景点和公园都适合"

问题在于这是死的。同样是 25 度、晴天:北京故宫需要建议避开人流高峰;杭州西湖需要推荐骑行路线;三亚应该提醒防晒。LLM 能感知城市的旅游特色、气候背景,给出有地域差异的个性化旅游建议,这是规则系统做不到的。

4.2 核心代码:AiTravelPlanHelper.kt

class AiTravelPlanHelper {companionobject{privateconstval API_URL ="https://api.anthropic.com/v1/messages"privateconstval CLAUDE_API_KEY ="YOUR_CLAUDE_API_KEY"privateconstval MODEL ="claude-haiku-4-5-20251001"}interface TravelPlanCallback {funonTravelPlan(plan: String)funonError(reason: String)}fungetTravelPlan( city: String, temp: String, weather: String, wind: String, humidity: String, callback: TravelPlanCallback ){val systemPrompt ="你是一个专业的旅游规划助手,根据天气数据为用户生成简洁的中文旅游建议。"+"要求:语气自然友好,不超过80字,直接给建议,包含1-2个当地特色景点推荐,不要重复天气数据。"val userMessage ="城市:$city,气温:${temp}°C,天气:$weather,"+"风力:$wind,湿度:${humidity}%,请给出旅游建议。"val requestBody =JSONObject().apply{put("model", MODEL)put("max_tokens",300)put("system", systemPrompt)put("messages",JSONArray().apply{put(JSONObject().apply{put("role","user")put("content", userMessage)})})}.toString()val request = Request.Builder().url(API_URL).addHeader("x-api-key", CLAUDE_API_KEY).addHeader("anthropic-version","2023-06-01").addHeader("content-type","application/json").post(requestBody.toRequestBody("application/json".toMediaType())).build() client.newCall(request).enqueue(object: Callback {overridefunonResponse(call: Call, response: Response){val body = response.body?.string()?:returnval plan =JSONObject(body).getJSONArray("content").getJSONObject(0).optString("text")?.trim()if(plan !=null) callback.onTravelPlan(plan)else callback.onError("解析失败")}overridefunonFailure(call: Call, e: IOException){ callback.onError("网络请求失败: ${e.message}")}})}}

4.3 Prompt 设计要点

「不要重复天气数据」这条约束很关键——用户刚听完 TTS 播报了天气,建议里再说「当前北京25度晴天,推荐去故宫」是纯粹的信息冗余。选 claude4.5而不是更强的模型,是因为这个场景对「聪明程度」要求不高,对延迟的要求更高:用户说完天气查询,天气 TTS 结束后 2 秒内最好就能听到旅游建议。

4.4 与天气查询的串联时序

privatefunqueryWeather(cityCode: String, cityName: String){ weatherApiHelper.getWeatherForecast(cityCode,object: WeatherApiHelper.WeatherCallback{overridefunonSuccess(response: WeatherApiResponse){val live = response.lives?.firstOrNull()val forecast = response.forecasts?.firstOrNull()// 1. 打开眼镜端 Custom View(旅游规划区初始显示「规划获取中...」)openGlassCustomView(weatherViewHelper.generateWeatherViewJson(live, forecast))// 2. TTS 播报天气摘要sendWeatherTts(weatherViewHelper.generateWeatherTtsText(live, forecast))// 3. 更新多轮上下文 conversationContext = conversationContext.advance(cityCode, cityName)// 4. 异步获取 AI 旅游规划(不阻塞天气播报)if(live !=null)fetchAiTravelPlan(live, cityName)}overridefunonError(error: String){notifyAiError()}})}privatefunfetchAiTravelPlan(live: Live, cityName: String){val wind ="${live.winddirection ?:""}${live.windpower ?:""}".trim() travelPlanHelper.getTravelPlan( city = cityName, temp = live.temperature ?:"--", weather = live.weather ?:"--", wind = wind, humidity = live.humidity ?:"--", callback =object: AiTravelPlanHelper.TravelPlanCallback{overridefunonTravelPlan(plan: String){// 更新眼镜端旅游规划控件updateGlassCustomView(weatherViewHelper.generateTravelPlanUpdateJson(plan))// 延迟 2 秒播报,避免与天气 TTS 重叠Handler(Looper.getMainLooper()).postDelayed({sendGlobalTtsContent(plan)},2000L)}overridefunonError(reason: String){updateGlassCustomView( weatherViewHelper.generateTravelPlanUpdateJson("旅游规划暂时无法获取"))}})}

4.5 眼镜端 Custom View 新增旅游规划区

WeatherViewHelper 在原有天气卡片末尾追加分割线和旅游规划控件:

// 分割线 children.put(createTextView( id ="tv_divider", text ="─────────────────", textSize ="10sp", textColor ="#FF444444", marginTop ="12dp", marginBottom ="8dp"))// AI 旅游规划占位(成功后 updateCustomView 更新) children.put(createTextView( id = ViewIds.TV_TRAVEL_PLAN, text ="规划获取中...", textSize ="14sp", textColor ="#FFFFCC00"// 金色,区别于普通信息))

仅更新旅游规划的方法:

fungenerateTravelPlanUpdateJson(plan: String): String {val updates =JSONArray() updates.put(createUpdateAction(ViewIds.TV_TRAVEL_PLAN,"text", plan))return updates.toString()}

五、踩坑与排错速查

直辖市逆地理编码返回城市名为空

高德 regeo 接口,北京/上海/天津/重庆的 city 字段是空字符串,城市信息在 province 里:

// 错误写法:val city = component.optString("city")// 北京返回 ""// 正确写法:val city = component.optString("city").ifEmpty{ component.optString("province")}

续播语义识别错误

判断关键是「有没有天气关键词」:

  • 有天气关键词(「北京天气」)→ 走正常解析,不走续播
  • 无天气关键词(「北京呢」)+ 有城市名 → 走续播,切换城市
  • 续播词(「那边呢」)→ 复用上次城市

AI 旅游规划延迟太长/播报重叠

Claude Haiku 响应通常在 1-2 秒。fetchAiTravelPlan 在天气查询成功后立即异步发起,规划播报延迟 2 秒,基本不会与天气 TTS 重叠。如果网络慢可以加 OkHttp 超时:

OkHttpClient.Builder().readTimeout(10, TimeUnit.SECONDS).build()

requestSingleUpdate 废弃警告

LocationManager.requestSingleUpdate() 在 API 30+ 被标记废弃,但本项目 minSdk=28,功能完全正常,用 @Suppress(“DEPRECATION”) 压警告即可。


六、完整调用示意

用户:「这里天气」 → isLocationIntent → INTENT_LOCATION → checkLocationPermission → LocationHelper.getCurrentCityCode → 高德 regeo → adcode=110105(朝阳区) → queryWeather("110105", "北京市朝阳区") → openCustomView(天气卡片,旅游规划区显示「获取中...」) → sendTtsContent(「北京市朝阳区当前天气,温度25度,晴...」) → context.advance("110105", "北京市朝阳区") → AiTravelPlanHelper.getTravelPlan → Claude API → updateCustomView(「今天天气舒适,推荐去朝阳公园散步,傍晚可以去三里屯逛逛」) → 2秒后 sendGlobalTtsContent(「今天天气舒适,推荐去朝阳公园散步,傍晚可以去三里屯逛逛」) 用户:「福州呢」 → parseContinuationIntent → 形态2,切换到福州 → queryWeather("310101", "福州") ...(同上流程) 用户:「那边呢」 → parseContinuationIntent → 形态1,复用福州 → queryWeather("310101", "福州") ... 

七、其他功能

做完这篇,其实有一个更大的问题浮现:眼镜应该做什么?

手机是工具——你主动去用它。眼镜是助手——它在你需要的时候说一句话,然后闭上嘴。

天气+旅游是最安全的起点:不打扰、有明确答案、TTS 一句话说完。但如果你想继续探索,以下方向都在这套框架上可以直接延伸:

  • 景点导览:到达景点后自动识别位置,推送景点介绍和历史背景
  • 行程提醒:结合日历,提前推送目的地天气和出行建议
  • 实时路况:结合地图数据,提供出行路线和实时交通信息
  • 多日规划:「那明天呢」处理预报字段,生成多天旅游行程
  • 美食推荐:结合当地特色美食,根据天气推荐适合的餐厅
    🙏🙏🙏🙏

Read more

【无人机追踪】基于 0-1 整数规划实现「能耗最小」的无人机联盟选取,完成目标攻击任务的同时,让所有无人机的总能耗达到最优附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 🍎 往期回顾关注个人主页:Matlab科研工作室  👇 关注我领取海量matlab电子书和数学建模资料  🍊个人信条:格物致知,完整Matlab代码获取及仿真咨询内容私信。 🔥 内容介绍  一、背景 在现代军事作战或特定的工业应用场景中,常常需要多架无人机协同完成目标攻击任务。然而,无人机的能源储备有限,能耗问题成为制约其任务执行效率和持续时间的关键因素。如何在众多无人机中选取合适的无人机组成联盟,使其在成功完成目标攻击任务的同时,将所有参与无人机的总能耗控制在最优水平,是一个亟待解决的重要问题。 传统的无人机任务分配方式可能没有充分考虑能耗因素,或者只是简单地基于距离、速度等单一指标进行分配,这往往无法实现总能耗的最优控制。基于 0 - 1 整数规划的方法为解决这一问题提供了一种有效的途径,它能够综合考虑多种约束条件,精确地对无人机进行筛选和组合,以达到能耗最小化的目标。 二、原理 (一)0 - 1 整数规划基础

FPGA 50 ,Xilinx Vivado 2020 版本安装流程,以及常见问题解析,附中文翻译( Vivado 2020 版本安装教程 )

FPGA 50 ,Xilinx Vivado 2020 版本安装流程,以及常见问题解析,附中文翻译( Vivado 2020 版本安装教程 )

前言 Xilinx 统一安装程序(Unified Installer) 是进行 FPGA 与异构计算平台开发的重要基础工具,集成了 Vivado、Vitis 以及相关文档与设备支持组件。正确完成安装是后续进行硬件设计、软件开发与系统验证的前提。 本文以 Xilinx 统一安装程序 2020.1 为例,结合实际安装过程,对 安装步骤 进行逐步说明,并对 关键选项 的含义进行必要解释。同时,针对安装过程中可能出现的 常见错误(如归档文件无法打开、安装中断等问题),给出原因分析与解决建议,帮助用户快速定位并解决问题。 需要注意的是,安装文件的完整性与安装环境的稳定性对安装成功率影响较大。若安装过程中出现异常,建议优先检查 安装包是否完整、磁盘空间是否充足以及系统权限与安全软件设置是否合理。希望本文能够为初次接触 Xilinx 工具 或在安装过程中遇到问题的用户提供参考和帮助。

Sublime配置verilog开发环境-具备语法高亮、代码补全、自定义代码段及语法检查等功能,提升FPGA开发效率!

Sublime配置verilog开发环境-具备语法高亮、代码补全、自定义代码段及语法检查等功能,提升FPGA开发效率!

对于在学习FPGA开发之前使用过其他集成开发工具如VS、pycharm、keil或编辑工具如Sublime、VScode、Notepad的朋友,在使用Vivado时可能会像博主一样感觉自带编辑器用起来不太舒服,比如不支持语法高亮显示,不支持代码自动补全等功能。因次,使用第三方编辑器来编写Verilog代码是很有必要的。 本文将详细介绍如何在文本编辑器Sublime中配置verilog开发环境,最终实现语法高亮、代码补全、自定义代码段及语法检查等功能,使得可以在Sublime中高效编写verilog代码,大幅提升FPGA开发效率!附带自己在配置中的踩坑经验,希望朋友们按着下面的流程走可以一步配置到位!下面两图为使用Vivado编写代码及使用Sublime编写代码的对比图。 1.Sublime的介绍与安装配置         Sublime Text,是一款由 Sublime HQ 开发的跨平台轻量级代码编辑器,以 “启动快、插件丰富、自定义性强” 为核心特点,广泛用于代码编写、文本编辑和开发效率提升,支持 Windows、macOS、Linux 三大操作系统。

近五年体内微/纳米机器人赋能肿瘤精准治疗综述:以 GBM 为重点

近五年体内微/纳米机器人赋能肿瘤精准治疗综述:以 GBM 为重点

摘要 实体瘤治疗长期受制于递送效率低、肿瘤组织渗透不足以及免疫抑制与耐药等问题。传统纳米药物多依赖被动累积与扩散,难以在肿瘤内部形成均匀有效的药物浓度分布。2021–2025 年,体内微/纳米机器人(包括外场驱动微型机器人、自驱动纳米马达以及生物混合机器人)围绕“运动能力”形成了三条相互收敛的技术路线: 其一,通过磁驱、声驱、光/化学自驱等方式实现运动增强递药与深层渗透,将治疗从“被动到达”推进到“主动进入”; 其二,与免疫治疗深度融合,实现原位免疫唤醒与肿瘤微环境重塑; 其三,针对胶质母细胞瘤(glioblastoma, GBM)等难治肿瘤,研究趋势转向“跨屏障递送(BBB/BBTB)+ 成像/外场闭环操控 + 时空可控释放”的系统工程。 本文围绕“运动—分布—疗效”的因果链条,总结 2021–2025 年代表性研究与关键评价指标,讨论临床转化所需的安全性、