工作愉快
*实现agent ReAct与LLM chain自动切换逻辑✓ 聊天窗区分任务消息✓ 修复删除日程bug✓ 优化远程音频逻辑✓ 等待处理引入加载中效果✓ 优化prompt以解决日程任务递归调用问题✓ 修复一次性日程清除的bug✓
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
agent/tools/IotmService.py
|
26
README.md
@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
|
||||
**12月迟来的报到,Fay数字人 AI Agent版(含智慧农业箱的操作demo代码,如果你需要完整代码可以公众号留言申请获取)第4版正式上传!**
|
||||
**12月迟来的报到,Fay数字人 AI Agent版(含智慧农业箱的操作demo代码,如果你需要完整代码可以公众号留言申请获取)第5版正式上传!**
|
||||
|
||||
如果你需要是一个线上线下的销售员,请移步[`带货完整版`](https://github.com/TheRamU/Fay/tree/fay-sales-edition)
|
||||
|
||||
@ -55,9 +55,29 @@ python main.py
|
||||
|
||||
### **启动数字人(非必须)**
|
||||
|
||||
+ 启动数字人[xszyou/fay-ue5: 可对接fay数字人的ue5工程 (github.com)](https://github.com/xszyou/fay-ue5)
|
||||
+ 仓库地址:https://github.com/xszyou/fay-ue5
|
||||
|
||||
### **启动android 连接器(非必须)**
|
||||
|
||||
+ 仓库地址:https://github.com/xszyou/fay-android
|
||||
|
||||
### **更新日志**
|
||||
2023.12.25:
|
||||
|
||||
*实现agent ReAct与LLM chain自动切换逻辑✓
|
||||
|
||||
聊天窗区分任务消息✓
|
||||
|
||||
修复删除日程bug✓
|
||||
|
||||
优化远程音频逻辑✓
|
||||
|
||||
等待处理引入加载中效果✓
|
||||
|
||||
优化prompt以解决日程任务递归调用问题✓
|
||||
|
||||
修复一次性日程清除的bug✓
|
||||
|
||||
|
||||
|
||||
### **联系**
|
||||
|
||||
|
26
README_EN.md
@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
|
||||
**Belated December announcement, the 4th edition of Fay Digital Human AI Agent Version (complete code for smart agriculture box can be requested via our public channel) is officially uploaded!**
|
||||
**Belated December announcement, the 5th edition of Fay Digital Human AI Agent Version (complete code for smart agriculture box can be requested via our public channel) is officially uploaded!**
|
||||
|
||||
If you need an online and offline salesperson, please go to [`Complete Retail Version`](https://github.com/TheRamU/Fay/tree/fay-sales-edition)
|
||||
|
||||
@ -52,9 +52,29 @@ python main.py
|
||||
|
||||
### **Launching the Digital Human (Optional)**
|
||||
|
||||
+ Launch the digital human[xszyou/fay-ue5: 可对接fay数字人的ue5工程 (github.com)](https://github.com/xszyou/fay-ue5)
|
||||
Repository URL:https://github.com/xszyou/fay-ue5
|
||||
|
||||
|
||||
### **Launch of Android Connector (Optional)**
|
||||
Repository URL: https://github.com/xszyou/fay-android
|
||||
|
||||
|
||||
### **Changelog**
|
||||
2023.12.25:
|
||||
|
||||
Implemented the automatic switching logic between agent ReAct and LLM chain ✓
|
||||
Distinguished task messages in the chat window ✓
|
||||
|
||||
Fixed the bug in deleting schedules ✓
|
||||
|
||||
Optimized remote audio logic ✓
|
||||
|
||||
Introduced loading effects for pending processes ✓
|
||||
|
||||
Optimized prompts to resolve recursive calling issues in schedule tasks ✓
|
||||
|
||||
Fixed the bug in clearing one-time schedules ✓
|
||||
|
||||
|
||||
|
||||
### **Contact**
|
||||
|
||||
|
@ -8,6 +8,7 @@ scheduled_tasks = {}
|
||||
agent_running = False
|
||||
agent = FayAgentCore()
|
||||
|
||||
|
||||
# 数据库初始化
|
||||
def init_db():
|
||||
conn = sqlite3.connect('timer.db')
|
||||
@ -51,8 +52,7 @@ def parse_repeat_rule(rule, task_time):
|
||||
|
||||
# 执行任务
|
||||
def execute_task(task_time, id, content):
|
||||
content = "执行任务->现在" + content
|
||||
agent.run(content)
|
||||
agent.run("执行任务->立刻" + content)
|
||||
del scheduled_tasks[id]
|
||||
# 如果不重复,执行后删除记录
|
||||
conn = sqlite3.connect('timer.db')
|
||||
@ -90,7 +90,6 @@ def agent_start():
|
||||
|
||||
agent_running = True
|
||||
init_db()
|
||||
# insert_test_data()
|
||||
check_and_execute_thread = threading.Thread(target=check_and_execute)
|
||||
check_and_execute_thread.start()
|
||||
|
||||
|
@ -6,6 +6,7 @@ from langchain.docstore import InMemoryDocstore
|
||||
from langchain.vectorstores import FAISS
|
||||
from langchain.agents import AgentExecutor, Tool, ZeroShotAgent, initialize_agent, agent_types
|
||||
from langchain.chains import LLMChain
|
||||
from langchain.prompts import PromptTemplate
|
||||
|
||||
from agent.tools.MyTimer import MyTimer
|
||||
from agent.tools.QueryTime import QueryTime
|
||||
@ -19,6 +20,8 @@ from agent.tools.QueryTimerDB import QueryTimerDB
|
||||
from agent.tools.DeleteTimer import DeleteTimer
|
||||
from agent.tools.GetSwitchLog import GetSwitchLog
|
||||
from agent.tools.getOnRunLinkage import getOnRunLinkage
|
||||
from agent.tools.SetChatStatus import SetChatStatus
|
||||
|
||||
|
||||
import utils.config_util as utils
|
||||
from core.content_db import Content_Db
|
||||
@ -26,6 +29,7 @@ from core import wsa_server
|
||||
import os
|
||||
|
||||
|
||||
|
||||
class FayAgentCore():
|
||||
def __init__(self):
|
||||
|
||||
@ -64,6 +68,7 @@ class FayAgentCore():
|
||||
delete_timer_tool = DeleteTimer()
|
||||
get_switch_log = GetSwitchLog()
|
||||
get_on_run_linkage = getOnRunLinkage()
|
||||
set_chat_status_tool = SetChatStatus()
|
||||
|
||||
tools = [
|
||||
Tool(
|
||||
@ -126,22 +131,57 @@ class FayAgentCore():
|
||||
func=get_on_run_linkage.run,
|
||||
description=get_on_run_linkage.description
|
||||
),
|
||||
Tool(
|
||||
name=set_chat_status_tool.name,
|
||||
func=set_chat_status_tool.run,
|
||||
description=set_chat_status_tool.description
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
#agent用于执行任务
|
||||
self.agent = initialize_agent(agent_types=agent_types.AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION,
|
||||
tools=tools, llm=llm, verbose=True,
|
||||
max_history=5,
|
||||
memory=memory, handle_parsing_errors=True)
|
||||
|
||||
#创建llm chain用于聊天
|
||||
tools_prompt = "["
|
||||
tool_names = [tool.name for tool in tools if tool.name != set_chat_status_tool.name and tool.name != say_tool.name]
|
||||
tools_prompt += "、".join(tool_names) + "]"
|
||||
template = """你是一个智慧农业实验箱里的ai,你的责任是陪伴主人生活、工作,以及协助主人打理好农业种植箱里的农作物.现在主人正在和你聊天,若你在聊天过程中感觉到主人想使用以下工具,请按“agent:'主人刚刚的说话'”这样的格式回复,否则请直接回复我文字内容。工具如下:
|
||||
""" + tools_prompt +"""
|
||||
{chat_history}
|
||||
Human: {human_input}
|
||||
AI:"""
|
||||
prompt = PromptTemplate(
|
||||
input_variables=["chat_history", "human_input"], template=template
|
||||
)
|
||||
self.llm_chain = LLMChain(
|
||||
llm=llm,
|
||||
prompt=prompt,
|
||||
verbose=True,
|
||||
memory=memory
|
||||
)
|
||||
|
||||
self.is_chat = False#聊天状态
|
||||
|
||||
|
||||
def run(self, input_text):
|
||||
#消息保存
|
||||
contentdb = Content_Db()
|
||||
contentdb.add_content('member', 'agent', input_text.replace('主人语音说了:', '').replace('主人文字说了:', ''))
|
||||
wsa_server.get_web_instance().add_cmd({"panelReply": {"type":"member","content":input_text.replace('主人语音说了:', '').replace('主人文字说了:', '')}})
|
||||
result = None
|
||||
result = ""
|
||||
try:
|
||||
result = self.agent.run(input_text)
|
||||
#判断执行聊天模式还是agent模式,双模式在运行过程中会主动切换
|
||||
if self.is_chat:
|
||||
result = self.llm_chain.predict(human_input=input_text.replace('主人语音说了:', '').replace('主人文字说了:', ''))
|
||||
if "agent:" in result.lower() or not self.is_chat:
|
||||
print(result)
|
||||
print(self.is_chat)
|
||||
self.is_chat = False
|
||||
input_text = result if result.lower().replace("agent:", "") else input_text
|
||||
result = self.agent.run(input_text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
result = "执行完毕" if result is None or result == "N/A" else result
|
||||
|
@ -2,11 +2,11 @@ import os
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import BaseTool
|
||||
import agent.tools.IotmService as IotmService
|
||||
import tools.IotmService as IotmService
|
||||
|
||||
class GetSwitchLog(BaseTool):
|
||||
name = "GetSwitchLog"
|
||||
description = "此工具用于查询农业箱的设备开关操作历史记录,设备序号:小风扇(1)、电热风扇(2)、制冷风扇(3)、肥料开关(4)、补光设备(5)、植物生长灯(6)、二氧化碳(7)"
|
||||
description = "此工具用于查询农业箱的设备开关当天的操作历史记录"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -16,10 +16,31 @@ class GetSwitchLog(BaseTool):
|
||||
pass
|
||||
|
||||
|
||||
def _run(self, para: str) -> str:
|
||||
infos = IotmService.get_switch_log()
|
||||
|
||||
return infos
|
||||
def _run(self, para: str):
|
||||
logs = IotmService.get_switch_log()
|
||||
device_logs = {}
|
||||
|
||||
switch_mapping = {
|
||||
1: '小风扇',
|
||||
2: '电热风扇',
|
||||
3: '制冷风扇',
|
||||
4: '水开关',
|
||||
5: '肥料开关',
|
||||
6: '植物生长灯',
|
||||
7: '二氧化碳'
|
||||
}
|
||||
|
||||
for val in logs:
|
||||
switch_name = switch_mapping[val['number']]
|
||||
status = 'on' if val['status'] == 1 else 'off'
|
||||
info = val['timetText']
|
||||
|
||||
if switch_name not in device_logs:
|
||||
device_logs[switch_name] = {'on': [], 'off': []}
|
||||
|
||||
device_logs[switch_name][status].append(info)
|
||||
|
||||
return device_logs
|
||||
|
||||
if __name__ == "__main__":
|
||||
tool = GetSwitchLog()
|
||||
|
@ -7,7 +7,7 @@ from langchain.tools import BaseTool
|
||||
|
||||
class Knowledge(BaseTool):
|
||||
name = "Knowledge"
|
||||
description = """此工具用于查询箱内植物的专业知识,可以作为补充参考,使用时请传入相关问题作为参数,例如:“草梅最适合的生长温度”"""
|
||||
description = """此工具用于查询箱内植物的专业知识,使用时请传入相关问题作为参数,例如:“草梅最适合的生长温度”"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -5,11 +5,12 @@ import requests
|
||||
from langchain.tools import BaseTool
|
||||
import fay_booter
|
||||
from core.interact import Interact
|
||||
from agent import agent_service
|
||||
|
||||
|
||||
class Say(BaseTool):
|
||||
name = "Say"
|
||||
description = """此工具用于沟通及提醒,使用时请传入说话内容作为参数,例如:“该下班了,请注意休息”"""
|
||||
name = "语音输出工具"
|
||||
description = """此工具用于语音输出,使用时请传入说话内容作为参数,例如:“该下班了,请注意休息”"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -20,8 +21,9 @@ class Say(BaseTool):
|
||||
|
||||
|
||||
def _run(self, para: str) -> str:
|
||||
interact = Interact("agent", 1, {'user': '', 'msg': para})
|
||||
interact = Interact("audio", 1, {'user': '', 'msg': para})
|
||||
fay_booter.feiFei.on_interact(interact)
|
||||
agent_service.agent.is_chat = True
|
||||
return "语音输出了:" + para
|
||||
|
||||
|
||||
|
29
agent/tools/SetChatStatus.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from langchain.tools import BaseTool
|
||||
from agent import agent_service
|
||||
|
||||
|
||||
class SetChatStatus(BaseTool):
|
||||
name = "SetChatStatus"
|
||||
description = """此工具用于设置聊天状态,当识别到主人想进行交流聊天时使用此工具"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
async def _arun(self, *args: Any, **kwargs: Any) -> Any:
|
||||
# 用例中没有用到 arun 不予具体实现
|
||||
pass
|
||||
|
||||
|
||||
def _run(self, para: str) -> str:
|
||||
agent_service.agent.is_chat = True
|
||||
return "设置聊天状态成功"
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tool = SetChatStatus()
|
||||
info = tool.run("该下班了,请注意休息")
|
||||
print(info)
|
@ -1,12 +1,13 @@
|
||||
import os
|
||||
from typing import Any
|
||||
from itertools import groupby
|
||||
|
||||
from langchain.tools import BaseTool
|
||||
import agent.tools.IotmService as IotmService
|
||||
import tools.IotmService as IotmService
|
||||
|
||||
class getOnRunLinkage(BaseTool):
|
||||
name = "getOnRunLinkage"
|
||||
description = "此工具用于查询农业箱当前在运行的联动,设备序号:小风扇(1)、电热风扇(2)、制冷风扇(3)、肥料开关(4)、补光设备(5)、植物生长灯(6)、二氧化碳(7)"
|
||||
description = "此工具用于查询农业箱当前在运行的联动"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@ -17,9 +18,45 @@ class getOnRunLinkage(BaseTool):
|
||||
|
||||
|
||||
def _run(self, para: str) -> str:
|
||||
infos = IotmService.get_on_run_linkage()
|
||||
return infos
|
||||
logs = IotmService.get_on_run_linkage()
|
||||
desc_list = {
|
||||
'co2_S36': '二氧化碳',
|
||||
'light_bh1': '箱内的光照强度',
|
||||
'air_S37': '污染气体',
|
||||
'nh3_S37': '氨气',
|
||||
'temperature_MP14': '箱外温度',
|
||||
'temperature_MP21': '箱内温度',
|
||||
'humidity_MP14': '箱外湿度',
|
||||
'humidity_S34': '箱内土壤的湿度',
|
||||
}
|
||||
infos = {}
|
||||
|
||||
|
||||
logs.sort(key=lambda x: (x['label'], x['port']))
|
||||
|
||||
for (sensor_type, port), group in groupby(logs, key=lambda x: (x['label'], x['port'])):
|
||||
|
||||
group_infos = []
|
||||
for val in group:
|
||||
|
||||
onoff = '开启设备开关' if val['onoff'] == 1 else '关闭设备开关'
|
||||
|
||||
info = {
|
||||
'max': val['maxVal'],
|
||||
'min': val['minVal'],
|
||||
'onoff': onoff,
|
||||
}
|
||||
if float(val['keeptime']) > 0:
|
||||
info["持续时间(若需执行开启设备,持续时间过后执行关闭),单位为分钟"] = val['keeptime']
|
||||
if float(val['delaytime']) > 0:
|
||||
info["执行后下次检查相距时间,单位为分钟"] = val['delaytime']
|
||||
|
||||
group_infos.append(info)
|
||||
|
||||
key_str = f"{sensor_type}_{port}"
|
||||
infos[desc_list.get(key_str, 'Unknown')] = group_infos
|
||||
|
||||
return infos
|
||||
if __name__ == "__main__":
|
||||
tool = getOnRunLinkage()
|
||||
info = tool.run("")
|
||||
|
@ -1,15 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
<map>
|
||||
<entry key="..\:/android/projects/fayConnectorDemo/app/src/main/res/layout/activity_main.xml" value="0.358695652173913" />
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
@ -1 +0,0 @@
|
||||
/build
|
@ -1,38 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.yaheen.fayconnectordemo"
|
||||
minSdk 29
|
||||
targetSdk 32
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -1,26 +0,0 @@
|
||||
package com.yaheen.fayconnectordemo;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.yaheen.fayconnectordemo", appContext.getPackageName());
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.yaheen.fayconnectordemo">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/><!--网络访问-->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.FayConnectorDemo"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<service android:name=".FayConnectorService" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,343 +0,0 @@
|
||||
package com.yaheen.fayconnectordemo;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
public class FayConnectorService extends Service {
|
||||
private AudioRecord record;
|
||||
private int recordBufsize = 0;
|
||||
private Socket socket = null;
|
||||
private InputStream in = null;
|
||||
private OutputStream out = null;
|
||||
public static boolean running = false;
|
||||
private File cacheDir = null;
|
||||
private String channelId = null;
|
||||
private PendingIntent pendingIntent = null;
|
||||
private NotificationManagerCompat notificationManager = null;
|
||||
private long totalrece = 0;
|
||||
private long totalsend = 0;
|
||||
private AudioManager mAudioManager = null;
|
||||
private boolean isPlay = false;
|
||||
|
||||
|
||||
//创建通知
|
||||
private String createNotificationChannel(String channelID, String channelNAME, int level) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationChannel channel = new NotificationChannel(channelID, channelNAME, level);
|
||||
manager.createNotificationChannel(channel);
|
||||
return channelID;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
super.onStartCommand(intent, START_FLAG_REDELIVERY, startId);
|
||||
return Service.START_STICKY;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.d("fay", "服务启动");
|
||||
|
||||
|
||||
|
||||
//开启蓝牙传输
|
||||
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
mAudioManager.startBluetoothSco();
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
|
||||
BroadcastReceiver receiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
|
||||
if (AudioManager.SCO_AUDIO_STATE_CONNECTED == state) {
|
||||
Log.d("fay", "蓝牙sco连接成功");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
this.registerReceiver(receiver, intentFilter);
|
||||
|
||||
running = true;
|
||||
this.cacheDir = getApplicationContext().getFilesDir();//getCacheDir();
|
||||
Thread sendThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(FayConnectorService.this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (record == null) {
|
||||
recordBufsize = AudioRecord
|
||||
.getMinBufferSize(16000,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT);
|
||||
record = new AudioRecord(MediaRecorder.AudioSource.MIC,
|
||||
16000,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
recordBufsize);
|
||||
|
||||
}
|
||||
try {
|
||||
socket = new Socket("192.168.1.101", 10001);
|
||||
in = socket.getInputStream();
|
||||
out = socket.getOutputStream();
|
||||
Log.d("fay", "fay控制器连接成功");
|
||||
} catch (IOException e) {
|
||||
Log.d("fay", "socket连接失败");
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
byte[] data = new byte[1024];
|
||||
record.startRecording();
|
||||
Log.d("fay", "麦克风启动成功");
|
||||
try {
|
||||
Log.d("fay", "开始传输音频");
|
||||
while (running) {
|
||||
if (isPlay){
|
||||
continue;
|
||||
}
|
||||
int size = record.read(data, 0, 1024);
|
||||
if (size > 0) {
|
||||
out.write(data);
|
||||
totalsend += data.length / 1024;
|
||||
}else{//录音异常,等待60秒重新录取
|
||||
try {
|
||||
Thread.sleep(60000);
|
||||
record.stop();
|
||||
record.startRecording();
|
||||
}catch (Exception e){
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) { //通过异常关退出循环
|
||||
Log.d("fay", "服务端关闭:" + e.toString());
|
||||
} finally {
|
||||
running = false;
|
||||
record.stop();
|
||||
record = null;
|
||||
((AudioManager) getSystemService(Context.AUDIO_SERVICE)).stopBluetoothSco();
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
socket = null;
|
||||
Log.d("fay", "send线程结束");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Thread receThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (running) {
|
||||
while (socket != null && !socket.isClosed()) {
|
||||
byte[] data = new byte[9];
|
||||
byte[] wavhead = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};//文件传输开始标记
|
||||
in.read(data);
|
||||
if (Arrays.equals(wavhead, data)) {
|
||||
Log.d("fay", "开始接收音频文件");
|
||||
String filedata = "";
|
||||
data = new byte[1024];
|
||||
int len = 0;
|
||||
while ((len = in.read(data)) != -1) {
|
||||
byte[] temp = new byte[len];
|
||||
System.arraycopy(data, 0, temp, 0, len);
|
||||
filedata += MainActivity.bytesToHexString(temp);
|
||||
int index = filedata.indexOf("080706050403020100");
|
||||
if (filedata.length() > 9 && index > 0) {//wav文件结束标记
|
||||
filedata = filedata.substring(0, index).replaceAll("F0F1F2F3F4F5F6F7F8", "");
|
||||
File wavFile = new File(cacheDir, String.format("sample-%s.wav", new Date().getTime() + ""));
|
||||
wavFile.createNewFile();
|
||||
FileOutputStream fos = new FileOutputStream(wavFile);
|
||||
fos.write(MainActivity.decodeHexBytes(filedata.toCharArray()));
|
||||
fos.close();
|
||||
totalrece += filedata.length() / 2 / 1024;
|
||||
Log.d("fay", "wav文件接收完成:" + wavFile.getAbsolutePath() + "," + filedata.length() / 2);
|
||||
try {
|
||||
MediaPlayer player = new MediaPlayer();
|
||||
player.setDataSource(wavFile.getAbsolutePath());
|
||||
player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
Log.d("fay", "开始播放");
|
||||
if (mAudioManager.isBluetoothScoOn()){
|
||||
mAudioManager.stopBluetoothSco();
|
||||
mAudioManager.setBluetoothScoOn(false);
|
||||
mAudioManager.setMode(mAudioManager.MODE_NORMAL);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
}catch (Exception e){
|
||||
|
||||
}
|
||||
isPlay = true;
|
||||
mp.start();
|
||||
}
|
||||
});
|
||||
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
Log.d("fay", "播放完成");
|
||||
isPlay = false;
|
||||
mp.release();
|
||||
mAudioManager.startBluetoothSco();
|
||||
mAudioManager.setMode(mAudioManager.MODE_IN_CALL);
|
||||
mAudioManager.setBluetoothScoOn(true);
|
||||
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
player.setVolume(1,1);
|
||||
player.setLooping(false);
|
||||
player.prepareAsync();
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e("fay", e.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {//通过异常判断socket已经关闭,退出循环
|
||||
|
||||
} finally {
|
||||
Log.d("fay", "rece线程结束");
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
sendThread.start();
|
||||
receThread.start();
|
||||
|
||||
//通知栏
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try{
|
||||
while (running) {
|
||||
Thread.sleep(3000);
|
||||
if (totalsend + totalrece > 2048){
|
||||
inotify("fay connector demo", "已经连接fay控制器,累计接收/发送:" + String.format("%.2f", (double)totalrece / 1024) + "/" + String.format("%.2f", (double)totalsend / 1024) + "MB");
|
||||
} else {
|
||||
inotify("fay connector demo", "已经连接fay控制器,累计接收/发送:" + totalrece + "/" + totalsend + "KB");
|
||||
}
|
||||
}
|
||||
inotify("fay connector demo", "已经断开fay控制器");
|
||||
}catch (Exception e){
|
||||
Log.e("fay", e.toString());
|
||||
}finally {
|
||||
FayConnectorService.this.stopSelf();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void inotify(String title, String content){
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
if (pendingIntent == null){
|
||||
pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||
}
|
||||
if (channelId == null){
|
||||
channelId = createNotificationChannel("my_channel_ID", "my_channel_NAME", NotificationManager.IMPORTANCE_HIGH);
|
||||
}
|
||||
if (notificationManager == null){
|
||||
notificationManager = NotificationManagerCompat.from(this);
|
||||
}
|
||||
NotificationCompat.Builder notification2 = new NotificationCompat.Builder(FayConnectorService.this, channelId)
|
||||
.setContentTitle(title)
|
||||
.setContentText(content)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setSmallIcon(R.drawable.icon)
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true);
|
||||
//notificationManager.notify(100, notification2.build());
|
||||
startForeground(100, notification2.build());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d("fay", "服务关闭");
|
||||
super.onDestroy();
|
||||
mAudioManager.stopBluetoothSco();
|
||||
running = false;
|
||||
stopForeground(true);
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package com.yaheen.fayconnectordemo;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private TextView tv = null;
|
||||
private boolean running = false;
|
||||
private Intent serviceIntent = null;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
tv = this.findViewById(R.id.tv);
|
||||
serviceIntent = new Intent(this, FayConnectorService.class);
|
||||
|
||||
//按钮点击
|
||||
tv.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Log.d("fay","onclick");
|
||||
running = FayConnectorService.running;//isServiceRunning();//同步service的运行状态,不好使!
|
||||
if (!running){//运行
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//开启
|
||||
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.RECORD_AUDIO)) {
|
||||
Log.d("fay", "用户彻底拒绝了权限");
|
||||
return;
|
||||
} else {
|
||||
// 用户未彻底拒绝授予权限
|
||||
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.RECORD_AUDIO}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d("fay","权限ok");
|
||||
|
||||
Snackbar.make(view, "正在连接fay控制器", Snackbar.LENGTH_SHORT)
|
||||
.setAction("Action", null).show();
|
||||
startForegroundService(serviceIntent);
|
||||
running = true;
|
||||
}
|
||||
}
|
||||
} else{//关闭
|
||||
stopService(serviceIntent);
|
||||
Snackbar.make(view, "已经断开fay控制器", Snackbar.LENGTH_SHORT)
|
||||
.setAction("Action", null).show();
|
||||
running = false;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static String bytesToHexString(byte[] data){
|
||||
String result="";
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
result+=Integer.toHexString((data[i] & 0xFF) | 0x100).toUpperCase().substring(1, 3);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static byte[] decodeHexBytes(char[] data) {
|
||||
int len = data.length;
|
||||
if ((len & 0x01) != 0) {
|
||||
throw new RuntimeException("未知的字符");
|
||||
}
|
||||
byte[] out = new byte[len >> 1];
|
||||
for (int i = 0, j = 0; j < len; i++) {
|
||||
int f = toDigit(data[j], j) << 4;
|
||||
j++;
|
||||
f = f | toDigit(data[j], j);
|
||||
j++;
|
||||
out[i] = (byte) (f & 0xFF);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
protected static int toDigit(char ch, int index) {
|
||||
int digit = Character.digit(ch, 16);
|
||||
if (digit == -1) {
|
||||
throw new RuntimeException("非法16进制字符 " + ch
|
||||
+ " 在索引 " + index);
|
||||
}
|
||||
return digit;
|
||||
}
|
||||
|
||||
private boolean isServiceRunning() {
|
||||
ActivityManager activityManager = (ActivityManager) this.getApplicationContext()
|
||||
.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ComponentName serviceName = new ComponentName("com.yaheen.fayconnectordemo", ".FayConnectorService");
|
||||
PendingIntent intent = activityManager.getRunningServiceControlPanel(serviceName);
|
||||
if (intent == null){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
Before Width: | Height: | Size: 13 KiB |
@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="连接/断开fay控制器"
|
||||
android:id="@+id/tv"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 7.6 KiB |
@ -1,16 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.FayConnectorDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">fayConnectorDemo</string>
|
||||
</resources>
|
@ -1,16 +0,0 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.FayConnectorDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
@ -1,17 +0,0 @@
|
||||
package com.yaheen.fayconnectordemo;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '7.2.1' apply false
|
||||
id 'com.android.library' version '7.2.1' apply false
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
@ -1,6 +0,0 @@
|
||||
#Fri Jan 20 09:27:45 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
@ -1,89 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
@ -1,16 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
rootProject.name = "fayConnectorDemo"
|
||||
include ':app'
|
@ -33,7 +33,6 @@ if platform.system() == "Windows":
|
||||
from test_olipsync import LipSyncGenerator
|
||||
|
||||
|
||||
|
||||
#文本消息处理(20231211增加:agent操作)
|
||||
def send_for_answer(msg):
|
||||
#记录运行时间
|
||||
@ -53,6 +52,11 @@ def send_for_answer(msg):
|
||||
#agent 处理
|
||||
text = agent_service.agent.run(msg)
|
||||
|
||||
#聊天模式语音输入语音输出
|
||||
if text and "语音" in msg and agent_service.agent.is_chat:
|
||||
interact = Interact("audio", 1, {'user': '', 'msg': text})
|
||||
fay_booter.feiFei.on_interact(interact)
|
||||
|
||||
#推送数字人
|
||||
if not cfg.config["interact"]["playSound"]:
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'log', 'Value': text}}
|
||||
@ -240,7 +244,7 @@ class FeiFei:
|
||||
# if audio_length <= config_util.config["interact"]["maxInteractTime"] or say_type == "script":
|
||||
if config_util.config["interact"]["playSound"]: # 展板播放
|
||||
self.__play_sound(file_url)
|
||||
else:#发送音频给ue和socket
|
||||
else:#发送音频给ue
|
||||
#推送ue
|
||||
content = {'Topic': 'Unreal', 'Data': {'Key': 'audio', 'Value': os.path.abspath(file_url), 'Text': self.a_msg, 'Time': audio_length, 'Type': say_type}}
|
||||
#计算lips
|
||||
@ -254,22 +258,22 @@ class FeiFei:
|
||||
util.log(1, "唇型数字生成失败,无法使用新版ue5工程")
|
||||
wsa_server.get_instance().add_cmd(content)
|
||||
|
||||
#推送远程音频
|
||||
if self.deviceConnect is not None:
|
||||
try:
|
||||
self.deviceConnect.send(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08') # 发送音频开始标志,同时也检查设备是否在线
|
||||
wavfile = open(os.path.abspath(file_url),'rb')
|
||||
#推送远程音频
|
||||
if self.deviceConnect is not None:
|
||||
try:
|
||||
self.deviceConnect.send(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08') # 发送音频开始标志,同时也检查设备是否在线
|
||||
wavfile = open(os.path.abspath(file_url),'rb')
|
||||
data = wavfile.read(1024)
|
||||
total = 0
|
||||
while data:
|
||||
total += len(data)
|
||||
self.deviceConnect.send(data)
|
||||
data = wavfile.read(1024)
|
||||
total = 0
|
||||
while data:
|
||||
total += len(data)
|
||||
self.deviceConnect.send(data)
|
||||
data = wavfile.read(1024)
|
||||
time.sleep(0.001)
|
||||
self.deviceConnect.send(b'\x08\x07\x06\x05\x04\x03\x02\x01\x00')# 发送音频结束标志
|
||||
util.log(1, "远程音频发送完成:{}".format(total))
|
||||
except socket.error as serr:
|
||||
util.log(1,"远程音频输入输出设备已经断开:{}".format(serr))
|
||||
time.sleep(0.001)
|
||||
self.deviceConnect.send(b'\x08\x07\x06\x05\x04\x03\x02\x01\x00')# 发送音频结束标志
|
||||
util.log(1, "远程音频发送完成:{}".format(total))
|
||||
except socket.error as serr:
|
||||
util.log(1,"远程音频输入输出设备已经断开:{}".format(serr))
|
||||
|
||||
time.sleep(audio_length + 0.5)
|
||||
wsa_server.get_web_instance().add_cmd({"panelMsg": ""})
|
||||
|
@ -155,8 +155,7 @@ class Recorder:
|
||||
if not data:
|
||||
continue
|
||||
|
||||
|
||||
if cfg.config['source']['record']['enabled']:
|
||||
if cfg.config['source']['record']['enabled'] and not self.is_remote():
|
||||
if len(cfg.config['source']['record'])<3:
|
||||
channels = 1
|
||||
else:
|
||||
@ -167,7 +166,6 @@ class Recorder:
|
||||
mono = data[:, 0] # taking the first channel
|
||||
data = mono.tobytes()
|
||||
|
||||
|
||||
level = audioop.rms(data, 2)
|
||||
if len(self.__history_data) >= 5:
|
||||
self.__history_data.pop(0)
|
||||
@ -229,3 +227,8 @@ class Recorder:
|
||||
@abstractmethod
|
||||
def get_stream(self):
|
||||
pass
|
||||
|
||||
#TODO Edit by xszyou on 20231225:子类实现返回是否远程音频
|
||||
@abstractmethod
|
||||
def is_remote(self):
|
||||
pass
|
||||
|
@ -71,7 +71,9 @@ class RecorderListener(Recorder):
|
||||
except Exception as e:
|
||||
print(e)
|
||||
util.log(1, "请检查设备是否有误,再重新启动!")
|
||||
|
||||
|
||||
def is_remote(self):
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@ -129,6 +131,9 @@ class DeviceInputListener(Recorder):
|
||||
def start_ngrok(self, clientId):
|
||||
self.ngrok = ngrok_util.NgrokCilent(clientId)
|
||||
self.ngrok.start()
|
||||
|
||||
def is_remote(self):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
@ -417,4 +417,32 @@ textarea {
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
-webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
/* CSS 加载动画样式 */
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 5px solid #f3f3f3; /* 轻颜色 */
|
||||
border-top: 5px solid #3498db; /* 蓝色 */
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
.spinner {
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 50%;
|
||||
border-top: 4px solid #3498db;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
@ -74,7 +74,8 @@ new Vue({
|
||||
msg_list:[],
|
||||
is_connect: false,
|
||||
wake_word_enabled:false,
|
||||
wake_word: ''
|
||||
wake_word: '',
|
||||
loading: false
|
||||
|
||||
}
|
||||
},
|
||||
@ -535,6 +536,12 @@ new Vue({
|
||||
'type' : data['type'] ,
|
||||
'way' : 'send'
|
||||
}
|
||||
console.log(info)
|
||||
if (data['type'] == 'fay'){
|
||||
_this.loading = false;
|
||||
}else{
|
||||
_this.loading = true;
|
||||
}
|
||||
_this.msg_list.push(info);
|
||||
|
||||
this.timer = setTimeout(()=>{ //设置延迟执行
|
||||
|
@ -157,28 +157,32 @@
|
||||
<div class="right_main">
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
|
||||
<div v-for="item in msg_list">
|
||||
|
||||
<div v-for="(item, index) in msg_list" :key="index">
|
||||
|
||||
<div class="item item-center"><span>[[item.timetext]]</span></div>
|
||||
|
||||
<div class="item item-left" v-if="item.type == 'fay'">
|
||||
<div class="avatar"><img src="{{ url_for('static',filename='to.jpg') }}" />
|
||||
</div>
|
||||
<div class="bubble bubble-left">[[item.content]]
|
||||
</div>
|
||||
</div>
|
||||
<div class="item item-right" v-else>
|
||||
<div class="bubble bubble-right">[[item.content]]</div>
|
||||
<div class="avatar"><img src="{{ url_for('static',filename='from.jpg') }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="item item-left" v-if="item.type == 'fay'">
|
||||
<div class="avatar"><img src="{{ url_for('static',filename='to.jpg') }}" />
|
||||
</div>
|
||||
<div class="bubble bubble-left">[[item.content]]
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="item item-right" v-else>
|
||||
<div class="bubble bubble-right" >[[item.content]]<div v-if="loading && index === msg_list.length - 1" class="spinner"></div></div>
|
||||
<div class="avatar"><img src="{{ url_for('static',filename='from.jpg') }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<textarea v-model="send_msg" name="text" id="textarea" placeholder="发送些内容给Fay..."></textarea>
|
||||
<div class="button-area">
|
||||
|
@ -35,7 +35,7 @@ def receive_audio(client):
|
||||
break
|
||||
print("receive audio end:{}".format(len(filedata)), end="")
|
||||
|
||||
filename = "sample/recv_{}.mp3".format(time.time())
|
||||
filename = "samples/recv_{}.mp3".format(time.time())
|
||||
with open(filename, "wb") as f:
|
||||
f.write(filedata)
|
||||
f.close()
|
||||
|