Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cac9dd41aa | ||
|
|
d29aa0dfd1 | ||
|
|
5b0eb711a6 | ||
|
|
dd03bbfcea | ||
|
|
80341f540f | ||
|
|
cb151b989e | ||
|
|
d450cf4933 | ||
|
|
d4f3340383 | ||
|
|
bdb20c2130 | ||
|
|
605e482199 | ||
|
|
3aa55dd875 | ||
|
|
31a82e5461 | ||
|
|
4551fe1f5a | ||
|
|
80a8226c80 |
@@ -51,14 +51,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
def docDir = rootProject.getRootDir().getAbsolutePath() + "/../apk/dashboard"
|
def docDir = rootProject.getRootDir().getAbsolutePath() + "/../apk/dashboardclient"
|
||||||
def releaseDir = rootProject.getRootDir().getAbsolutePath() + "/../apk/dashboard"
|
def releaseDir = rootProject.getRootDir().getAbsolutePath() + "/../apk/dashboardclient"
|
||||||
if (versionName.contains("beta"))
|
if (versionName.contains("beta"))
|
||||||
releaseDir += "/beta"
|
releaseDir += "/beta"
|
||||||
|
|
||||||
variant.outputs.all {
|
variant.outputs.all {
|
||||||
// 常规版本不加后缀
|
// 常规版本不加后缀
|
||||||
outputFileName = "dashboard_${defaultConfig.versionName}.apk"
|
outputFileName = "dashboardclient_${defaultConfig.versionName}.apk"
|
||||||
}
|
}
|
||||||
|
|
||||||
// assemble 结束后,将apk复制到指定目录
|
// assemble 结束后,将apk复制到指定目录
|
||||||
@@ -70,7 +70,7 @@ android {
|
|||||||
into releaseDir
|
into releaseDir
|
||||||
}
|
}
|
||||||
|
|
||||||
def downloadUrl = "http://thinkdisk.thinkbo.cn/src/web/dashboard/${outputFileName}"
|
def downloadUrl = "http://thinkdisk.thinkbo.cn/src/web/dashboardclient/${outputFileName}"
|
||||||
genUpdateJson(downloadUrl, releaseDir, outputFileName, 'changelog.md', defaultConfig, variant)
|
genUpdateJson(downloadUrl, releaseDir, outputFileName, 'changelog.md', defaultConfig, variant)
|
||||||
|
|
||||||
File file = new File("${project.getProjectDir().path}/changelog.md")
|
File file = new File("${project.getProjectDir().path}/changelog.md")
|
||||||
@@ -96,6 +96,8 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation libs.serialport
|
||||||
|
implementation libs.acra.http
|
||||||
implementation libs.appcompat
|
implementation libs.appcompat
|
||||||
implementation libs.material
|
implementation libs.material
|
||||||
implementation libs.activity
|
implementation libs.activity
|
||||||
@@ -111,6 +113,4 @@ dependencies {
|
|||||||
implementation libs.banner
|
implementation libs.banner
|
||||||
implementation libs.glide
|
implementation libs.glide
|
||||||
annotationProcessor libs.glidecompiler
|
annotationProcessor libs.glidecompiler
|
||||||
|
|
||||||
implementation 'io.github.xmaihh:serialport:2.1.1'
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "看板APP更新日志和文件下载"
|
title: "仪表盘 APP 更新日志和文件下载"
|
||||||
author:
|
author:
|
||||||
- 宁波升维信息技术有限公司
|
- 宁波升维信息技术有限公司
|
||||||
---
|
---
|
||||||
@@ -9,11 +9,52 @@ author:
|
|||||||
2. $ {VERSION_CODE} (去掉空格),会自动替换实际修订号,比如 1.1.4.$ {VERSION_CODE}
|
2. $ {VERSION_CODE} (去掉空格),会自动替换实际修订号,比如 1.1.4.$ {VERSION_CODE}
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### [1.0.1.${VERSION_CODE}] - 2025.9.16
|
### [1.0.5.${VERSION_CODE}] - 2026.3.5
|
||||||
|
|
||||||
#### 文件下载
|
#### 文件下载
|
||||||
|
|
||||||
* [dashboard_1.0.1.apk](dashboard_1.0.1.apk)
|
* [dashboardclient_1.0.5.apk](dashboardclient_1.0.5.apk)
|
||||||
|
|
||||||
|
#### 更新记录
|
||||||
|
* App 启动时如果串口路径为空时自动检测设备, 否则开关一次设备
|
||||||
|
* 设置界面可清除串口路径
|
||||||
|
|
||||||
|
### [1.0.4.23] - 2026.1.5
|
||||||
|
|
||||||
|
#### 文件下载
|
||||||
|
|
||||||
|
* [dashboardclient_1.0.4.apk](dashboardclient_1.0.4.apk)
|
||||||
|
|
||||||
|
#### 更新记录
|
||||||
|
* 自动检测串口,检测时保证发送一次开机指令
|
||||||
|
* 增加 acra
|
||||||
|
|
||||||
|
### [1.0.3.22] - 2025.11.1
|
||||||
|
|
||||||
|
#### 文件下载
|
||||||
|
|
||||||
|
* [dashboardclient_1.0.3.apk](dashboardclient_1.0.3.apk)
|
||||||
|
|
||||||
|
#### 更新记录
|
||||||
|
* 当选择自动跳转时,直接访问 URL 前缀,这样网址自由设置
|
||||||
|
|
||||||
|
### [1.0.2.19] - 2025.10.11
|
||||||
|
|
||||||
|
#### 文件下载
|
||||||
|
|
||||||
|
* [dashboardclient_1.0.2.apk](dashboardclient_1.0.2.apk)
|
||||||
|
|
||||||
|
#### 更新记录
|
||||||
|
* 设置界面支持切换到不同类型的仪表盘地址,默认是自动跳转
|
||||||
|
* 监听遥控器 F8 按键,打开设置界面
|
||||||
|
* webView 禁止缩放,避免受到安卓字体和dpi缩放的影响
|
||||||
|
* 优化图标和翻译
|
||||||
|
|
||||||
|
### [1.0.1.12] - 2025.9.16
|
||||||
|
|
||||||
|
#### 文件下载
|
||||||
|
|
||||||
|
* [dashboardclient_1.0.1.apk](dashboardclient_1.0.1.apk)
|
||||||
|
|
||||||
#### 更新记录
|
#### 更新记录
|
||||||
* APP 启动时,发送一次电视开机指令
|
* APP 启动时,发送一次电视开机指令
|
||||||
@@ -22,7 +63,7 @@ author:
|
|||||||
|
|
||||||
#### 文件下载
|
#### 文件下载
|
||||||
|
|
||||||
* [dashboard_1.0.0.apk](dashboard_1.0.0.apk)
|
* [dashboardclient_1.0.0.apk](dashboard_1.0.0.apk)
|
||||||
|
|
||||||
#### 更新记录
|
#### 更新记录
|
||||||
* 初版
|
* 初版
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MyApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
BIN
app_dashboard/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,46 @@
|
|||||||
|
/*************************************************************************
|
||||||
|
* 版权所有 (C) 2026宁波升维信息技术有限公司. 保留所有权利.
|
||||||
|
*
|
||||||
|
* 本软件是由 宁波升维信息技术有限公司 开发的。
|
||||||
|
* 未经版权所有者明确授权,任何人不得使用、复制、修改、分发本软件及其相关文档。
|
||||||
|
* 本软件包含机密和专有信息,未经授权不得向任何第三方披露。
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
package cn.ykbox.dashboard;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import org.acra.ACRA;
|
||||||
|
import org.acra.config.CoreConfigurationBuilder;
|
||||||
|
import org.acra.config.HttpSenderConfigurationBuilder;
|
||||||
|
import org.acra.data.StringFormat;
|
||||||
|
|
||||||
|
public class MyApplication extends Application {
|
||||||
|
public static final String TAG = "MyApplication";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
initLog();
|
||||||
|
initAcra();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void initLog() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAcra() {
|
||||||
|
ACRA.init(this, new CoreConfigurationBuilder()
|
||||||
|
.withBuildConfigClass(BuildConfig.class)
|
||||||
|
.withReportFormat(StringFormat.JSON)
|
||||||
|
.withPluginConfigurations(
|
||||||
|
new HttpSenderConfigurationBuilder()
|
||||||
|
.withBasicAuthLogin("u9UEf5W0giZOBrtR")
|
||||||
|
.withBasicAuthPassword("B39uMIhh15cCoOK7")
|
||||||
|
.withUri("http://acra.slackz.cn/report")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,18 @@ package cn.ykbox.dashboard.activity;
|
|||||||
|
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.webkit.WebResourceError;
|
import android.webkit.WebResourceError;
|
||||||
@@ -32,8 +35,10 @@ import java.util.Calendar;
|
|||||||
|
|
||||||
import cn.ykbox.dashboard.ConfigReader;
|
import cn.ykbox.dashboard.ConfigReader;
|
||||||
import cn.ykbox.dashboard.databinding.ActivityBuildingDashboardBinding;
|
import cn.ykbox.dashboard.databinding.ActivityBuildingDashboardBinding;
|
||||||
|
import cn.ykbox.dashboard.perferences.PreferenceConfiguration;
|
||||||
import cn.ykbox.dashboard.receiver.CommandBroadcastReceiver;
|
import cn.ykbox.dashboard.receiver.CommandBroadcastReceiver;
|
||||||
import cn.ykbox.dashboard.serial.SerialControlDevices;
|
import cn.ykbox.dashboard.serial.SerialControlDevices;
|
||||||
|
import cn.ykbox.dashboard.serial.SerialPortDetector;
|
||||||
|
|
||||||
public class BuildingDashboardActivity extends FullscreenActivity {
|
public class BuildingDashboardActivity extends FullscreenActivity {
|
||||||
private final static String TAG = "DashboardActivity";
|
private final static String TAG = "DashboardActivity";
|
||||||
@@ -60,6 +65,9 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
private ActivityBuildingDashboardBinding binding;
|
private ActivityBuildingDashboardBinding binding;
|
||||||
private ActivityResultLauncher<Intent> settingsLauncher;
|
private ActivityResultLauncher<Intent> settingsLauncher;
|
||||||
|
|
||||||
|
private SerialPortDetector detector;
|
||||||
|
private ProgressDialog progressDialog;
|
||||||
|
|
||||||
private final View.OnTouchListener mDelayHideTouchListener = (view, motionEvent) -> {
|
private final View.OnTouchListener mDelayHideTouchListener = (view, motionEvent) -> {
|
||||||
switch (motionEvent.getAction()) {
|
switch (motionEvent.getAction()) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
@@ -84,6 +92,7 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
super.setViews(binding.webview, binding.fullscreenContentControls);
|
super.setViews(binding.webview, binding.fullscreenContentControls);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
initSerialPort();
|
||||||
initSettingsLauncher();
|
initSettingsLauncher();
|
||||||
initListener();
|
initListener();
|
||||||
initWebView();
|
initWebView();
|
||||||
@@ -94,16 +103,130 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
// 当用户选择了自动跳转时,直接访问 urlPrefix,这样 url 可以自由设置
|
||||||
SharedPreferences pre = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences pre = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
String url = pre.getString("k_url", "http://172.18.22.211:8002/Dashboard");
|
String urlPrefix = pre.getString("k_url_prefix", "http://172.18.22.211:8002/Dashboard");
|
||||||
mainUrl = url + "/index.html";
|
String urlEndPoint = pre.getString("k_url_end_point", "/").replaceFirst("^/", "");
|
||||||
configUrl = url + "/data/config.json";
|
configUrl = urlPrefix + "/data/config.json";
|
||||||
|
mainUrl = urlEndPoint.isEmpty() ? urlPrefix : urlPrefix + "/" + urlEndPoint;
|
||||||
|
|
||||||
|
Log.i(TAG, "Main: " + mainUrl);
|
||||||
|
Log.i(TAG, "Config: " + configUrl);
|
||||||
|
|
||||||
configLoadHandler.post(configLoadRunnable);
|
configLoadHandler.post(configLoadRunnable);
|
||||||
loadUrlWithRetry();
|
loadUrlWithRetry();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initSerialPort() {
|
||||||
|
String portPath = PreferenceConfiguration.getSerialPortPath(this);
|
||||||
|
int baudRate = PreferenceConfiguration.getSerialPortBaudRate(this);
|
||||||
|
boolean sendPowerOnCmd = PreferenceConfiguration.getSendPowerOnCmd(this);
|
||||||
|
|
||||||
|
// 初始化串口检测器
|
||||||
|
detector = new SerialPortDetector(portPath, baudRate);
|
||||||
|
|
||||||
|
// 只有 portPath 为空时才进行串口检测
|
||||||
|
if (TextUtils.isEmpty(portPath)) {
|
||||||
|
Log.i(TAG, "Starting serial port detection/verification...");
|
||||||
|
// 参数 true 表示确保设备开机(发送两个命令)
|
||||||
|
// 参数 false 表示仅检测串口(任一命令有响应即可)
|
||||||
|
startSerialPortDetection(true);
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Using saved serial port path: " + portPath);
|
||||||
|
|
||||||
|
// 如果勾选了"发送打开电源指令",则先发送关闭再发送打开
|
||||||
|
if (sendPowerOnCmd) {
|
||||||
|
Log.i(TAG, "Sending power on sequence...");
|
||||||
|
new Thread(() -> {
|
||||||
|
detector.sendPowerOffCommand();
|
||||||
|
try {
|
||||||
|
Thread.sleep(3000); // 等待 3 秒
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Sleep interrupted", e);
|
||||||
|
}
|
||||||
|
detector.sendPowerOnCommand();
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动串口检测
|
||||||
|
* @param ensurePowerOn 是否确保设备开机
|
||||||
|
*/
|
||||||
|
private void startSerialPortDetection(boolean ensurePowerOn) {
|
||||||
|
// 显示进度对话框
|
||||||
|
progressDialog = new ProgressDialog(this);
|
||||||
|
progressDialog.setTitle("串口检测");
|
||||||
|
progressDialog.setMessage("正在检测串口...");
|
||||||
|
progressDialog.setCancelable(false);
|
||||||
|
progressDialog.show();
|
||||||
|
|
||||||
|
// 设置检测回调
|
||||||
|
detector.setCallback(new SerialPortDetector.DetectionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onDetectionStart() {
|
||||||
|
Log.d(TAG, "Detection started");
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
progressDialog.setMessage("开始检测串口...");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetectionProgress(String portPath, int current, int total) {
|
||||||
|
Log.d(TAG, "Testing port " + current + "/" + total + ": " + portPath);
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
progressDialog.setMessage("正在测试串口 " + current + "/" + total + "\n" + portPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetectionSuccess(String portPath) {
|
||||||
|
Log.i(TAG, "Detection success: " + portPath);
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
Toast.makeText(BuildingDashboardActivity.this,
|
||||||
|
"串口检测成功!\n路径: " + portPath,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
PreferenceConfiguration.setSerialPortPath(BuildingDashboardActivity.this, portPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetectionFailed() {
|
||||||
|
Log.e(TAG, "Detection failed");
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
// 可以显示错误界面或重试选项
|
||||||
|
showDetectionFailedDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始检测
|
||||||
|
detector.startDetection(ensurePowerOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示检测失败的对话框
|
||||||
|
*/
|
||||||
|
private void showDetectionFailedDialog() {
|
||||||
|
Toast.makeText(BuildingDashboardActivity.this,
|
||||||
|
"未找到有效的串口设备,请检查设备连接后重试。",
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
private void loadUrlWithRetry() {
|
private void loadUrlWithRetry() {
|
||||||
|
Log.i("WebView", "Loading " + mainUrl);
|
||||||
binding.webview.loadUrl(mainUrl);
|
binding.webview.loadUrl(mainUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +265,24 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听按键,因为 WebView 可能会拦截功能键,这里不使用 onKeyDown
|
||||||
|
* @param event
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
Log.d(TAG, "dispatchKeyEvent:" + event.getKeyCode());
|
||||||
|
|
||||||
|
if ( event.getKeyCode() == KeyEvent.KEYCODE_F8
|
||||||
|
&& event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||||
|
openSettingsActivity();
|
||||||
|
return true; // 不再向下分发
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
private void initListener() {
|
private void initListener() {
|
||||||
binding.settingsButton.setOnClickListener(view -> openSettingsActivity());
|
binding.settingsButton.setOnClickListener(view -> openSettingsActivity());
|
||||||
}
|
}
|
||||||
@@ -157,15 +298,31 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
WebSettings webSettings = binding.webview.getSettings();
|
WebSettings webSettings = binding.webview.getSettings();
|
||||||
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
|
||||||
webSettings.setJavaScriptEnabled(true);
|
webSettings.setJavaScriptEnabled(true);
|
||||||
|
// 允许媒体自动播放 (Android 8+ 必须)
|
||||||
|
webSettings.setMediaPlaybackRequiresUserGesture(false);
|
||||||
|
|
||||||
|
// 禁用缩放相关设置
|
||||||
|
webSettings.setTextZoom(100);
|
||||||
|
webSettings.setSupportZoom(false); // 禁止缩放
|
||||||
|
webSettings.setBuiltInZoomControls(false);
|
||||||
|
|
||||||
|
binding.webview.setInitialScale(100);
|
||||||
|
|
||||||
binding.webview.setWebViewClient(new WebViewClient() {
|
binding.webview.setWebViewClient(new WebViewClient() {
|
||||||
|
private boolean hasError = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||||
|
hasError = false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceivedError(WebView view, WebResourceRequest request,
|
public void onReceivedError(WebView view, WebResourceRequest request,
|
||||||
WebResourceError error) {
|
WebResourceError error) {
|
||||||
super.onReceivedError(view, request, error);
|
super.onReceivedError(view, request, error);
|
||||||
|
|
||||||
// 只处理主页面的错误,不处理资源文件错误
|
if (request.isForMainFrame()) {
|
||||||
if (request.getUrl().toString().equals(mainUrl)) {
|
hasError = true;
|
||||||
handleLoadError();
|
handleLoadError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,9 +331,8 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
public void onReceivedHttpError(WebView view, WebResourceRequest request,
|
public void onReceivedHttpError(WebView view, WebResourceRequest request,
|
||||||
WebResourceResponse errorResponse) {
|
WebResourceResponse errorResponse) {
|
||||||
super.onReceivedHttpError(view, request, errorResponse);
|
super.onReceivedHttpError(view, request, errorResponse);
|
||||||
|
if (request.isForMainFrame() && errorResponse.getStatusCode() >= 400) {
|
||||||
// 处理HTTP错误(如404, 500等)
|
hasError = true;
|
||||||
if (request.getUrl().toString().equals(mainUrl)) {
|
|
||||||
handleLoadError();
|
handleLoadError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,15 +340,12 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onPageFinished(WebView view, String url) {
|
public void onPageFinished(WebView view, String url) {
|
||||||
super.onPageFinished(view, url);
|
super.onPageFinished(view, url);
|
||||||
|
if (!hasError) {
|
||||||
// 页面加载成功,重置重试计数
|
|
||||||
if (url.equals(mainUrl)) {
|
|
||||||
retryCount = 0;
|
retryCount = 0;
|
||||||
Log.d("WebView", "页面加载成功: " + url);
|
Log.d("WebView", "加载OK");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initConfigLoader() {
|
private void initConfigLoader() {
|
||||||
@@ -251,8 +404,6 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject serialConfig = new JSONObject(serialConfigJson);
|
JSONObject serialConfig = new JSONObject(serialConfigJson);
|
||||||
String portPath = serialConfig.optString("DevicePath", "/dev/ttyS2");
|
|
||||||
int baudRate = serialConfig.optInt("Baud", 9600);
|
|
||||||
|
|
||||||
if (serialConfig.has("Commands")) {
|
if (serialConfig.has("Commands")) {
|
||||||
JSONArray commands = serialConfig.getJSONArray("Commands");
|
JSONArray commands = serialConfig.getJSONArray("Commands");
|
||||||
@@ -268,8 +419,6 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
Intent intent = new Intent(this, CommandBroadcastReceiver.class);
|
Intent intent = new Intent(this, CommandBroadcastReceiver.class);
|
||||||
intent.setAction(CommandBroadcastReceiver.ACTION_SEND_COMMAND);
|
intent.setAction(CommandBroadcastReceiver.ACTION_SEND_COMMAND);
|
||||||
intent.putExtra(CommandBroadcastReceiver.EXTRA_COMMAND_HEX, hex);
|
intent.putExtra(CommandBroadcastReceiver.EXTRA_COMMAND_HEX, hex);
|
||||||
intent.putExtra(CommandBroadcastReceiver.EXTRA_PORT_PATH, portPath);
|
|
||||||
intent.putExtra(CommandBroadcastReceiver.EXTRA_BAUD_RATE, baudRate);
|
|
||||||
|
|
||||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, i, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, i, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
|
|
||||||
@@ -285,17 +434,14 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
||||||
Log.d(TAG, "setExactAndAllowWhileIdle, Set repeating alarm for " + time + " with command " + hex + " on port " + portPath + " at " + baudRate + " baud");
|
Log.d(TAG, "setExactAndAllowWhileIdle, Set repeating alarm for " + time + " with command " + hex);
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
||||||
Log.d(TAG, "setExact, Set repeating alarm for " + time + " with command " + hex + " on port " + portPath + " at " + baudRate + " baud");
|
Log.d(TAG, "setExact, Set repeating alarm for " + time + " with command " + hex);
|
||||||
} else {
|
} else {
|
||||||
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
||||||
Log.d(TAG, "Set repeating alarm for " + time + " with command " + hex + " on port " + portPath + " at " + baudRate + " baud");
|
Log.d(TAG, "Set repeating alarm for " + time + " with command " + hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package cn.ykbox.dashboard.activity;
|
package cn.ykbox.dashboard.activity;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import cn.ykbox.dashboard.R;
|
import cn.ykbox.dashboard.R;
|
||||||
|
import cn.ykbox.dashboard.perferences.PreferenceConfiguration;
|
||||||
|
|
||||||
public class SettingsActivity extends AppCompatActivity {
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@@ -30,6 +34,21 @@ public class SettingsActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||||
|
|
||||||
|
Preference clearDevicePref = findPreference("k_clear_device");
|
||||||
|
if (clearDevicePref != null) {
|
||||||
|
clearDevicePref.setOnPreferenceClickListener(preference -> {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("确认清除")
|
||||||
|
.setMessage("确定要清除当前串口设备路径吗?")
|
||||||
|
.setPositiveButton("确定", (dialog, which) -> {
|
||||||
|
PreferenceConfiguration.setSerialPortPath(requireContext(), "");
|
||||||
|
})
|
||||||
|
.setNegativeButton("取消", null)
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,6 @@ public class StartActivity extends AppCompatActivity {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// 开机后手动发送一次开启电源指令
|
|
||||||
SerialControlDevices.sendCommand("/dev/ttyS2", 9600, "ACEAB400ED");
|
|
||||||
|
|
||||||
mContext = this;
|
mContext = this;
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
setContentView(R.layout.activity_start);
|
setContentView(R.layout.activity_start);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package cn.ykbox.dashboard.perferences;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置管理
|
||||||
|
* TODO 代码中零散的读取配置选项代码都集中到这里
|
||||||
|
*/
|
||||||
|
public class PreferenceConfiguration {
|
||||||
|
private final static String TAG = "PreferenceConfiguration";
|
||||||
|
|
||||||
|
private static final String KEY_SERIAL_PORT_PATH = "k_serial_port_path";
|
||||||
|
private static final String KEY_SERIAL_PORT_BAUD_RATE = "k_serial_baud";
|
||||||
|
private static final String KEY_SEND_POWER_ON_CMD = "k_send_power_on_cmd";
|
||||||
|
|
||||||
|
public static String getSerialPortPath(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getString(KEY_SERIAL_PORT_PATH, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setSerialPortPath(Context context, String portPath) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
prefs.edit().putString(KEY_SERIAL_PORT_PATH, portPath).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getSerialPortBaudRate(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
String baud = prefs.getString(KEY_SERIAL_PORT_BAUD_RATE, "9600");
|
||||||
|
return Integer.parseInt(baud);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getSendPowerOnCmd(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getBoolean(KEY_SEND_POWER_ON_CMD, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,25 +3,29 @@ package cn.ykbox.dashboard.receiver;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import cn.ykbox.dashboard.perferences.PreferenceConfiguration;
|
||||||
import cn.ykbox.dashboard.serial.SerialControlDevices;
|
import cn.ykbox.dashboard.serial.SerialControlDevices;
|
||||||
|
|
||||||
public class CommandBroadcastReceiver extends BroadcastReceiver {
|
public class CommandBroadcastReceiver extends BroadcastReceiver {
|
||||||
private static final String TAG = "CommandReceiver";
|
private static final String TAG = "CommandReceiver";
|
||||||
public static final String ACTION_SEND_COMMAND = "cn.ykbox.dashboard.ACTION_SEND_COMMAND";
|
public static final String ACTION_SEND_COMMAND = "cn.ykbox.dashboard.ACTION_SEND_COMMAND";
|
||||||
public static final String EXTRA_COMMAND_HEX = "EXTRA_COMMAND_HEX";
|
public static final String EXTRA_COMMAND_HEX = "EXTRA_COMMAND_HEX";
|
||||||
public static final String EXTRA_PORT_PATH = "EXTRA_PORT_PATH";
|
|
||||||
public static final String EXTRA_BAUD_RATE = "EXTRA_BAUD_RATE"; // 新增:波特率的 Extra Key
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (intent != null && ACTION_SEND_COMMAND.equals(intent.getAction())) {
|
if (intent != null && ACTION_SEND_COMMAND.equals(intent.getAction())) {
|
||||||
String hexCommand = intent.getStringExtra(EXTRA_COMMAND_HEX);
|
String hexCommand = intent.getStringExtra(EXTRA_COMMAND_HEX);
|
||||||
String portPath = intent.getStringExtra(EXTRA_PORT_PATH);
|
|
||||||
int baudRate = intent.getIntExtra(EXTRA_BAUD_RATE, 9600);
|
|
||||||
|
|
||||||
if (hexCommand != null && !hexCommand.isEmpty() && portPath != null && !portPath.isEmpty()) {
|
String portPath = PreferenceConfiguration.getSerialPortPath(context);
|
||||||
|
int baudRate = PreferenceConfiguration.getSerialPortBaudRate(context);
|
||||||
|
|
||||||
|
if (hexCommand != null && !hexCommand.isEmpty() && !TextUtils.isEmpty(portPath)) {
|
||||||
Log.d(TAG, "Received alarm to send command '" + hexCommand + "' to port '" + portPath + "' at " + baudRate + " baud");
|
Log.d(TAG, "Received alarm to send command '" + hexCommand + "' to port '" + portPath + "' at " + baudRate + " baud");
|
||||||
// 使用新的静态方法发送指令
|
// 使用新的静态方法发送指令
|
||||||
boolean success = SerialControlDevices.sendCommand(portPath, baudRate, hexCommand);
|
boolean success = SerialControlDevices.sendCommand(portPath, baudRate, hexCommand);
|
||||||
|
|||||||
@@ -0,0 +1,363 @@
|
|||||||
|
package cn.ykbox.dashboard.serial;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import android_serialport_api.SerialPortFinder;
|
||||||
|
import tp.xmaihh.serialport.SerialHelper;
|
||||||
|
import tp.xmaihh.serialport.bean.ComBean;
|
||||||
|
|
||||||
|
public class SerialPortDetector {
|
||||||
|
private static final String TAG = "SerialPortDetector";
|
||||||
|
// 测试命令
|
||||||
|
private static final String TEST_CMD_OFF = "ACEAB500ED"; // 关闭设备
|
||||||
|
private static final String TEST_CMD_ON = "ACEAB400ED"; // 打开设备
|
||||||
|
|
||||||
|
// 响应超时时间(毫秒)
|
||||||
|
private static final long RESPONSE_TIMEOUT = 6000;
|
||||||
|
|
||||||
|
private DetectionCallback callback;
|
||||||
|
private Handler mainHandler;
|
||||||
|
|
||||||
|
private String savedPath;
|
||||||
|
private int baudRate;
|
||||||
|
|
||||||
|
public interface DetectionCallback {
|
||||||
|
void onDetectionStart();
|
||||||
|
void onDetectionProgress(String portPath, int current, int total);
|
||||||
|
void onDetectionSuccess(String portPath);
|
||||||
|
void onDetectionFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SerialPortDetector(String savedPath, int baudRate) {
|
||||||
|
this.savedPath = savedPath;
|
||||||
|
this.baudRate = baudRate;
|
||||||
|
this.mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallback(DetectionCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送关闭设备指令到已保存的串口(不等待响应)
|
||||||
|
*/
|
||||||
|
public void sendPowerOffCommand() {
|
||||||
|
sendCommand(TEST_CMD_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送打开设备指令到已保存的串口(不等待响应)
|
||||||
|
*/
|
||||||
|
public void sendPowerOnCommand() {
|
||||||
|
sendCommand(TEST_CMD_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送指令到已保存的串口(不等待响应)
|
||||||
|
* @param commandHex 十六进制命令字符串
|
||||||
|
*/
|
||||||
|
private void sendCommand(String commandHex) {
|
||||||
|
if (TextUtils.isEmpty(savedPath)) {
|
||||||
|
Log.w(TAG, "No serial port path configured");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
SerialHelper serialHelper = null;
|
||||||
|
try {
|
||||||
|
serialHelper = new SimpleSerialHelper(savedPath, baudRate);
|
||||||
|
serialHelper.open();
|
||||||
|
serialHelper.sendHex(commandHex);
|
||||||
|
Log.d(TAG, "Sent command: " + commandHex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error sending command: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (serialHelper != null) {
|
||||||
|
try {
|
||||||
|
serialHelper.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error closing port: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始串口检测(智能检测)
|
||||||
|
* 先验证已保存的串口,如果不可用再进行全局检测
|
||||||
|
*/
|
||||||
|
public void startDetection() {
|
||||||
|
startDetection(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始串口检测(智能检测)
|
||||||
|
* @param ensurePowerOn 是否确保设备开机(发送两个命令)
|
||||||
|
*/
|
||||||
|
public void startDetection(boolean ensurePowerOn) {
|
||||||
|
new Thread(() -> {
|
||||||
|
notifyDetectionStart();
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(savedPath)) {
|
||||||
|
Log.d(TAG, "Found saved serial port path: " + savedPath);
|
||||||
|
Log.d(TAG, "Verifying saved port: " + savedPath);
|
||||||
|
|
||||||
|
// 验证已保存的串口是否可用
|
||||||
|
if (testSerialPort(savedPath, baudRate, ensurePowerOn)) {
|
||||||
|
Log.i(TAG, "Saved serial port is valid: " + savedPath);
|
||||||
|
notifyDetectionSuccess(savedPath);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Saved serial port is invalid, starting full detection");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "No saved serial port path, starting full detection");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已保存的串口不可用或不存在,进行全局检测
|
||||||
|
performFullDetection(ensurePowerOn);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performFullDetection(boolean ensurePowerOn) {
|
||||||
|
SerialPortFinder finder = new SerialPortFinder();
|
||||||
|
String[] devicePaths = finder.getAllDevicesPath();
|
||||||
|
|
||||||
|
if (devicePaths == null || devicePaths.length == 0) {
|
||||||
|
Log.e(TAG, "No serial ports found");
|
||||||
|
notifyDetectionFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Found " + devicePaths.length + " serial ports");
|
||||||
|
|
||||||
|
// 遍历所有串口
|
||||||
|
for (int i = 0; i < devicePaths.length; i++) {
|
||||||
|
String portPath = devicePaths[i];
|
||||||
|
notifyDetectionProgress(portPath, i + 1, devicePaths.length);
|
||||||
|
|
||||||
|
Log.d(TAG, "Testing port: " + portPath + " at " + baudRate + " baud");
|
||||||
|
|
||||||
|
if (testSerialPort(portPath, baudRate, ensurePowerOn)) {
|
||||||
|
Log.i(TAG, "Serial port detected successfully: " + portPath);
|
||||||
|
notifyDetectionSuccess(portPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "No valid serial port found");
|
||||||
|
notifyDetectionFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试指定的串口
|
||||||
|
* @param portPath 串口路径
|
||||||
|
* @param baudRate 波特率
|
||||||
|
* @param ensurePowerOn 是否确保设备开机(发送两个命令)
|
||||||
|
*/
|
||||||
|
private boolean testSerialPort(String portPath, int baudRate, boolean ensurePowerOn) {
|
||||||
|
TestSerialHelper serialHelper = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
serialHelper = new TestSerialHelper(portPath, baudRate);
|
||||||
|
serialHelper.open();
|
||||||
|
|
||||||
|
if (ensurePowerOn) {
|
||||||
|
// 需要确保设备开机,两个命令都要发送
|
||||||
|
Log.d(TAG, "Ensuring device power on by sending both commands");
|
||||||
|
|
||||||
|
boolean cmd1Success = sendCommandAndWaitResponse(serialHelper, TEST_CMD_OFF);
|
||||||
|
boolean cmd2Success = sendCommandAndWaitResponse(serialHelper, TEST_CMD_ON);
|
||||||
|
|
||||||
|
// 只要有一个命令收到有效响应就认为串口可用
|
||||||
|
if (cmd1Success || cmd2Success) {
|
||||||
|
Log.d(TAG, "Device responded (CMD1: " + cmd1Success + ", CMD2: " + cmd2Success + ")");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// 只需要检测串口,任意一个命令有响应即可
|
||||||
|
if (sendCommandAndWaitResponse(serialHelper, TEST_CMD_OFF)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (sendCommandAndWaitResponse(serialHelper, TEST_CMD_ON)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error testing port " + portPath + ": " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
if (serialHelper != null) {
|
||||||
|
try {
|
||||||
|
serialHelper.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error closing port: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送命令并等待响应
|
||||||
|
*/
|
||||||
|
private boolean sendCommandAndWaitResponse(TestSerialHelper serialHelper, String command) {
|
||||||
|
try {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
AtomicBoolean responseReceived = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
serialHelper.setResponseListener((isValid) -> {
|
||||||
|
if (isValid) {
|
||||||
|
responseReceived.set(true);
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空之前的响应标志
|
||||||
|
serialHelper.resetResponse();
|
||||||
|
|
||||||
|
// 发送命令
|
||||||
|
serialHelper.sendHex(command);
|
||||||
|
Log.d(TAG, "Sent command: " + command);
|
||||||
|
|
||||||
|
// 等待响应
|
||||||
|
boolean timeout = !latch.await(RESPONSE_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (timeout) {
|
||||||
|
Log.d(TAG, "Response timeout for command: " + command);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseReceived.get();
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(TAG, "Wait interrupted: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回调通知方法
|
||||||
|
private void notifyDetectionStart() {
|
||||||
|
if (callback != null) {
|
||||||
|
mainHandler.post(() -> callback.onDetectionStart());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDetectionProgress(String portPath, int current, int total) {
|
||||||
|
if (callback != null) {
|
||||||
|
mainHandler.post(() -> callback.onDetectionProgress(portPath, current, total));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDetectionSuccess(String portPath) {
|
||||||
|
if (callback != null) {
|
||||||
|
mainHandler.post(() -> callback.onDetectionSuccess(portPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDetectionFailed() {
|
||||||
|
if (callback != null) {
|
||||||
|
mainHandler.post(() -> callback.onDetectionFailed());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的串口 Helper,用于只发送命令不等待响应
|
||||||
|
*/
|
||||||
|
private static class SimpleSerialHelper extends SerialHelper {
|
||||||
|
public SimpleSerialHelper(String sPort, int iBaudRate) {
|
||||||
|
super(sPort, iBaudRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDataReceived(ComBean comBean) {
|
||||||
|
// 不需要处理响应
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部测试用的 SerialHelper
|
||||||
|
*/
|
||||||
|
private static class TestSerialHelper extends SerialHelper {
|
||||||
|
private ResponseListener responseListener;
|
||||||
|
|
||||||
|
public interface ResponseListener {
|
||||||
|
void onResponse(boolean isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestSerialHelper(String sPort, int iBaudRate) {
|
||||||
|
super(sPort, iBaudRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResponseListener(ResponseListener listener) {
|
||||||
|
this.responseListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetResponse() {
|
||||||
|
// 重置响应状态,为下一次测试做准备
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDataReceived(ComBean comBean) {
|
||||||
|
byte[] data = comBean.bRec;
|
||||||
|
|
||||||
|
// 检查是否为有效响应格式: DA XX XX XX XX ED
|
||||||
|
if (isValidResponse(data)) {
|
||||||
|
Log.d(TAG, "Valid response received: " + bytesToHex(data));
|
||||||
|
if (responseListener != null) {
|
||||||
|
responseListener.onResponse(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Invalid response: " + bytesToHex(data));
|
||||||
|
if (responseListener != null) {
|
||||||
|
responseListener.onResponse(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查响应是否符合格式: DA XX XX XX XX ED
|
||||||
|
*/
|
||||||
|
private boolean isValidResponse(byte[] data) {
|
||||||
|
if (data == null || data.length < 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查起始字节是否为 0xDA
|
||||||
|
if ((data[0] & 0xFF) != 0xDA) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查结束字节是否为 0xED
|
||||||
|
if ((data[data.length - 1] & 0xFF) != 0xED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字节数组转十六进制字符串(用于日志)
|
||||||
|
*/
|
||||||
|
private String bytesToHex(byte[] bytes) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02X ", b & 0xFF));
|
||||||
|
}
|
||||||
|
return sb.toString().trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
@@ -5,166 +5,6 @@
|
|||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#3DDC84"
|
android:fillColor="#065994"
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
<path
|
</vector>
|
||||||
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>
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<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">
|
||||||
|
<group android:scaleX="0.75"
|
||||||
|
android:scaleY="0.75"
|
||||||
|
android:translateX="13.5"
|
||||||
|
android:translateY="13.5">
|
||||||
|
<path
|
||||||
|
android:pathData="m36.75,60.38c0,-1.13 -0.38,-2.25 -1.13,-3 -0.75,-0.75 -1.88,-1.5 -3,-1.5 -1.13,0 -2.25,0.75 -3,1.5 -0.75,0.75 -1.5,1.88 -1.5,3 0,1.13 0.38,2.25 1.13,3 0.75,0.75 1.88,1.13 3,1.13 1.13,0 2.25,-0.38 3,-1.13 0.75,-0.75 1.5,-1.88 1.5,-3zM43.13,45.38c0,-1.13 -0.38,-2.25 -1.13,-3 -0.75,-0.75 -1.88,-1.5 -3,-1.5 -1.13,0 -2.25,0.38 -3,1.13 -0.75,0.75 -1.13,2.25 -1.13,3.38 0,1.13 0.38,2.25 1.13,3 0.75,0.75 1.88,1.13 3,1.13 1.13,0 2.25,-0.38 3,-1.13 0.75,-0.75 1.13,-1.88 1.13,-3zM57.75,61.5 L61.13,48.75c0,-0.75 0,-1.13 -0.38,-1.5C60.38,46.5 60,46.13 59.63,46.13L57.75,46.13C57.38,46.5 57,46.88 57,47.63l-3.38,12.75c-1.5,0 -2.63,0.75 -3.75,1.5 -1.13,0.75 -1.88,1.88 -2.25,3.38 -0.38,1.88 -0.38,3.38 0.75,4.88 0.75,1.5 2.25,2.63 3.75,3 1.5,0.38 3.38,0.38 4.88,-0.75 1.5,-0.75 2.63,-2.25 3,-3.75 0.38,-1.5 0.38,-2.63 -0.38,-3.75 0,-1.5 -0.75,-2.63 -1.88,-3.38zM79.88,60.38c0,-1.13 -0.38,-2.25 -1.13,-3 -0.75,-0.75 -1.88,-1.13 -3,-1.13 -1.13,0 -2.25,0.38 -3,1.13 -0.75,0.75 -1.13,1.88 -1.13,3 0,1.13 0.38,2.25 1.13,3 0.75,0.75 1.88,1.13 3,1.13 1.13,0 2.25,-0.38 3,-1.13 0.38,-0.75 1.13,-1.88 1.13,-3zM58.13,39c0,-1.13 -0.38,-2.25 -1.13,-3 -0.75,-1.13 -1.88,-1.5 -3,-1.5 -1.13,0 -2.25,0.38 -3,1.13 -0.75,1.13 -1.13,2.25 -1.13,3.38 0,1.13 0.38,2.25 1.13,3 0.75,0.75 1.88,1.13 3,1.13 1.13,0 2.25,-0.38 3,-1.13 0.75,-0.75 1.13,-1.88 1.13,-3zM73.13,45.38c0,-1.13 -0.38,-2.25 -1.13,-3 -0.75,-0.75 -1.88,-1.13 -3,-1.13 -1.13,0 -2.25,0.38 -3,1.13 -0.75,0.75 -1.13,1.88 -1.13,3 0,1.13 0.38,2.25 1.13,3 0.75,0.75 1.88,1.13 3,1.13 1.13,0 2.25,-0.38 3,-1.13 0.75,-0.75 1.13,-1.88 1.13,-3zM84,60.38c0,6 -1.5,11.25 -4.88,16.13C78.75,77.25 78,77.63 77.25,77.63L30.38,77.63c-0.75,0 -1.5,-0.38 -1.88,-1.13C25.5,71.63 24,66 24,60.38 24,56.25 24.75,52.5 26.25,48.75 27.75,45 30,42 32.63,39c2.63,-3 6,-4.88 9.75,-6.38 3.75,-1.5 7.5,-2.25 11.63,-2.25 4.13,0 7.88,0.75 11.63,2.25 3.75,1.5 6.75,3.75 9.75,6.38 2.63,2.63 4.88,6 6.38,9.75 1.5,3.75 2.25,7.5 2.25,11.63z"
|
||||||
|
android:strokeWidth="0.0585938"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 874 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.1 KiB |
7
app_dashboard/src/main/res/values-zh-rCN/arrays.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<resources>
|
||||||
|
<string-array name="url_end_point_entries">
|
||||||
|
<item>自动跳转</item>
|
||||||
|
<item>按楼层</item>
|
||||||
|
<item>网格+统计</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
6
app_dashboard/src/main/res/values-zh-rCN/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">仪表盘</string>
|
||||||
|
<string name="title_activity_building_dashboard">教学楼仪表盘</string>
|
||||||
|
<string name="title_activity_settings">设置</string>
|
||||||
|
</resources>
|
||||||
@@ -9,4 +9,16 @@
|
|||||||
<item>reply</item>
|
<item>reply</item>
|
||||||
<item>reply_all</item>
|
<item>reply_all</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="url_end_point_entries">
|
||||||
|
<item>Auto Redirect</item>
|
||||||
|
<item>Floor</item>
|
||||||
|
<item>Grid+Statics</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="url_end_point_values">
|
||||||
|
<item>/</item>
|
||||||
|
<item>/dashboards/1</item>
|
||||||
|
<item>/dashboards/2</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,22 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Dashboard</string>
|
<string name="app_name">Dashboard</string>
|
||||||
<string name="dummy_button">Dummy Button</string>
|
<string name="title_activity_building_dashboard">Building Dashboard</string>
|
||||||
<string name="dummy_content">DUMMY\nCONTENT</string>
|
<string name="title_activity_settings">Settings</string>
|
||||||
<string name="title_activity_building_dashboard">BuildingDashboardActivity</string>
|
<string name="url_end_point_default_value" translatable="false">/</string>
|
||||||
<string name="title_activity_settings">SettingsActivity</string>
|
|
||||||
|
|
||||||
<!-- Preference Titles -->
|
|
||||||
<string name="messages_header">Messages</string>
|
|
||||||
<string name="sync_header">Sync</string>
|
|
||||||
|
|
||||||
<!-- Messages Preferences -->
|
|
||||||
<string name="signature_title">Your signature</string>
|
|
||||||
<string name="reply_title">Default reply action</string>
|
|
||||||
|
|
||||||
<!-- Sync Preferences -->
|
|
||||||
<string name="sync_title">Sync email periodically</string>
|
|
||||||
<string name="attachment_title">Download incoming attachments</string>
|
|
||||||
<string name="attachment_summary_on">Automatically download attachments for incoming emails
|
|
||||||
</string>
|
|
||||||
<string name="attachment_summary_off">Only download attachments when manually requested</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,9 +1,38 @@
|
|||||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
<PreferenceCategory app:title="网页地址">
|
<PreferenceCategory app:title="网页地址">
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="k_url"
|
app:key="k_url_prefix"
|
||||||
app:title="URL 前缀"
|
app:title="URL 前缀"
|
||||||
app:defaultValue="http://172.18.22.211:8002/Dashboard"
|
app:defaultValue="http://172.18.22.211:8002/Dashboard"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<ListPreference
|
||||||
|
app:title="仪表盘风格"
|
||||||
|
app:key="k_url_end_point"
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
|
app:entries="@array/url_end_point_entries"
|
||||||
|
app:entryValues="@array/url_end_point_values"
|
||||||
|
app:defaultValue="@string/url_end_point_default_value" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="外部设备">
|
||||||
|
<EditTextPreference
|
||||||
|
app:key="k_serial_port_path"
|
||||||
|
app:title="串口设备"
|
||||||
|
app:defaultValue=""
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<EditTextPreference
|
||||||
|
app:key="k_serial_baud"
|
||||||
|
app:title="串口波特率"
|
||||||
|
app:defaultValue="9600"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<CheckBoxPreference
|
||||||
|
app:key="k_send_power_on_cmd"
|
||||||
|
app:title="App 启动时发送打开电源指令"
|
||||||
|
app:summary="每次启动 App 时先发送关闭再发送打开电源指令"
|
||||||
|
app:defaultValue="true" />
|
||||||
|
<Preference
|
||||||
|
app:key="k_clear_device"
|
||||||
|
app:title="清除设备"
|
||||||
|
app:summary="清除当前串口设备路径, 下次启动时自动检测设备。" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
@@ -15,10 +15,11 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
|
maven { url 'https://maven.aliyun.com/repository/public/' }
|
||||||
maven { url 'https://maven.aliyun.com/repository/jcenter' } // 代替 jc
|
maven { url 'https://maven.aliyun.com/repository/jcenter' } // 代替 jc
|
||||||
|
maven { url 'https://repo1.maven.org/maven2/' }
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url 'https://repo1.maven.org/maven2/' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提示过时的API
|
// 提示过时的API
|
||||||
|
|||||||
20
clear.bat
@@ -1,20 +0,0 @@
|
|||||||
@echo off
|
|
||||||
|
|
||||||
echo clear builds
|
|
||||||
|
|
||||||
rd /s /q %~dp0\app_classtv\build
|
|
||||||
rd /s /q %~dp0\app_sinclass\build
|
|
||||||
rd /s /q %~dp0\app_sinclasspad\build
|
|
||||||
rd /s /q %~dp0\app_sinclasspad2\build
|
|
||||||
rd /s /q %~dp0\app_startap\build
|
|
||||||
rd /s /q %~dp0\bjcast\build
|
|
||||||
rd /s /q %~dp0\cccl\build
|
|
||||||
rd /s /q %~dp0\demo_coaface\build
|
|
||||||
rd /s /q %~dp0\demo_serial\build
|
|
||||||
rd /s /q %~dp0\demo_serviceecd\build
|
|
||||||
rd /s /q %~dp0\demo_twoscreen\build
|
|
||||||
rd /s /q %~dp0\serialport\build
|
|
||||||
rd /s /q %~dp0\serviceecd\build
|
|
||||||
rd /s /q %~dp0\signageapi\build
|
|
||||||
rd /s /q %~dp0\signageui\build
|
|
||||||
rd /s /q %~dp0\signageutil\build
|
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
serialport = "2.1.1"
|
||||||
|
acraHttp = "5.13.1"
|
||||||
agp = "8.8.2"
|
agp = "8.8.2"
|
||||||
exoplayer = "2.19.1"
|
exoplayer = "2.19.1"
|
||||||
banner = "2.2.2"
|
banner = "2.2.2"
|
||||||
@@ -28,6 +30,8 @@ utilcodex = "1.31.1"
|
|||||||
xlog = "1.11.1"
|
xlog = "1.11.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
serialport = { module = "io.github.xmaihh:serialport", version.ref = "serialport" }
|
||||||
|
acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" }
|
||||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||||
banner = { module = "io.github.youth5201314:banner", version.ref = "banner" }
|
banner = { module = "io.github.youth5201314:banner", version.ref = "banner" }
|
||||||
exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" }
|
exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" }
|
||||||
|
|||||||
68
readme.md
@@ -1,10 +1,70 @@
|
|||||||
|
# Dashboard Client
|
||||||
|
|
||||||
## 说明
|
## 1. 简介
|
||||||
* 这是一个临时项目,功能很简单,全屏轮播几张图片
|
|
||||||
|
|
||||||
|
本项目是一个运行在 Android 设备上的仪表盘(Dashboard)客户端。
|
||||||
|
|
||||||
|
其主要功能是作为一个**仪表盘(Dashboard)**的展示端:它通过全屏 `WebView` 加载并展示一个远程的网页仪表盘,并根据服务器数据的配置,在预定的时间通过串口与外部硬件设备进行通信。
|
||||||
|
|
||||||
## TODO
|
## 2. 主要功能
|
||||||
|
|
||||||
* 可以指定本地和网络图片
|
* **网页仪表盘展示**:通过 `WebView` 全屏展示一个可配置的远程 URL。
|
||||||
|
* **远程配置**:应用会定期从服务器获取 `config.json` 文件,动态更新其行为。
|
||||||
|
* **定时串口指令**:根据 `config.json` 中的计划,使用 `AlarmManager` 在精确的时间点发送十六进制(Hex)串口指令。
|
||||||
|
* **开机自启**:设备启动后,应用会自动运行。
|
||||||
|
* **网络重试机制**:当加载仪表盘主页失败时,应用会自动进行延时重试。
|
||||||
|
|
||||||
|
## 3. 工作流程
|
||||||
|
|
||||||
|
1. **启动**:应用启动后,进入全屏模式并加载在设置中配置的远程网页 URL。
|
||||||
|
2. **加载配置**:应用会定期从服务器的 `/data/config.json` 路径下拉取配置文件。
|
||||||
|
3. **解析与调度**:解析 `config.json` 中定义的串口设备路径、波特率以及多个定时指令。
|
||||||
|
4. **设置定时任务**:使用 Android 的 `AlarmManager` 为每一条指令安排一个在未来特定时间触发的广播。
|
||||||
|
5. **执行指令**:当预定时间到达,`CommandBroadcastReceiver` 被唤醒,并通过 `SerialControlDevices` 类向指定的串口发送 Hex 指令。
|
||||||
|
|
||||||
|
## 4. 配置
|
||||||
|
|
||||||
|
### 4.1 仪表盘 URL
|
||||||
|
|
||||||
|
在应用的设置界面中,可以配置仪表盘的主 URL,例如 `http://192.168.1.100:8000/Dashboard`。应用会自动加载该 URL 下的 `index.html`。
|
||||||
|
|
||||||
|
### 4.2 串口指令 (config.json)
|
||||||
|
|
||||||
|
应用会从 `[主 URL]/data/config.json` 加载串口指令。`config.json` 的格式如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"DevicePath": "/dev/ttyS2",
|
||||||
|
"Baud": 9600,
|
||||||
|
"Commands": [
|
||||||
|
{
|
||||||
|
"Time": "08:00",
|
||||||
|
"Hex": "AABB0101CCDD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Time": "18:00",
|
||||||
|
"Hex": "AABB0100CCDD"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* `DevicePath`: 串口设备节点路径。
|
||||||
|
* `Baud`: 串口通信的波特率。
|
||||||
|
* `Commands`: 一个指令数组,包含多个定时任务。
|
||||||
|
* `Time`: 指令每天执行的时间(24小时制)。
|
||||||
|
* `Hex`: 需要发送的十六进制指令字符串。
|
||||||
|
|
||||||
|
## 5. 如何构建
|
||||||
|
|
||||||
|
这是一个标准的 Android Gradle 项目。使用 Android Studio 打开即可,或在命令行中执行以下命令构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建 Release 版本的 APK
|
||||||
|
./gradlew clean assembleRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. TODO
|
||||||
|
|
||||||
|
* [ ] 恢复并优化图片轮播功能,支持本地与网络图片。
|
||||||
|
* [ ] 提供更丰富的远程配置选项(如轮播间隔、动画效果等)。
|
||||||
|
* [ ] 增加 Web 与客户端通过 Javascript Interface 的双向交互。
|
||||||
|
|||||||
@@ -1,8 +1 @@
|
|||||||
include ':app_dashboard'
|
include ':app_dashboard'
|
||||||
//include ':utils'
|
|
||||||
//project(':utils').projectDir = new File("mod_utils/utils")
|
|
||||||
//include ':signageapi'
|
|
||||||
//project(':signageapi').projectDir = new File("mod_signageapi/signageapi")
|
|
||||||
//include ':serialport'
|
|
||||||
//project(':serialport').projectDir = new File("mod_serialport/serialport")
|
|
||||||
//include ':dashboard'
|
|
||||||
|
|||||||