feat: 添加权限管理并增强导航功能
- 新增 PermissionManager 用于检查和请求 Temi 机器人权限 - 在 AndroidManifest 中声明所需的权限元数据 - 为 NavController 添加充电功能和改进的巡逻逻辑 - 扩展 MQTT 命令支持,包括充电和可配置巡逻 - 添加 Python 测试脚本用于 MQTT 流式文本测试 - 使用任务状态跟踪替代原有的接待模式标志
This commit is contained in:
@@ -20,6 +20,15 @@
|
||||
<meta-data
|
||||
android:name="com.robotemi.sdk.metadata.SKILL"
|
||||
android:value="@string/app_name" />
|
||||
|
||||
<meta-data
|
||||
android:name="@string/metadata_permissions"
|
||||
android:value="com.robotemi.permission.settings,
|
||||
com.robotemi.permission.face_recognition,
|
||||
com.robotemi.permission.map,
|
||||
com.robotemi.permission.sequence,
|
||||
com.robotemi.permission.meetings" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
@@ -31,6 +40,8 @@
|
||||
</activity>
|
||||
<activity android:name=".SettingsActivity" />
|
||||
|
||||
|
||||
|
||||
<service android:name="org.eclipse.paho.android.service.MqttService" />
|
||||
|
||||
</application>
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.robotemi.sdk.listeners.OnRobotReadyListener
|
||||
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.DETECTED
|
||||
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener.Companion.IDLE
|
||||
import com.robotemi.sdk.navigation.listener.OnReposeStatusChangedListener
|
||||
import com.robotemi.sdk.permission.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -25,7 +26,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener,
|
||||
OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
OnRequestPermissionResultListener {
|
||||
|
||||
private lateinit var robot: Robot
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
@@ -33,13 +35,14 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var navCon: NavController
|
||||
private lateinit var permissionManager: PermissionManager
|
||||
|
||||
private var lastArrivalLocation: String? = null
|
||||
private var lastArrivalAt: Long = 0L
|
||||
private val fixedFaceScale = 1.0f
|
||||
private val baseFaceSizeDp = 1000f
|
||||
private var currentTask: String = ""
|
||||
|
||||
// Reception mode
|
||||
private var isReceptionMode = false
|
||||
private var receptionLocation: String = ""
|
||||
private var receptionText: String = ""
|
||||
private var receptionDestination: String = ""
|
||||
@@ -54,6 +57,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
|
||||
robot = Robot.getInstance()
|
||||
navCon = NavController(robot)
|
||||
permissionManager = PermissionManager(robot)
|
||||
|
||||
prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||
|
||||
@@ -82,6 +86,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
robot.addOnGoToLocationStatusChangedListener(this)
|
||||
robot.addOnDetectionStateChangedListener(this)
|
||||
robot.addOnReposeStatusChangedListener(this)
|
||||
robot.addOnRequestPermissionResultListener(this)
|
||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||
mqttManager?.connect()
|
||||
}
|
||||
@@ -93,6 +98,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
robot.removeOnGoToLocationStatusChangedListener(this)
|
||||
robot.removeOnDetectionStateChangedListener(this)
|
||||
robot.removeOnReposeStatusChangedListener(this)
|
||||
robot.removeOnRequestPermissionResultListener(this)
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||
mqttManager?.disconnect()
|
||||
}
|
||||
@@ -108,6 +114,21 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
override fun onRobotReady(isReady: Boolean) {
|
||||
if (isReady) {
|
||||
Log.i("MainActivity", "Robot is ready!")
|
||||
permissionManager.checkAndRequestPermissions()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionResult(
|
||||
permission: Permission,
|
||||
grantResult: Int,
|
||||
requestCode: Int
|
||||
) {
|
||||
if (requestCode == PermissionManager.REQUEST_CODE_TEMI_PERMISSIONS) {
|
||||
if (grantResult == Permission.GRANTED) {
|
||||
Log.i("MainActivity", "Temi permission GRANTED: $permission")
|
||||
} else {
|
||||
Log.w("MainActivity", "Temi permission DENIED: $permission")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +136,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
mqttManager?.handleTtsStatusChange(ttsRequest)
|
||||
when (ttsRequest.status) {
|
||||
TtsRequest.Status.STARTED -> {
|
||||
Log.i("MainActivity", "TTS started: ${ttsRequest.speech}")
|
||||
Log.i("MainActivity", "TTS started")
|
||||
binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.TALKING
|
||||
}
|
||||
TtsRequest.Status.COMPLETED -> {
|
||||
@@ -152,8 +173,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
}
|
||||
|
||||
override fun onDetectionStateChanged(state: Int) {
|
||||
// Reception mode logic
|
||||
if (isReceptionMode && lastArrivalLocation == receptionLocation) {
|
||||
if (currentTask == "reception" && lastArrivalLocation == receptionLocation) {
|
||||
when (state) {
|
||||
DETECTED -> {
|
||||
if (binding.btnReception.visibility != android.view.View.VISIBLE) {
|
||||
@@ -171,34 +191,34 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
}
|
||||
|
||||
// Home Base logic
|
||||
if (lastArrivalLocation?.lowercase() == "home base") {
|
||||
when (state) {
|
||||
DETECTED -> {
|
||||
mainScope.launch {
|
||||
HttpManager.workflow_execute(
|
||||
context = this@MainActivity,
|
||||
apiKey = "wf_865e80f5fc1a4a319474a21d47470863",
|
||||
workflowId = "2031297462423851009",
|
||||
inputs = emptyMap<String, Any>()
|
||||
)
|
||||
}
|
||||
}
|
||||
IDLE -> {
|
||||
mainScope.launch {
|
||||
HttpManager.workflow_execute(
|
||||
context = this@MainActivity,
|
||||
apiKey = "wf_c02aa853371345dbb29572641d083c24",
|
||||
workflowId = "2031634633458520065",
|
||||
inputs = emptyMap<String, Any>()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (lastArrivalLocation?.lowercase() == "home base") {
|
||||
// when (state) {
|
||||
// DETECTED -> {
|
||||
// mainScope.launch {
|
||||
// HttpManager.workflow_execute(
|
||||
// context = this@MainActivity,
|
||||
// apiKey = "wf_865e80f5fc1a4a319474a21d47470863",
|
||||
// workflowId = "2031297462423851009",
|
||||
// inputs = emptyMap<String, Any>()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// IDLE -> {
|
||||
// mainScope.launch {
|
||||
// HttpManager.workflow_execute(
|
||||
// context = this@MainActivity,
|
||||
// apiKey = "wf_c02aa853371345dbb29572641d083c24",
|
||||
// workflowId = "2031634633458520065",
|
||||
// inputs = emptyMap<String, Any>()
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
fun startReceptionMode(location: String, text: String, destination: String) {
|
||||
isReceptionMode = true
|
||||
currentTask = "reception"
|
||||
receptionLocation = location
|
||||
receptionText = text
|
||||
receptionDestination = destination
|
||||
@@ -209,7 +229,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
}
|
||||
|
||||
private fun stopReceptionMode() {
|
||||
isReceptionMode = false
|
||||
currentTask = ""
|
||||
receptionLocation = ""
|
||||
receptionText = ""
|
||||
receptionDestination = ""
|
||||
@@ -217,6 +237,11 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
Log.i("MainActivity", "Reception mode stopped")
|
||||
}
|
||||
|
||||
fun setCurrentTask(task: String) {
|
||||
currentTask = task
|
||||
Log.i("MainActivity", "Current task set to: $task")
|
||||
}
|
||||
|
||||
override fun onReposeStatusChanged(status: Int, description: String) {
|
||||
when (status) {
|
||||
OnReposeStatusChangedListener.REPOSING_COMPLETE -> {
|
||||
@@ -266,4 +291,5 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
binding.animatedEmojiView.layoutParams = params
|
||||
binding.animatedEmojiView.requestLayout()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -161,6 +161,10 @@ class MqttManager(
|
||||
private fun handleJsonCommand(obj: JSONObject) {
|
||||
val action = obj.optString("action", obj.optString("cmd", obj.optString("type", ""))).lowercase()
|
||||
when (action) {
|
||||
"charge" -> {
|
||||
speak("前往充电桩", "zh")
|
||||
navController.charge()
|
||||
}
|
||||
"goto" -> {
|
||||
val location = obj.optString("location", obj.optString("target", ""))
|
||||
goTo(location)
|
||||
@@ -184,7 +188,25 @@ class MqttManager(
|
||||
stopTts()
|
||||
}
|
||||
"patrol" -> {
|
||||
navController.randomPatrol()
|
||||
(context as? MainActivity)?.setCurrentTask("patrol")
|
||||
speak("接到巡逻任务", "zh")
|
||||
val flag = obj.optBoolean("flag", true)
|
||||
if (flag) {
|
||||
Log.d(TAG, "navController.randomPatrol() called.")
|
||||
navController.randomPatrol()
|
||||
} else {
|
||||
val locationsArray = obj.optJSONArray("locations")
|
||||
if (locationsArray != null && locationsArray.length() > 0) {
|
||||
val locations = List(locationsArray.length()) {
|
||||
locationsArray.getString(it)
|
||||
}
|
||||
Log.d(TAG, "navController.NavPatrol() called with locations: $locations")
|
||||
navController.NavPatrol(locations)
|
||||
} else {
|
||||
Log.w(TAG, "Patrol command received without locations, falling back to random patrol.")
|
||||
navController.randomPatrol()
|
||||
}
|
||||
}
|
||||
}
|
||||
"reception" -> {
|
||||
val location = obj.optString("location", "前台")
|
||||
@@ -230,9 +252,7 @@ class MqttManager(
|
||||
}
|
||||
|
||||
private fun stopTts() {
|
||||
// Clear buffer
|
||||
speechBuffer.setLength(0)
|
||||
|
||||
scope.launch(Dispatchers.Main) {
|
||||
ttsQueue.clear()
|
||||
isTtsBusy = false
|
||||
@@ -254,7 +274,6 @@ class MqttManager(
|
||||
private fun speak(text: String, langCode: String?) {
|
||||
val content = text.trim()
|
||||
if (content.isEmpty()) {
|
||||
// Log.w(TAG, "Speak ignored: empty text") // Too noisy for stream?
|
||||
return
|
||||
}
|
||||
val language = resolveLanguage(langCode)
|
||||
|
||||
@@ -5,6 +5,13 @@ import com.robotemi.sdk.Robot
|
||||
|
||||
class NavController(private val robot: Robot) {
|
||||
private val TAG = "NavController"
|
||||
private var playmode = false
|
||||
|
||||
fun charge(): Boolean {
|
||||
playmode = !playmode
|
||||
robot.goTo("home base", playmode)
|
||||
return true
|
||||
}
|
||||
|
||||
fun goTo(location: String, backwards: Boolean = false): Boolean {
|
||||
robot.goTo(location, backwards)
|
||||
@@ -20,8 +27,12 @@ class NavController(private val robot: Robot) {
|
||||
return robot.locations
|
||||
}
|
||||
|
||||
fun patrol(locations: List<String>, nonStop: Boolean = false, times: Int = 1, waiting: Int = 3) {
|
||||
robot.patrol(locations, nonStop, times, waiting)
|
||||
fun NavPatrol(locations: List<String>, nonStop: Boolean = false, times: Int = 1, waiting: Int = 3) {
|
||||
try {
|
||||
robot.patrol(locations, nonStop, times, waiting)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "patrol() command failed.", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun repose(): Boolean {
|
||||
@@ -31,12 +42,14 @@ class NavController(private val robot: Robot) {
|
||||
|
||||
fun randomPatrol() {
|
||||
val allLocations = getAllLocations()
|
||||
if (allLocations.size < 3) {
|
||||
val availablePatrolLocations = allLocations.filter { !it.equals("home base", ignoreCase = true) }
|
||||
if (availablePatrolLocations.size < 3) {
|
||||
Log.w(TAG, "Patrol command ignored: Not enough valid locations (excluding home base). Need at least 3, but found ${availablePatrolLocations.size}.")
|
||||
return
|
||||
}
|
||||
val patrolCount = (3..minOf(6, allLocations.size)).random()
|
||||
val patrolLocations = allLocations.shuffled().take(patrolCount)
|
||||
|
||||
patrol(patrolLocations, false, 1, 3)
|
||||
val patrolCount = (3..minOf(6, availablePatrolLocations.size)).random()
|
||||
val patrolLocations = availablePatrolLocations.shuffled().take(patrolCount)
|
||||
Log.i(TAG, "Starting random patrol with $patrolCount locations: ${patrolLocations.joinToString()}.")
|
||||
NavPatrol(patrolLocations, false, 1, 5)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.example.lzwcai_terminal_temi
|
||||
|
||||
import android.util.Log
|
||||
import com.robotemi.sdk.Robot
|
||||
import com.robotemi.sdk.permission.Permission
|
||||
|
||||
class PermissionManager(private val robot: Robot) {
|
||||
|
||||
private val TAG = "PermissionManager"
|
||||
|
||||
companion object {
|
||||
const val REQUEST_CODE_TEMI_PERMISSIONS = 101
|
||||
}
|
||||
|
||||
private val requiredPermissions = listOf(
|
||||
Permission.MAP,
|
||||
Permission.SEQUENCE,
|
||||
Permission.FACE_RECOGNITION,
|
||||
Permission.SETTINGS
|
||||
)
|
||||
|
||||
fun checkAndRequestPermissions() {
|
||||
|
||||
val neededPermissions = requiredPermissions.filter {
|
||||
robot.checkSelfPermission(it) != Permission.GRANTED
|
||||
}
|
||||
|
||||
if (neededPermissions.isNotEmpty()) {
|
||||
Log.i(TAG, "Requesting Temi permissions: $neededPermissions")
|
||||
robot.requestPermissions(neededPermissions, REQUEST_CODE_TEMI_PERMISSIONS)
|
||||
} else {
|
||||
Log.i(TAG, "All required Temi permissions are already granted.")
|
||||
}
|
||||
}
|
||||
}
|
||||
100
test.py
Normal file
100
test.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import paho.mqtt.client as mqtt
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import random
|
||||
|
||||
# --- MQTT 配置 ---
|
||||
# 重要:请将这里替换为您的 MQTT 代理的 IP 地址
|
||||
BROKER_IP = "192.168.2.236"
|
||||
BROKER_PORT = 1883
|
||||
MQTT_TOPIC = "robot/cmd"
|
||||
MQTT_USERNAME = "lzwc"
|
||||
MQTT_PASSWORD = "Lzwc@4187."
|
||||
|
||||
# --- 要进行流式传输的文本 ---
|
||||
# 您可以更改此文本
|
||||
TEXT_TO_STREAM = "你好,我是一个先进的人工智能助理。我的设计目标是理解并生成自然语言,从而能够与人类进行流畅的对话。我可以回答问题、提供信息、撰写文章,甚至进行一些基础的编程任务。这个流式传输演示旨在展示我如何将长篇回复分解成小的数据块,并实时发送给客户端,从而创造出一种更具互动性和即时性的体验。希望这次演示能够清晰地展示我的能力。"
|
||||
|
||||
# --- 流媒体参数 ---
|
||||
# 每个数据块中发送的字符长度范围
|
||||
MIN_CHUNK_SIZE = 2
|
||||
MAX_CHUNK_SIZE = 8
|
||||
# 块之间的延迟(秒)
|
||||
STREAM_DELAY = 0.1
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
"""客户端连接到代理时的回调。"""
|
||||
if rc == 0:
|
||||
print("成功连接到 MQTT 代理!")
|
||||
else:
|
||||
print(f"连接失败, 返回码 {rc}\n")
|
||||
sys.exit(1)
|
||||
|
||||
def on_publish(client, userdata, mid):
|
||||
"""消息发布时的回调。"""
|
||||
# 默认注释掉,因为可能会产生大量输出
|
||||
# print(f"已发布消息,mid: {mid}")
|
||||
pass
|
||||
|
||||
def create_mqtt_client():
|
||||
"""创建并配置 MQTT 客户端。"""
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
|
||||
client.on_connect = on_connect
|
||||
client.on_publish = on_publish
|
||||
return client
|
||||
|
||||
def stream_text(client, text_to_stream):
|
||||
"""将给定的文本以数据块的形式流式传输到 MQTT 主题。"""
|
||||
print(f"开始向主题 '{MQTT_TOPIC}' 流式传输文本:")
|
||||
print(f"文本: {text_to_stream}")
|
||||
|
||||
index = 0
|
||||
while index < len(text_to_stream):
|
||||
# 生成一个随机的块大小
|
||||
chunk_size = random.randint(MIN_CHUNK_SIZE, MAX_CHUNK_SIZE)
|
||||
# 获取块,确保不会超出文本长度
|
||||
chunk = text_to_stream[index:index + chunk_size]
|
||||
index += len(chunk)
|
||||
|
||||
# 创建 JSON 负载
|
||||
payload = {
|
||||
"action": "stream",
|
||||
"text": chunk,
|
||||
"lang": "zh" # 假设是中文,如果需要可以更改
|
||||
}
|
||||
payload_json = json.dumps(payload, ensure_ascii=False)
|
||||
|
||||
# 发布消息
|
||||
result = client.publish(MQTT_TOPIC, payload_json)
|
||||
|
||||
# 检查发布是否成功
|
||||
if result.rc != mqtt.MQTT_ERR_SUCCESS:
|
||||
print(f"发送消息失败: {mqtt.error_string(result.rc)}")
|
||||
else:
|
||||
print(f"已发送块: '{chunk}'")
|
||||
|
||||
# 等待一小段时间以模拟流式传输
|
||||
time.sleep(STREAM_DELAY)
|
||||
|
||||
print("\n流式传输完成。")
|
||||
|
||||
def main():
|
||||
"""运行 MQTT 流式脚本的主函数。"""
|
||||
client = create_mqtt_client()
|
||||
|
||||
try:
|
||||
client.connect(BROKER_IP, BROKER_PORT)
|
||||
except Exception as e:
|
||||
print(f"连接到代理 {BROKER_IP}:{BROKER_PORT} 时出错。请检查 IP 地址。")
|
||||
print(f"错误: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
stream_text(client, TEXT_TO_STREAM)
|
||||
|
||||
client.disconnect()
|
||||
print("已从 MQTT 代理断开。")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user