feat: 添加接待模式和HTTP工作流集成
- 新增接待模式:机器人可前往指定位置,检测到人后显示确认按钮 - 添加HttpManager用于执行HTTP工作流,支持Home Base的人员检测触发 - 移除repose监控功能,改用OnReposeStatusChangedListener回调 - 简化界面布局,移除随机表情和说话按钮 - 在AndroidManifest中添加usesCleartextTraffic以允许HTTP通信
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Lzwcaiterminaltemi"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<meta-data
|
||||
android:name="com.robotemi.sdk.metadata.SKILL"
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.example.lzwcai_terminal_temi
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
object HttpManager {
|
||||
private const val TAG = "HttpManager"
|
||||
|
||||
suspend fun workflow_execute(context: Context, apiKey: String, workflowId: String, inputs: Any): String? = withContext(Dispatchers.IO) {
|
||||
var result: String? = null
|
||||
try {
|
||||
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
|
||||
val ip = prefs.getString("network_ip", "") ?: ""
|
||||
if (ip.isEmpty()) {
|
||||
Log.e(TAG, "No IP address configured")
|
||||
return@withContext null
|
||||
}
|
||||
val workflowUrl = "http://$ip:8088/open/workflow/execute"
|
||||
|
||||
val url = URL(workflowUrl)
|
||||
val connection = url.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.setRequestProperty("Content-Type", "application/json")
|
||||
connection.setRequestProperty("X-API-Key", apiKey)
|
||||
connection.doOutput = true
|
||||
connection.doInput = true
|
||||
connection.connectTimeout = 10000
|
||||
connection.readTimeout = 10000
|
||||
|
||||
val jsonBody = JSONObject()
|
||||
jsonBody.put("workflowId", workflowId)
|
||||
|
||||
when (inputs) {
|
||||
is Map<*, *> -> jsonBody.put("inputs", JSONObject(inputs))
|
||||
is List<*> -> jsonBody.put("inputs", JSONArray(inputs))
|
||||
is Array<*> -> jsonBody.put("inputs", JSONArray(inputs))
|
||||
else -> jsonBody.put("inputs", inputs)
|
||||
}
|
||||
|
||||
Log.d(TAG, "Request Body: $jsonBody")
|
||||
|
||||
val writer = OutputStreamWriter(connection.outputStream)
|
||||
writer.write(jsonBody.toString())
|
||||
writer.flush()
|
||||
writer.close()
|
||||
|
||||
val responseCode = connection.responseCode
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
val reader = connection.inputStream.bufferedReader()
|
||||
result = reader.readText()
|
||||
reader.close()
|
||||
Log.i(TAG, "Workflow execute success: $result")
|
||||
} else {
|
||||
val errorStream = connection.errorStream
|
||||
val errorMsg = errorStream?.bufferedReader()?.readText() ?: "Unknown error"
|
||||
Log.e(TAG, "Workflow execute failed: $responseCode - $errorMsg")
|
||||
}
|
||||
connection.disconnect()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Workflow execute exception", e)
|
||||
}
|
||||
return@withContext result
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,25 @@ import com.example.lzwcai_terminal_temi.databinding.ActivityMainBinding
|
||||
import com.robotemi.sdk.Robot
|
||||
import com.robotemi.sdk.TtsRequest
|
||||
import com.robotemi.sdk.Robot.TtsListener
|
||||
import com.robotemi.sdk.listeners.OnDetectionStateChangedListener
|
||||
import com.robotemi.sdk.listeners.OnGoToLocationStatusChangedListener
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnGoToLocationStatusChangedListener,
|
||||
OnDetectionStateChangedListener, OnReposeStatusChangedListener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private lateinit var robot: Robot
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private var mqttManager: MqttManager? = null
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var navCon: NavController
|
||||
private var lastArrivalLocation: String? = null
|
||||
@@ -27,6 +38,12 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
private val fixedFaceScale = 1.0f
|
||||
private val baseFaceSizeDp = 1000f
|
||||
|
||||
// Reception mode
|
||||
private var isReceptionMode = false
|
||||
private var receptionLocation: String = ""
|
||||
private var receptionText: String = ""
|
||||
private var receptionDestination: String = ""
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -44,17 +61,17 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
}
|
||||
|
||||
binding.btnRandomExpression.setOnClickListener {
|
||||
binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.values().random()
|
||||
}
|
||||
|
||||
binding.btnSpeak.setOnClickListener {
|
||||
speakLongSentence()
|
||||
}
|
||||
|
||||
binding.animatedEmojiView.currentExpression = AnimatedEmojiView.Expression.SMILE
|
||||
applyFaceScale(fixedFaceScale)
|
||||
|
||||
binding.btnReception.setOnClickListener {
|
||||
val destination = receptionDestination
|
||||
stopReceptionMode()
|
||||
val ttsRequest = TtsRequest.create("接待任务确认,请跟我来", false, language = TtsRequest.Language.ZH_CN)
|
||||
robot.speak(ttsRequest)
|
||||
navCon.goTo(destination, false)
|
||||
}
|
||||
|
||||
updateMqttConnection()
|
||||
}
|
||||
|
||||
@@ -63,6 +80,8 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
robot.addOnRobotReadyListener(this)
|
||||
robot.addTtsListener(this)
|
||||
robot.addOnGoToLocationStatusChangedListener(this)
|
||||
robot.addOnDetectionStateChangedListener(this)
|
||||
robot.addOnReposeStatusChangedListener(this)
|
||||
prefs.registerOnSharedPreferenceChangeListener(this)
|
||||
mqttManager?.connect()
|
||||
}
|
||||
@@ -72,6 +91,8 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
robot.removeOnRobotReadyListener(this)
|
||||
robot.removeTtsListener(this)
|
||||
robot.removeOnGoToLocationStatusChangedListener(this)
|
||||
robot.removeOnDetectionStateChangedListener(this)
|
||||
robot.removeOnReposeStatusChangedListener(this)
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||
mqttManager?.disconnect()
|
||||
}
|
||||
@@ -80,6 +101,7 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
super.onDestroy()
|
||||
mqttManager?.disconnect()
|
||||
LogManager.stopLogcatListener()
|
||||
mainScope.cancel()
|
||||
Log.i("MainActivity", "All resources released on destroy.")
|
||||
}
|
||||
|
||||
@@ -89,16 +111,8 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
}
|
||||
}
|
||||
|
||||
private fun speakLongSentence() {
|
||||
val longSentence = "你好,我是一个智能机器人。我很高兴能为您服务。现在,我将为您展示我的各种表情和语音能力。希望您喜欢!"
|
||||
val ttsRequest = TtsRequest.create(longSentence, false, language = TtsRequest.Language.ZH_CN)
|
||||
robot.speak(ttsRequest)
|
||||
}
|
||||
|
||||
override fun onTtsStatusChanged(ttsRequest: TtsRequest) {
|
||||
// Forward TTS status to MqttManager for stream queue handling
|
||||
mqttManager?.handleTtsStatusChange(ttsRequest)
|
||||
|
||||
when (ttsRequest.status) {
|
||||
TtsRequest.Status.STARTED -> {
|
||||
Log.i("MainActivity", "TTS started: ${ttsRequest.speech}")
|
||||
@@ -137,6 +151,92 @@ class MainActivity : AppCompatActivity(), OnRobotReadyListener, TtsListener, OnG
|
||||
Log.i("MainActivity", "Arrived at $location, announcement sent.")
|
||||
}
|
||||
|
||||
override fun onDetectionStateChanged(state: Int) {
|
||||
// Reception mode logic
|
||||
if (isReceptionMode && lastArrivalLocation == receptionLocation) {
|
||||
when (state) {
|
||||
DETECTED -> {
|
||||
if (binding.btnReception.visibility != android.view.View.VISIBLE) {
|
||||
binding.btnReception.visibility = android.view.View.VISIBLE
|
||||
val ttsRequest = TtsRequest.create(receptionText, false, language = TtsRequest.Language.ZH_CN)
|
||||
robot.speak(ttsRequest)
|
||||
Log.i("MainActivity", "Reception: Person detected (new session) at $receptionLocation")
|
||||
}
|
||||
}
|
||||
IDLE -> {
|
||||
binding.btnReception.visibility = android.view.View.GONE
|
||||
Log.i("MainActivity", "Reception: Person left (IDLE)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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>()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startReceptionMode(location: String, text: String, destination: String) {
|
||||
isReceptionMode = true
|
||||
receptionLocation = location
|
||||
receptionText = text
|
||||
receptionDestination = destination
|
||||
Log.i("MainActivity", "Reception mode started: location=$location, text=$text, dest=$destination")
|
||||
if (lastArrivalLocation != location) {
|
||||
navCon.goTo(location, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopReceptionMode() {
|
||||
isReceptionMode = false
|
||||
receptionLocation = ""
|
||||
receptionText = ""
|
||||
receptionDestination = ""
|
||||
binding.btnReception.visibility = android.view.View.GONE
|
||||
Log.i("MainActivity", "Reception mode stopped")
|
||||
}
|
||||
|
||||
override fun onReposeStatusChanged(status: Int, description: String) {
|
||||
when (status) {
|
||||
OnReposeStatusChangedListener.REPOSING_COMPLETE -> {
|
||||
val ttsRequest = TtsRequest.create("重新定位成功", false, language = TtsRequest.Language.ZH_CN)
|
||||
robot.speak(ttsRequest)
|
||||
Log.i("MainActivity", "Repose complete")
|
||||
}
|
||||
OnReposeStatusChangedListener.REPOSING_ABORT -> {
|
||||
val ttsRequest = TtsRequest.create("重新定位被中断", false, language = TtsRequest.Language.ZH_CN)
|
||||
robot.speak(ttsRequest)
|
||||
Log.w("MainActivity", "Repose aborted")
|
||||
}
|
||||
OnReposeStatusChangedListener.REPOSING_START -> {
|
||||
val ttsRequest = TtsRequest.create("正在进行重新定位", false, language = TtsRequest.Language.ZH_CN)
|
||||
robot.speak(ttsRequest)
|
||||
Log.i("MainActivity", "Repose started")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
if (key == "network_ip") {
|
||||
Log.i("MainActivity", "IP address changed, re-initializing MQTT connection.")
|
||||
|
||||
@@ -97,7 +97,6 @@ class MqttManager(
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
robot.removeTtsListener(this)
|
||||
scope.launch {
|
||||
try {
|
||||
reconnectJob?.cancel()
|
||||
@@ -178,9 +177,7 @@ class MqttManager(
|
||||
}
|
||||
"repose" -> {
|
||||
val ok = navController.repose()
|
||||
speak("正在进行重新定位")
|
||||
Log.i(TAG, "Repose command sent: $ok")
|
||||
monitorReposeStatus()
|
||||
}
|
||||
"stop" -> {
|
||||
navController.stop()
|
||||
@@ -189,6 +186,12 @@ class MqttManager(
|
||||
"patrol" -> {
|
||||
navController.randomPatrol()
|
||||
}
|
||||
"reception" -> {
|
||||
val location = obj.optString("location", "前台")
|
||||
val text = obj.optString("text", "你是我要接待的贵宾吗?")
|
||||
val destination = obj.optString("destination", "会议室")
|
||||
(context as? MainActivity)?.startReceptionMode(location, text, destination)
|
||||
}
|
||||
else -> Log.w(TAG, "Unknown command action: $action")
|
||||
}
|
||||
}
|
||||
@@ -238,35 +241,6 @@ class MqttManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun monitorReposeStatus() {
|
||||
scope.launch {
|
||||
Log.i(TAG, "Starting repose monitoring...")
|
||||
val timeout = 30000L
|
||||
val startTime = System.currentTimeMillis()
|
||||
var lastStatus: String? = null
|
||||
|
||||
while (System.currentTimeMillis() - startTime < timeout) {
|
||||
delay(1000L)
|
||||
val status = robot.reposeStatus.toString().uppercase()
|
||||
|
||||
if (status != lastStatus) {
|
||||
Log.i(TAG, "Repose status: $status")
|
||||
lastStatus = status
|
||||
}
|
||||
|
||||
if (status == "COMPLETE") {
|
||||
speak("重新定位成功", null)
|
||||
return@launch
|
||||
} else if (status == "FAILURE" || status == "ABORTED") {
|
||||
speak("重新定位失败", null)
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "Repose monitoring timed out.")
|
||||
speak("重新定位超时", null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun goTo(location: String, backwards: Boolean = false) {
|
||||
val target = location.trim()
|
||||
if (target.isEmpty()) {
|
||||
@@ -298,7 +272,7 @@ class MqttManager(
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleTtsStatusChange(ttsRequest: TtsRequest) {
|
||||
fun handleTtsStatusChange(ttsRequest: TtsRequest) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
when (ttsRequest.status) {
|
||||
TtsRequest.Status.STARTED -> {
|
||||
@@ -307,7 +281,7 @@ class MqttManager(
|
||||
TtsRequest.Status.COMPLETED,
|
||||
TtsRequest.Status.CANCELED,
|
||||
TtsRequest.Status.ERROR,
|
||||
TtsRequest.Status.ABORTED -> {
|
||||
TtsRequest.Status.NOT_ALLOWED -> {
|
||||
isTtsBusy = false
|
||||
processNextTts()
|
||||
}
|
||||
|
||||
@@ -22,26 +22,16 @@
|
||||
android:layout_height="500dp"
|
||||
android:layout_centerInParent="true" />
|
||||
|
||||
<LinearLayout
|
||||
<Button
|
||||
android:id="@+id/btnReception"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="32dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRandomExpression"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/btn_random_expression" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSpeak"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/btn_speak" />
|
||||
</LinearLayout>
|
||||
android:layout_marginBottom="100dp"
|
||||
android:text="是的"
|
||||
android:textSize="24sp"
|
||||
android:padding="20dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
Reference in New Issue
Block a user