2025-12-04 15:04:46 +08:00
|
|
|
|
package com.cmvr.test.service;
|
|
|
|
|
|
|
2025-12-08 14:51:58 +08:00
|
|
|
|
import cn.hutool.http.HttpRequest;
|
2025-12-17 18:43:18 +08:00
|
|
|
|
import cn.hutool.http.HttpUtil;
|
2025-12-09 17:30:08 +08:00
|
|
|
|
import com.alibaba.fastjson2.JSON;
|
|
|
|
|
|
import com.alibaba.fastjson2.JSONArray;
|
2025-12-08 14:51:58 +08:00
|
|
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
|
|
|
|
import com.cmvr.common.exception.GlobalException;
|
2025-12-05 17:04:51 +08:00
|
|
|
|
import com.cmvr.common.utils.http.CallAPIUtil;
|
2025-12-08 14:51:58 +08:00
|
|
|
|
import com.cmvr.common.utils.uuid.IdUtils;
|
2025-12-16 14:02:24 +08:00
|
|
|
|
import com.cmvr.edge.client.model.EdgeCommonVO;
|
|
|
|
|
|
import com.cmvr.edge.client.model.humanoid.EdgeMoveJVO;
|
2025-12-04 15:04:46 +08:00
|
|
|
|
import com.cmvr.edge.client.service.EdgeBioHeadService;
|
2025-12-16 14:02:24 +08:00
|
|
|
|
import com.cmvr.edge.client.service.EdgeHumanoidRobotService;
|
2025-12-04 15:04:46 +08:00
|
|
|
|
import com.cmvr.test.enums.FlowiseActionEnum;
|
2025-12-09 09:38:08 +08:00
|
|
|
|
import com.cmvr.test.flow.context.TaskContext;
|
|
|
|
|
|
import com.cmvr.test.flow.context.TaskContextManager;
|
|
|
|
|
|
import com.cmvr.test.flow.control.FlowControlService;
|
2025-12-17 18:43:18 +08:00
|
|
|
|
import com.cmvr.test.flow.runtime.engine.FlowTaskRuntimeService;
|
2025-12-04 15:04:46 +08:00
|
|
|
|
import com.cmvr.test.model.vo.FlowiseActionRequestVO;
|
2025-12-08 15:51:26 +08:00
|
|
|
|
import com.cmvr.test.model.vo.FlowiseChatRequestVO;
|
|
|
|
|
|
import com.cmvr.test.model.vo.FlowiseStartRequestVO;
|
2025-12-04 15:04:46 +08:00
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
2025-12-08 14:51:58 +08:00
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
2025-12-04 15:04:46 +08:00
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
2025-12-08 14:51:58 +08:00
|
|
|
|
import javax.annotation.Resource;
|
2025-12-16 14:02:24 +08:00
|
|
|
|
import java.util.Collections;
|
2025-12-05 17:04:51 +08:00
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
import java.util.Map;
|
2025-12-16 14:02:24 +08:00
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
2025-12-05 17:04:51 +08:00
|
|
|
|
|
2025-12-04 15:04:46 +08:00
|
|
|
|
@Slf4j
|
|
|
|
|
|
@Service
|
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
|
public class FlowiseActionService {
|
2025-12-08 14:51:58 +08:00
|
|
|
|
|
2025-12-04 15:04:46 +08:00
|
|
|
|
private final EdgeBioHeadService edgeBioHeadService;
|
2025-12-16 14:02:24 +08:00
|
|
|
|
private final EdgeHumanoidRobotService edgeHumanoidRobotService;
|
2025-12-09 09:38:08 +08:00
|
|
|
|
private final TaskContextManager taskContextManager;
|
|
|
|
|
|
private final FlowControlService flowControlService;
|
2025-12-17 18:43:18 +08:00
|
|
|
|
private final FlowTaskRuntimeService flowTaskRuntimeService;
|
2025-12-04 15:04:46 +08:00
|
|
|
|
|
2025-12-08 14:51:58 +08:00
|
|
|
|
@Value("${flowise.tts}")
|
|
|
|
|
|
private String flowiseTts;
|
|
|
|
|
|
|
2025-12-09 17:30:08 +08:00
|
|
|
|
@Value("${flowise.query}")
|
|
|
|
|
|
private String flowiseQuery;
|
|
|
|
|
|
|
2025-12-08 14:51:58 +08:00
|
|
|
|
@Value("${flowise.start}")
|
|
|
|
|
|
private String flowiseStart;
|
|
|
|
|
|
|
|
|
|
|
|
@Value("${flowise.abort}")
|
|
|
|
|
|
private String flowiseAbort;
|
|
|
|
|
|
|
|
|
|
|
|
@Value("${flowise.api-key}")
|
|
|
|
|
|
private String flowiseApiKey;
|
|
|
|
|
|
|
|
|
|
|
|
@Resource(name = "threadPoolTaskExecutor")
|
|
|
|
|
|
private final ThreadPoolTaskExecutor executor;
|
|
|
|
|
|
|
2025-12-16 14:02:24 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 整个动作是否在运行(张嘴 + 摆头)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private final AtomicBoolean actionRunning = new AtomicBoolean(true);
|
|
|
|
|
|
|
2025-12-08 14:51:58 +08:00
|
|
|
|
|
|
|
|
|
|
private final Object lock = new Object();
|
|
|
|
|
|
private String chatId = null;
|
|
|
|
|
|
|
2025-12-08 15:51:26 +08:00
|
|
|
|
public String start(FlowiseStartRequestVO request) {
|
2025-12-12 15:20:22 +08:00
|
|
|
|
log.info("flowise start: {}", System.currentTimeMillis());
|
2025-12-09 17:30:08 +08:00
|
|
|
|
abort();
|
2025-12-08 14:51:58 +08:00
|
|
|
|
synchronized (lock) {
|
|
|
|
|
|
if (chatId != null) {
|
2025-12-08 15:51:26 +08:00
|
|
|
|
throw new GlobalException("Flow is already running: " + chatId);
|
2025-12-08 14:51:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
chatId = IdUtils.fastUUID();
|
|
|
|
|
|
|
|
|
|
|
|
JSONObject body = new JSONObject();
|
2025-12-08 15:51:26 +08:00
|
|
|
|
body.put("question", request.getQuestion());
|
2025-12-08 14:51:58 +08:00
|
|
|
|
body.put("chatId", chatId);
|
|
|
|
|
|
body.put("streaming", true);
|
|
|
|
|
|
|
|
|
|
|
|
executor.submit(() -> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
HttpRequest.post(flowiseStart)
|
|
|
|
|
|
.header("Content-Type", "application/json")
|
2025-12-09 17:30:08 +08:00
|
|
|
|
.header("Authorization", "Bearer " + flowiseApiKey)
|
2025-12-08 14:51:58 +08:00
|
|
|
|
.body(body.toJSONString())
|
|
|
|
|
|
.timeout(0)
|
|
|
|
|
|
.execute()
|
|
|
|
|
|
.body();
|
|
|
|
|
|
} catch (Exception e) {
|
2025-12-08 15:51:26 +08:00
|
|
|
|
throw new GlobalException("flowise 流程执行失败: ", e.getMessage());
|
2025-12-08 14:51:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return chatId;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public String abort() {
|
|
|
|
|
|
CallAPIUtil.doPostJson(flowiseTts + "stop", null, null);
|
2025-12-09 09:38:08 +08:00
|
|
|
|
Map<String, TaskContext> instContextMap = taskContextManager.getInstContextMap();
|
2025-12-12 15:20:22 +08:00
|
|
|
|
instContextMap.forEach((key, value) -> flowControlService.stop(key));
|
2025-12-08 14:51:58 +08:00
|
|
|
|
synchronized (lock) {
|
|
|
|
|
|
|
|
|
|
|
|
if (chatId == null) {
|
2025-12-09 17:30:08 +08:00
|
|
|
|
return "No flow running.";
|
2025-12-08 14:51:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
String id = chatId;
|
|
|
|
|
|
|
|
|
|
|
|
String result = HttpRequest.put(flowiseAbort + id)
|
|
|
|
|
|
.header("Authorization", "Bearer " + flowiseApiKey)
|
|
|
|
|
|
.execute()
|
|
|
|
|
|
.body();
|
|
|
|
|
|
|
|
|
|
|
|
chatId = null;
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 19:39:36 +08:00
|
|
|
|
private boolean acOn = false;
|
|
|
|
|
|
private boolean musicOn = false;
|
|
|
|
|
|
|
2025-12-05 17:04:51 +08:00
|
|
|
|
public String command(FlowiseActionRequestVO request) {
|
2025-12-12 15:20:22 +08:00
|
|
|
|
log.info("command start: {}", System.currentTimeMillis());
|
2025-12-04 15:04:46 +08:00
|
|
|
|
String action = request.getAction();
|
|
|
|
|
|
FlowiseActionEnum actionEnum = FlowiseActionEnum.fromAction(action);
|
2025-12-08 15:51:26 +08:00
|
|
|
|
String terminalId = "4ed1246c465b97975f96c9ef8371a3bd";
|
|
|
|
|
|
String bioId = "bio_head";
|
2025-12-04 15:04:46 +08:00
|
|
|
|
switch (actionEnum) {
|
|
|
|
|
|
// ==== 表情 ====
|
|
|
|
|
|
case SMILE:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
log.info("高兴");
|
2025-12-05 17:04:51 +08:00
|
|
|
|
return edgeBioHeadService.specialExpression(terminalId, bioId, 1);
|
2025-12-04 15:04:46 +08:00
|
|
|
|
case SAD:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
log.info("悲伤");
|
2025-12-05 17:04:51 +08:00
|
|
|
|
return edgeBioHeadService.specialExpression(terminalId, bioId, 4);
|
2025-12-17 18:43:18 +08:00
|
|
|
|
case SURPRISED:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
log.info("惊讶");
|
2025-12-17 18:43:18 +08:00
|
|
|
|
return edgeBioHeadService.specialExpression(terminalId, bioId, 2);
|
|
|
|
|
|
case TIRED:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
log.info("累了");
|
2025-12-05 17:04:51 +08:00
|
|
|
|
return edgeBioHeadService.specialExpression(terminalId, bioId, 5);
|
2025-12-04 15:04:46 +08:00
|
|
|
|
|
2025-12-05 17:04:51 +08:00
|
|
|
|
// ==== 动作 ====
|
2025-12-17 18:43:18 +08:00
|
|
|
|
case AC_ON:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
if (!acOn) {
|
|
|
|
|
|
log.info("空调打开了");
|
|
|
|
|
|
HttpUtil.post("http://192.168.0.123:8000/action/air", "");
|
|
|
|
|
|
acOn = true;
|
|
|
|
|
|
return "空调打开了";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 已经是开着的
|
|
|
|
|
|
speakStatus("空调是开着的呢");
|
|
|
|
|
|
return "空调已经打开";
|
|
|
|
|
|
}
|
2025-12-17 18:43:18 +08:00
|
|
|
|
|
|
|
|
|
|
case AC_OFF:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
if (acOn) {
|
|
|
|
|
|
log.info("空调关闭了");
|
|
|
|
|
|
HttpUtil.post("http://192.168.0.123:8000/action/air", "");
|
|
|
|
|
|
acOn = false;
|
|
|
|
|
|
return "空调关闭了";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
speakStatus("空调已经是关着的呢");
|
|
|
|
|
|
return "空调已经关闭";
|
|
|
|
|
|
}
|
2025-12-17 18:43:18 +08:00
|
|
|
|
|
|
|
|
|
|
case MUSIC_ON:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
if (!musicOn) {
|
|
|
|
|
|
log.info("音乐打开了");
|
|
|
|
|
|
HttpUtil.post("http://192.168.0.123:8000/action/music", "");
|
|
|
|
|
|
musicOn = true;
|
|
|
|
|
|
return "音乐打开了";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
speakStatus("音乐是开着的呢");
|
|
|
|
|
|
return "音乐已经打开";
|
|
|
|
|
|
}
|
2025-12-17 18:43:18 +08:00
|
|
|
|
|
|
|
|
|
|
case MUSIC_OFF:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
if (musicOn) {
|
|
|
|
|
|
log.info("音乐关闭了");
|
|
|
|
|
|
HttpUtil.post("http://192.168.0.123:8000/action/music", "");
|
|
|
|
|
|
musicOn = false;
|
|
|
|
|
|
return "音乐关闭了";
|
|
|
|
|
|
} else {
|
|
|
|
|
|
speakStatus("音乐已经是关着的呢");
|
|
|
|
|
|
return "音乐已经关闭";
|
|
|
|
|
|
}
|
2025-12-17 18:43:18 +08:00
|
|
|
|
|
2025-12-18 19:39:36 +08:00
|
|
|
|
case DRIVE_MODE_ECONOMY:
|
|
|
|
|
|
HttpUtil.post("http://192.168.0.123:8000/action/drive_economy", "");
|
|
|
|
|
|
log.info("驾驶模式已切换为节能模式");
|
|
|
|
|
|
return "驾驶模式已切换为节能模式";
|
2025-12-17 18:43:18 +08:00
|
|
|
|
|
|
|
|
|
|
case DRIVE_MODE_COMFORT:
|
2025-12-18 19:39:36 +08:00
|
|
|
|
HttpUtil.post("http://192.168.0.123:8000/action/drive_comfort", "");
|
2025-12-17 18:43:18 +08:00
|
|
|
|
log.info("驾驶模式已切换为舒适模式");
|
|
|
|
|
|
return "驾驶模式已切换为舒适模式";
|
2025-12-04 15:04:46 +08:00
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new UnsupportedOperationException("未实现的 Flowise Action: " + action);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 17:04:51 +08:00
|
|
|
|
|
2025-12-08 15:51:26 +08:00
|
|
|
|
public String chat(FlowiseChatRequestVO requestVO) {
|
2025-12-12 15:20:22 +08:00
|
|
|
|
log.info("chat start: {}", System.currentTimeMillis());
|
2025-12-08 15:51:26 +08:00
|
|
|
|
Map<String, String> body = new HashMap<>();
|
|
|
|
|
|
body.put("text", requestVO.getText());
|
|
|
|
|
|
body.put("voice", "x4_yezi");
|
|
|
|
|
|
|
|
|
|
|
|
CallAPIUtil.doPostJson(flowiseTts + "play", null, body);
|
2025-12-05 17:04:51 +08:00
|
|
|
|
return "ok";
|
|
|
|
|
|
}
|
2025-12-09 17:30:08 +08:00
|
|
|
|
|
|
|
|
|
|
public JSONObject query(String chatId) {
|
|
|
|
|
|
String body = HttpRequest.get(flowiseQuery + chatId)
|
|
|
|
|
|
.header("Authorization", "Bearer " + flowiseApiKey)
|
|
|
|
|
|
.execute()
|
|
|
|
|
|
.body();
|
|
|
|
|
|
JSONObject root = JSON.parseObject(body);
|
|
|
|
|
|
String execStr = root.getString("executionData");
|
|
|
|
|
|
JSONArray execJson = JSON.parseArray(execStr);
|
|
|
|
|
|
root.put("executionData", execJson);
|
|
|
|
|
|
|
|
|
|
|
|
return root;
|
|
|
|
|
|
}
|
2025-12-16 14:02:24 +08:00
|
|
|
|
|
|
|
|
|
|
public String actionStart() {
|
|
|
|
|
|
|
|
|
|
|
|
// 防止重复启动
|
|
|
|
|
|
if (!actionRunning.compareAndSet(false, true)) {
|
|
|
|
|
|
log.warn("action 已在运行中");
|
|
|
|
|
|
return "already running";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 17:17:51 +08:00
|
|
|
|
String terminalId = "4ed1246c465b97975f96c9ef8371a3bd";
|
2025-12-16 14:02:24 +08:00
|
|
|
|
String bioId = "hc01";
|
|
|
|
|
|
String spkId = "spk1";
|
|
|
|
|
|
|
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
|
EdgeCommonVO speakVO = new EdgeCommonVO();
|
|
|
|
|
|
speakVO.setTerminalId(terminalId);
|
|
|
|
|
|
speakVO.setDeviceId(spkId);
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 张嘴
|
2025-12-18 17:17:51 +08:00
|
|
|
|
edgeBioHeadService.speakStart(speakVO);
|
2025-12-16 14:02:24 +08:00
|
|
|
|
log.info("张嘴动作触发完成");
|
|
|
|
|
|
|
2025-12-18 17:17:51 +08:00
|
|
|
|
// // 摆头循环
|
|
|
|
|
|
// boolean toRight = true;
|
|
|
|
|
|
//
|
|
|
|
|
|
// while (actionRunning.get()) {
|
|
|
|
|
|
//
|
|
|
|
|
|
// double rad = toRight ? 0.1d : -0.1d;
|
|
|
|
|
|
//
|
|
|
|
|
|
// EdgeMoveJVO moveJVO = buildHeadMoveJVO(
|
|
|
|
|
|
// terminalId,
|
|
|
|
|
|
// bioId,
|
|
|
|
|
|
// rad
|
|
|
|
|
|
// );
|
|
|
|
|
|
//
|
|
|
|
|
|
// long t0 = System.currentTimeMillis();
|
|
|
|
|
|
// log.info(">>> moveJ start, rad={},time={}", rad, t0);
|
|
|
|
|
|
// String res = edgeHumanoidRobotService.moveJ(moveJVO);
|
|
|
|
|
|
//
|
|
|
|
|
|
// long t1 = System.currentTimeMillis();
|
|
|
|
|
|
// log.info(">>> moveJ end, rad={}, cost={}", rad, t1 - t0);
|
|
|
|
|
|
//
|
|
|
|
|
|
// // 判断返回
|
|
|
|
|
|
// try {
|
|
|
|
|
|
// JSONObject resJson = JSON.parseObject(res);
|
|
|
|
|
|
// if (!resJson.getBooleanValue("success")) {
|
|
|
|
|
|
// log.warn("moveJ 返回失败,res={}", res);
|
|
|
|
|
|
// }
|
|
|
|
|
|
// } catch (Exception ignore) {
|
|
|
|
|
|
// }
|
|
|
|
|
|
//
|
|
|
|
|
|
// toRight = !toRight;
|
|
|
|
|
|
// }
|
2025-12-16 14:02:24 +08:00
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("action 执行异常", e);
|
|
|
|
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
// 回正
|
2025-12-18 17:17:51 +08:00
|
|
|
|
// try {
|
|
|
|
|
|
// EdgeMoveJVO resetVO = buildHeadMoveJVO(
|
|
|
|
|
|
// terminalId,
|
|
|
|
|
|
// bioId,
|
|
|
|
|
|
// 0d
|
|
|
|
|
|
// );
|
|
|
|
|
|
// log.info("结束动作,头部回正 rad=0");
|
|
|
|
|
|
// edgeHumanoidRobotService.moveJ(resetVO);
|
|
|
|
|
|
// } catch (Exception e) {
|
|
|
|
|
|
// log.error("头部回正异常", e);
|
|
|
|
|
|
// }
|
2025-12-16 14:02:24 +08:00
|
|
|
|
|
|
|
|
|
|
// 停止张嘴
|
|
|
|
|
|
try {
|
2025-12-18 17:17:51 +08:00
|
|
|
|
edgeBioHeadService.speakStop(speakVO);
|
2025-12-16 14:02:24 +08:00
|
|
|
|
log.info("speakStop 已调用");
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
log.error("speakStop 调用失败", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
actionRunning.set(false);
|
|
|
|
|
|
log.info("action 线程结束");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return "ok";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public String actionStop() {
|
|
|
|
|
|
if (!actionRunning.get()) {
|
|
|
|
|
|
return "not running";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.info("actionStop 调用,准备停止所有动作");
|
|
|
|
|
|
actionRunning.set(false);
|
|
|
|
|
|
return "ok";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private EdgeMoveJVO buildHeadMoveJVO(String terminalId,
|
|
|
|
|
|
String deviceId,
|
|
|
|
|
|
double rad) {
|
|
|
|
|
|
|
|
|
|
|
|
EdgeMoveJVO vo = new EdgeMoveJVO();
|
|
|
|
|
|
vo.setTerminalId(terminalId);
|
|
|
|
|
|
vo.setDeviceId(deviceId);
|
|
|
|
|
|
vo.setAcc(1d);
|
|
|
|
|
|
vo.setVel(1d);
|
|
|
|
|
|
|
|
|
|
|
|
EdgeMoveJVO.JointCmd headCmd = new EdgeMoveJVO.JointCmd();
|
|
|
|
|
|
headCmd.setJointName("HEAD_R");
|
|
|
|
|
|
headCmd.setRad(rad);
|
|
|
|
|
|
|
|
|
|
|
|
vo.setCmds(Collections.singletonList(headCmd));
|
|
|
|
|
|
return vo;
|
|
|
|
|
|
}
|
2025-12-18 19:39:36 +08:00
|
|
|
|
|
|
|
|
|
|
private void speakStatus(String text) {
|
|
|
|
|
|
Map<String, String> body = new HashMap<>();
|
|
|
|
|
|
body.put("text", text);
|
|
|
|
|
|
body.put("voice", "x4_yezi");
|
|
|
|
|
|
|
|
|
|
|
|
CallAPIUtil.doPostJson(flowiseTts + "play", null, body);
|
|
|
|
|
|
}
|
2025-12-08 15:51:26 +08:00
|
|
|
|
}
|