diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 7062b20..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,9 +0,0 @@
-[submodule "mod_utils"]
- path = mod_utils
- url = git@192.168.196.173:ykbox/mod_utils.git
-[submodule "mod_signageapi"]
- path = mod_signageapi
- url = git@192.168.196.173:ykbox/mod_signageapi.git
-[submodule "mod_serialport"]
- path = mod_serialport
- url = git@192.168.196.173:ykbox/mod_serialport.git
diff --git a/app_dashboard/build.gradle b/app_dashboard/build.gradle
index 542961e..29ce32b 100644
--- a/app_dashboard/build.gradle
+++ b/app_dashboard/build.gradle
@@ -48,6 +48,7 @@ dependencies {
implementation libs.utilcodex
+ implementation libs.okhttp
implementation libs.banner
implementation libs.glide
annotationProcessor libs.glidecompiler
diff --git a/app_dashboard/src/main/AndroidManifest.xml b/app_dashboard/src/main/AndroidManifest.xml
index fcaa8b9..c9a9d17 100644
--- a/app_dashboard/src/main/AndroidManifest.xml
+++ b/app_dashboard/src/main/AndroidManifest.xml
@@ -12,17 +12,17 @@
android:theme="@style/Theme.Dashboard"
android:usesCleartextTraffic="true">
@@ -31,7 +31,7 @@
@@ -42,6 +42,11 @@
+
+
\ No newline at end of file
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/BootBroadcastReceiver.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/BootBroadcastReceiver.java
index bb71d3b..7530753 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/BootBroadcastReceiver.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/BootBroadcastReceiver.java
@@ -4,6 +4,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import cn.ykbox.dashboard.activity.StartActivity;
+
public class BootBroadcastReceiver extends BroadcastReceiver {
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/ConfigReader.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/ConfigReader.java
index 8abbed4..c8dfad6 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/ConfigReader.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/ConfigReader.java
@@ -1,3 +1,5 @@
+package cn.ykbox.dashboard;
+
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
@@ -13,22 +15,19 @@ import java.util.concurrent.TimeUnit;
public class ConfigReader {
private static final String TAG = "ConfigReader";
private static final String PREFS_NAME = "AppConfig";
- private static final String POWER_OFF_TIME_KEY = "PowerOffTVTime";
+ private static final String SERIAL_CONFIG_KEY = "SerialConfig";
private Context context;
- private String configUrl;
private OkHttpClient httpClient;
private OnConfigLoadListener listener;
- // 回调接口
public interface OnConfigLoadListener {
- void onConfigLoaded(String powerOffTime);
+ void onConfigLoaded();
void onConfigLoadFailed(String error);
}
- public ConfigReader(Context context, String configUrl) {
+ public ConfigReader(Context context) {
this.context = context;
- this.configUrl = configUrl;
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
@@ -40,56 +39,40 @@ public class ConfigReader {
this.listener = listener;
}
- // 异步读取配置文件
- public void loadConfig() {
- new ConfigLoadTask().execute();
+ public void loadConfig(String configUrl) {
+ new ConfigLoadTask().execute(configUrl);
}
- // 从本地获取保存的PowerOffTVTime
- public String getSavedPowerOffTime() {
+ public String getSavedSerialConfig() {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
- return prefs.getString(POWER_OFF_TIME_KEY, "23:00"); // 默认值
+ return prefs.getString(SERIAL_CONFIG_KEY, "{}");
}
- // 保存PowerOffTVTime到本地
- private void savePowerOffTime(String powerOffTime) {
+ private void saveSerialConfig(String serialJsonString) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
- editor.putString(POWER_OFF_TIME_KEY, powerOffTime);
+ editor.putString(SERIAL_CONFIG_KEY, serialJsonString);
editor.apply();
- Log.d(TAG, "PowerOffTVTime saved: " + powerOffTime);
+ Log.d(TAG, "Serial config saved: " + serialJsonString);
}
- // 异步任务类
- private class ConfigLoadTask extends AsyncTask {
+ private class ConfigLoadTask extends AsyncTask {
private String errorMessage = null;
@Override
- protected String doInBackground(Void... voids) {
+ protected JSONObject doInBackground(String... params) {
+ String configUrl = params[0];
try {
- // 创建HTTP请求
- Request request = new Request.Builder()
- .url(configUrl)
- .addHeader("Accept", "application/json")
- .build();
-
- // 执行请求
+ Request request = new Request.Builder().url(configUrl).addHeader("Accept", "application/json").build();
Response response = httpClient.newCall(request).execute();
-
if (response.isSuccessful() && response.body() != null) {
String jsonString = response.body().string();
Log.d(TAG, "Received JSON: " + jsonString);
-
- // 解析JSON
- JSONObject jsonObject = new JSONObject(jsonString);
- String powerOffTime = jsonObject.getString("PowerOffTVTime");
-
- return powerOffTime;
+ return new JSONObject(jsonString);
} else {
errorMessage = "HTTP请求失败,状态码: " + response.code();
return null;
}
-
} catch (IOException e) {
errorMessage = "网络连接失败: " + e.getMessage();
Log.e(TAG, errorMessage, e);
@@ -102,17 +85,27 @@ public class ConfigReader {
}
@Override
- protected void onPostExecute(String powerOffTime) {
- if (powerOffTime != null) {
- // 保存到本地
- savePowerOffTime(powerOffTime);
+ protected void onPostExecute(JSONObject jsonObject) {
+ if (jsonObject != null) {
+ try {
+ if (jsonObject.has("Serial")) {
+ JSONObject serialObject = jsonObject.getJSONObject("Serial");
+ saveSerialConfig(serialObject.toString());
- // 通知回调
- if (listener != null) {
- listener.onConfigLoaded(powerOffTime);
+ if (listener != null) {
+ listener.onConfigLoaded();
+ }
+ } else {
+ throw new JSONException("JSON中缺少 'Serial' 字段");
+ }
+ } catch (JSONException e) {
+ String error = "解析JSON字段时出错: " + e.getMessage();
+ Log.e(TAG, error, e);
+ if (listener != null) {
+ listener.onConfigLoadFailed(error);
+ }
}
} else {
- // 通知错误
if (listener != null) {
listener.onConfigLoadFailed(errorMessage != null ? errorMessage : "未知错误");
}
@@ -120,7 +113,6 @@ public class ConfigReader {
}
}
- // 释放资源
public void release() {
if (httpClient != null) {
httpClient.dispatcher().executorService().shutdown();
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/ScheduledCommandTimer .java b/app_dashboard/src/main/java/cn/ykbox/dashboard/ScheduledCommandTimer .java
deleted file mode 100644
index 0dfab48..0000000
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/ScheduledCommandTimer .java
+++ /dev/null
@@ -1,249 +0,0 @@
-public class ScheduledCommandTimer {
- private static final String TAG = "ScheduledCommandTimer";
-
- private Handler handler;
- private Runnable timerRunnable;
- private boolean isTimerRunning = false;
-
- // 配置参数
- private long checkInterval = 10 * 1000; // 10秒检查间隔
- private int targetHour = 14; // 目标时间:小时
- private int targetMinute = 30; // 目标时间:分钟
- private long commandInterval = 10 * 1000; // 命令发送间隔10秒
- private int maxCommandCount = 3; // 最多发送次数
-
- // 状态变量
- private int commandSentCount = 0;
- private boolean hasReachedTargetTime = false;
- private long firstCommandTime = 0;
-
- // 回调接口
- private CommandCallback commandCallback;
- private TimerStatusCallback statusCallback;
-
- // 回调接口定义
- public interface CommandCallback {
- void onCommandExecute(int currentCount, int totalCount);
- }
-
- public interface TimerStatusCallback {
- void onTimerStarted();
- void onTimerStopped();
- void onTargetTimeReached();
- void onCommandSequenceCompleted();
- void onTimeCheck(int currentHour, int currentMinute);
- }
-
- // 构造函数
- public ScheduledCommandTimer() {
- initTimer();
- }
-
- public ScheduledCommandTimer(int targetHour, int targetMinute) {
- this.targetHour = targetHour;
- this.targetMinute = targetMinute;
- initTimer();
- }
-
- // 建造者模式配置类
- public static class Builder {
- private ScheduledCommandTimer timer = new ScheduledCommandTimer();
-
- public Builder setTargetTime(int hour, int minute) {
- timer.targetHour = hour;
- timer.targetMinute = minute;
- return this;
- }
-
- public Builder setCheckInterval(long intervalMs) {
- timer.checkInterval = intervalMs;
- return this;
- }
-
- public Builder setCommandInterval(long intervalMs) {
- timer.commandInterval = intervalMs;
- return this;
- }
-
- public Builder setMaxCommandCount(int count) {
- timer.maxCommandCount = count;
- return this;
- }
-
- public Builder setCommandCallback(CommandCallback callback) {
- timer.commandCallback = callback;
- return this;
- }
-
- public Builder setStatusCallback(TimerStatusCallback callback) {
- timer.statusCallback = callback;
- return this;
- }
-
- public ScheduledCommandTimer build() {
- timer.initTimer();
- return timer;
- }
- }
-
- private void initTimer() {
- handler = new Handler(Looper.getMainLooper());
- timerRunnable = new Runnable() {
- @Override
- public void run() {
- checkTimeAndSendCommand();
-
- if (isTimerRunning) {
- handler.postDelayed(this, checkInterval);
- }
- }
- };
- }
-
- // 启动定时器
- public void start() {
- if (!isTimerRunning) {
- isTimerRunning = true;
- handler.post(timerRunnable);
- Log.d(TAG, "定时器已启动");
-
- if (statusCallback != null) {
- statusCallback.onTimerStarted();
- }
- }
- }
-
- // 停止定时器
- public void stop() {
- if (isTimerRunning) {
- isTimerRunning = false;
- handler.removeCallbacks(timerRunnable);
- Log.d(TAG, "定时器已停止");
-
- if (statusCallback != null) {
- statusCallback.onTimerStopped();
- }
- }
- }
-
- // 重启定时器
- public void restart() {
- stop();
- resetCommandState();
- start();
- }
-
- private void checkTimeAndSendCommand() {
- Calendar now = Calendar.getInstance();
- int currentHour = now.get(Calendar.HOUR_OF_DAY);
- int currentMinute = now.get(Calendar.MINUTE);
-
- Log.d(TAG, String.format("时间检查: %02d:%02d, 目标: %02d:%02d",
- currentHour, currentMinute, targetHour, targetMinute));
-
- if (statusCallback != null) {
- statusCallback.onTimeCheck(currentHour, currentMinute);
- }
-
- // 检查是否到达指定时间
- if (currentHour == targetHour && currentMinute >= targetMinute) {
- if (!hasReachedTargetTime) {
- hasReachedTargetTime = true;
- firstCommandTime = System.currentTimeMillis();
- commandSentCount = 0;
- Log.d(TAG, "到达目标时间,开始发送命令序列");
-
- if (statusCallback != null) {
- statusCallback.onTargetTimeReached();
- }
- }
-
- processCommandSequence();
- } else {
- if (hasReachedTargetTime) {
- resetCommandState();
- }
- }
- }
-
- private void processCommandSequence() {
- long currentTime = System.currentTimeMillis();
- long elapsedTime = currentTime - firstCommandTime;
-
- // 检查是否在30秒窗口内且未超过最大发送次数
- if (elapsedTime <= 30 * 1000 && commandSentCount < maxCommandCount) {
- long expectedCommandTime = firstCommandTime + (commandSentCount * commandInterval);
-
- if (currentTime >= expectedCommandTime) {
- commandSentCount++;
- Log.d(TAG, String.format("执行命令 %d/%d, 已用时: %d毫秒",
- commandSentCount, maxCommandCount, elapsedTime));
-
- if (commandCallback != null) {
- commandCallback.onCommandExecute(commandSentCount, maxCommandCount);
- }
-
- // 检查是否完成所有命令
- if (commandSentCount >= maxCommandCount) {
- Log.d(TAG, "命令序列执行完成");
- if (statusCallback != null) {
- statusCallback.onCommandSequenceCompleted();
- }
- resetCommandState();
- }
- }
- } else if (elapsedTime > 30 * 1000) {
- Log.d(TAG, "30秒窗口超时,重置状态");
- resetCommandState();
- }
- }
-
- private void resetCommandState() {
- hasReachedTargetTime = false;
- commandSentCount = 0;
- firstCommandTime = 0;
- Log.d(TAG, "命令发送状态已重置");
- }
-
- // 获取当前状态
- public boolean isRunning() {
- return isTimerRunning;
- }
-
- public boolean hasReachedTarget() {
- return hasReachedTargetTime;
- }
-
- public int getCommandSentCount() {
- return commandSentCount;
- }
-
- public long getRemainingTime() {
- if (!hasReachedTargetTime) return -1;
- long elapsedTime = System.currentTimeMillis() - firstCommandTime;
- return Math.max(0, 30 * 1000 - elapsedTime);
- }
-
- // 配置方法
- public void setTargetTime(int hour, int minute) {
- this.targetHour = hour;
- this.targetMinute = minute;
- resetCommandState();
- }
-
- public void setCommandCallback(CommandCallback callback) {
- this.commandCallback = callback;
- }
-
- public void setStatusCallback(TimerStatusCallback callback) {
- this.statusCallback = callback;
- }
-
- // 释放资源
- public void destroy() {
- stop();
- commandCallback = null;
- statusCallback = null;
- handler = null;
- }
-}
\ No newline at end of file
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BaseActivity.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BaseActivity.java
index 0255a4f..2213be8 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BaseActivity.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BaseActivity.java
@@ -1,4 +1,4 @@
-package cn.ykbox.dashboard;
+package cn.ykbox.dashboard.activity;
import android.os.Build;
import android.os.Bundle;
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BuildingDashboardActivity.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BuildingDashboardActivity.java
index 68f5d6f..a87646e 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BuildingDashboardActivity.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/BuildingDashboardActivity.java
@@ -1,66 +1,69 @@
-package cn.ykbox.dashboard;
+package cn.ykbox.dashboard.activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebViewClient;
+import android.widget.Toast;
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.preference.PreferenceManager;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Calendar;
+
+import cn.ykbox.dashboard.ConfigReader;
import cn.ykbox.dashboard.databinding.ActivityBuildingDashboardBinding;
+import cn.ykbox.dashboard.receiver.CommandBroadcastReceiver;
public class BuildingDashboardActivity extends FullscreenActivity {
private final static String TAG = "DashboardActivity";
- /**
- * Whether or not the system UI should be auto-hidden after
- * {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
- */
private static final boolean AUTO_HIDE = true;
-
- /**
- * If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
- * user interaction before hiding the system UI.
- */
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
+ private static final long CONFIG_LOAD_INTERVAL = 10 * 60 * 1000; // 10 minutes
+ private static final int MAX_COMMAND_ALARMS = 20; // 支持的最大闹钟指令数量
- private ScheduledCommandTimer timer;
+ private String mainUrl;
+ private String configUrl;
private ConfigReader configReader;
+ private String lastAppliedSerialConfig = null;
+
+ // --- Periodic Config Loading ---
+ private final Handler configLoadHandler = new Handler(Looper.getMainLooper());
+ private Runnable configLoadRunnable;
- /**
- * Touch listener to use for in-layout UI controls to delay hiding the
- * system UI. This is to prevent the jarring behavior of controls going away
- * while interacting with activity UI.
- */
- private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
- @Override
- public boolean onTouch(View view, MotionEvent motionEvent) {
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_DOWN:
- if (AUTO_HIDE) {
- delayedHide(AUTO_HIDE_DELAY_MILLIS);
- }
- break;
- case MotionEvent.ACTION_UP:
- view.performClick();
- break;
- default:
- break;
- }
- return false;
- }
- };
private ActivityBuildingDashboardBinding binding;
- /// ActivityResultLauncher 用于处理设置页面的返回结果
private ActivityResultLauncher settingsLauncher;
+ private final View.OnTouchListener mDelayHideTouchListener = (view, motionEvent) -> {
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (AUTO_HIDE) {
+ delayedHide(AUTO_HIDE_DELAY_MILLIS);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ view.performClick();
+ break;
+ default:
+ break;
+ }
+ return false;
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
binding = ActivityBuildingDashboardBinding.inflate(getLayoutInflater());
@@ -69,184 +72,162 @@ public class BuildingDashboardActivity extends FullscreenActivity {
super.setViews(binding.webview, binding.fullscreenContentControls);
super.onCreate(savedInstanceState);
- // 初始化 ActivityResultLauncher
initSettingsLauncher();
- setupConfig();
+ initListener();
initWebView();
- initTimer();
-
- // Upon interacting with UI controls, delay any scheduled hide()
- // operations to prevent the jarring behavior of controls going away
- // while interacting with the UI.
- binding.settingsButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- // 打开设置界面
- openSettingsActivity();
- }
- });
+ initConfigLoader();
}
@Override
protected void onResume() {
super.onResume();
- if (timer != null) {
- timer.start();
- }
-
SharedPreferences pre = PreferenceManager.getDefaultSharedPreferences(this);
String url = pre.getString("k_url", "http://10.1.58.176:8002");
- binding.webview.loadUrl(url); // 加载网页
+ mainUrl = url + "/index.html";
+ configUrl = url + "/data/config.json";
+
+ configLoadHandler.post(configLoadRunnable);
+ binding.webview.loadUrl(mainUrl);
}
@Override
protected void onPause() {
super.onPause();
- if (timer != null) {
- timer.stop();
- }
+ configLoadHandler.removeCallbacks(configLoadRunnable);
}
@Override
protected void onDestroy() {
super.onDestroy();
- if (timer != null) {
- timer.destroy();
- }
if (configReader != null) {
configReader.release();
}
}
- /**
- * 初始化设置页面的 ActivityResultLauncher
- */
+ private void initListener() {
+ binding.settingsButton.setOnClickListener(view -> openSettingsActivity());
+ }
+
private void initSettingsLauncher() {
settingsLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
- result -> {
-
- }
+ result -> {}
);
}
- /**
- * 打开设置页面
- */
+ private void initWebView() {
+ binding.webview.getSettings().setJavaScriptEnabled(true);
+ binding.webview.setWebViewClient(new WebViewClient());
+ }
+
+ private void initConfigLoader() {
+ configLoadRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!TextUtils.isEmpty(configUrl)) {
+ Log.d(TAG, "Periodically loading config...");
+ if (configReader != null) {
+ configReader.loadConfig(configUrl);
+ }
+ } else {
+ Log.e(TAG, "configUrl is empty, skipping config load.");
+ }
+ configLoadHandler.postDelayed(this, CONFIG_LOAD_INTERVAL);
+ }
+ };
+
+ if (configReader == null) {
+ configReader = new ConfigReader(this);
+ configReader.setOnConfigLoadListener(new ConfigReader.OnConfigLoadListener() {
+ @Override
+ public void onConfigLoaded() {
+ Log.i(TAG, "Config loaded successfully. Refreshing alarms.");
+ String serialConfig = configReader.getSavedSerialConfig();
+ refreshAlarms(serialConfig);
+ }
+
+ @Override
+ public void onConfigLoadFailed(String error) {
+ Log.e(TAG, "Config load failed: " + error + ". Refreshing alarms with local config.");
+ String serialConfig = configReader.getSavedSerialConfig();
+ refreshAlarms(serialConfig);
+ }
+ });
+ }
+ }
+
+ private void refreshAlarms(String serialConfigJson) {
+ if (serialConfigJson != null && serialConfigJson.equals(lastAppliedSerialConfig)) {
+ Log.d(TAG, "Serial config has not changed. Skipping alarm refresh.");
+ return;
+ }
+
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+
+ for (int i = 0; i < MAX_COMMAND_ALARMS; i++) {
+ Intent intent = new Intent(this, CommandBroadcastReceiver.class);
+ intent.setAction(CommandBroadcastReceiver.ACTION_SEND_COMMAND);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, i, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE);
+ if (pendingIntent != null) {
+ alarmManager.cancel(pendingIntent);
+ pendingIntent.cancel();
+ }
+ }
+
+ try {
+ JSONObject serialConfig = new JSONObject(serialConfigJson);
+ String portPath = serialConfig.optString("DevicePath", "/dev/ttyS2");
+ int baudRate = serialConfig.optInt("Baud", 9600);
+
+ if (serialConfig.has("Commands")) {
+ JSONArray commands = serialConfig.getJSONArray("Commands");
+ for (int i = 0; i < commands.length() && i < MAX_COMMAND_ALARMS; i++) {
+ JSONObject command = commands.getJSONObject(i);
+ String time = command.getString("Time");
+ String hex = command.getString("Hex");
+
+ String[] timeParts = time.split(":");
+ int hour = Integer.parseInt(timeParts[0]);
+ int minute = Integer.parseInt(timeParts[1]);
+
+ Intent intent = new Intent(this, CommandBroadcastReceiver.class);
+ intent.setAction(CommandBroadcastReceiver.ACTION_SEND_COMMAND);
+ 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);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(System.currentTimeMillis());
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ calendar.set(Calendar.MINUTE, minute);
+ calendar.set(Calendar.SECOND, 0);
+
+ if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
+ calendar.add(Calendar.DAY_OF_YEAR, 1);
+ }
+
+ alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent);
+ Log.d(TAG, "Set repeating alarm for " + time + " with command " + hex + " on port " + portPath + " at " + baudRate + " baud");
+ }
+ }
+
+ this.lastAppliedSerialConfig = serialConfigJson;
+ Log.d(TAG, "Alarms refreshed and config backed up.");
+
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to parse or schedule commands", e);
+ }
+ }
+
private void openSettingsActivity() {
Intent intent = new Intent(this, SettingsActivity.class);
settingsLauncher.launch(intent);
}
- private void setupConfig(String configUrl) {
- // 初始化配置读取器
- configReader = new ConfigReader(this, configUrl);
- configReader.setOnConfigLoadListener(new ConfigReader.OnConfigLoadListener() {
- @Override
- public void onConfigLoaded(String powerOffTime) {
- // 配置加载成功
- Log.i("MainActivity", "PowerOffTVTime加载成功: " + powerOffTime);
- // 可以在这里更新UI或执行其他操作
- }
-
- @Override
- public void onConfigLoadFailed(String error) {
- // 配置加载失败
- Log.e("MainActivity", "配置加载失败: " + error);
-
- // 使用本地保存的配置
- String savedTime = configReader.getSavedPowerOffTime();
- Log.i("MainActivity", "使用本地保存的PowerOffTVTime: " + savedTime);
- }
- });
-
- // 加载配置
- configReader.loadConfig();
-
- // 或者直接获取本地保存的值
- String localPowerOffTime = configReader.getSavedPowerOffTime();
- Log.i("MainActivity", "本地PowerOffTVTime: " + localPowerOffTime);
- }
-
- private void initWebView() {
- binding.webview.getSettings().setJavaScriptEnabled(true); // 启用 JavaScript
- binding.webview.setWebViewClient(new WebViewClient()); // 防止跳转到外部浏览器
- }
-
- private void initTimer() {
- // 方式1:使用建造者模式
- timer = new ScheduledCommandTimer.Builder()
- .setTargetTime(14, 30) // 设置目标时间为14:30
- .setCheckInterval(10 * 1000) // 10秒检查一次
- .setCommandInterval(10 * 1000) // 命令间隔10秒
- .setMaxCommandCount(3) // 最多发送3次
- .setCommandCallback(new ScheduledCommandTimer.CommandCallback() {
- @Override
- public void onCommandExecute(int currentCount, int totalCount) {
- // 执行你的命令逻辑
- executeCommand(currentCount, totalCount);
- }
- })
- .setStatusCallback(new ScheduledCommandTimer.TimerStatusCallback() {
- @Override
- public void onTimerStarted() {
- Log.d("MainActivity", "定时器启动");
- showToast("定时器已启动");
- }
-
- @Override
- public void onTimerStopped() {
- Log.d("MainActivity", "定时器停止");
- showToast("定时器已停止");
- }
-
- @Override
- public void onTargetTimeReached() {
- Log.d("MainActivity", "到达目标时间");
- showToast("到达目标时间,开始执行命令");
- }
-
- @Override
- public void onCommandSequenceCompleted() {
- Log.d("MainActivity", "命令序列完成");
- showToast("所有命令执行完成");
- }
-
- @Override
- public void onTimeCheck(int currentHour, int currentMinute) {
- // 可选:更新UI显示当前时间
- updateTimeDisplay(currentHour, currentMinute);
- }
- })
- .build();
-
- // 方式2:直接构造
- // timer = new ScheduledCommandTimer(14, 30);
- // timer.setCommandCallback(commandCallback);
- // timer.setStatusCallback(statusCallback);
- }
-
- private void executeCommand(int currentCount, int totalCount) {
- // 在这里实现你的具体命令逻辑
- Log.i("MainActivity", String.format("执行命令 %d/%d", currentCount, totalCount));
-
- // 示例:发送网络请求
- // sendNetworkRequest();
-
- // 示例:调用系统服务
- // callSystemService();
-
- // 示例:发送广播
- // sendBroadcast();
-
- runOnUiThread(() -> {
- showToast(String.format("执行命令 %d/%d", currentCount, totalCount));
- });
- }
-
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
-
-}
\ No newline at end of file
+}
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/FullscreenActivity.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/FullscreenActivity.java
index 44f3160..0ed32c8 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/FullscreenActivity.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/FullscreenActivity.java
@@ -1,4 +1,4 @@
-package cn.ykbox.dashboard;
+package cn.ykbox.dashboard.activity;
import android.annotation.SuppressLint;
import android.os.Build;
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/GalleryActivity.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/GalleryActivity.java
index 7b3b65b..59756b0 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/GalleryActivity.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/GalleryActivity.java
@@ -1,4 +1,4 @@
-package cn.ykbox.dashboard;
+package cn.ykbox.dashboard.activity;
import android.content.Context;
import android.os.Bundle;
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/SettingsActivity.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/SettingsActivity.java
index 4e17d66..f5d3726 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/SettingsActivity.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/SettingsActivity.java
@@ -1,4 +1,4 @@
-package cn.ykbox.dashboard;
+package cn.ykbox.dashboard.activity;
import android.os.Bundle;
@@ -6,6 +6,8 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
+import cn.ykbox.dashboard.R;
+
public class SettingsActivity extends AppCompatActivity {
@Override
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/StartActivity.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/StartActivity.java
index 8e2b0ba..87934dc 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/StartActivity.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/activity/StartActivity.java
@@ -1,4 +1,4 @@
-package cn.ykbox.dashboard;
+package cn.ykbox.dashboard.activity;
import android.content.Context;
import android.content.Intent;
@@ -11,6 +11,8 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+import cn.ykbox.dashboard.R;
+
public class StartActivity extends AppCompatActivity {
private Context mContext;
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/receiver/CommandBroadcastReceiver.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/receiver/CommandBroadcastReceiver.java
new file mode 100644
index 0000000..40ea0a8
--- /dev/null
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/receiver/CommandBroadcastReceiver.java
@@ -0,0 +1,35 @@
+package cn.ykbox.dashboard.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import cn.ykbox.dashboard.serial.SerialControlDevices;
+
+public class CommandBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "CommandReceiver";
+ 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_PORT_PATH = "EXTRA_PORT_PATH";
+ public static final String EXTRA_BAUD_RATE = "EXTRA_BAUD_RATE"; // 新增:波特率的 Extra Key
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null && ACTION_SEND_COMMAND.equals(intent.getAction())) {
+ String hexCommand = intent.getStringExtra(EXTRA_COMMAND_HEX);
+ String portPath = intent.getStringExtra(EXTRA_PORT_PATH);
+ // 新增:从 Intent 中获取波特率,如果不存在则默认为 9600
+ int baudRate = intent.getIntExtra(EXTRA_BAUD_RATE, 9600);
+
+ if (hexCommand != null && !hexCommand.isEmpty() && portPath != null && !portPath.isEmpty()) {
+ Log.d(TAG, "Received alarm to send command '" + hexCommand + "' to port '" + portPath + "' at " + baudRate + " baud");
+ // 使用新的静态方法发送指令
+ boolean success = SerialControlDevices.sendCommand(portPath, baudRate, hexCommand);
+ if (!success) {
+ Log.e(TAG, "Failed to send command via broadcast receiver.");
+ }
+ }
+ }
+ }
+}
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/serial/SerialControlDevices.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/serial/SerialControlDevices.java
index be346c6..ee41528 100644
--- a/app_dashboard/src/main/java/cn/ykbox/dashboard/serial/SerialControlDevices.java
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/serial/SerialControlDevices.java
@@ -1,72 +1,53 @@
-/*************************************************************************
- * 版权所有 (C) 2024 宁波升维信息技术有限公司. 保留所有权利.
- *
- * 本软件是由 宁波升维信息技术有限公司 开发的。
- * 未经版权所有者明确授权,任何人不得使用、复制、修改、分发本软件及其相关文档。
- * 本软件包含机密和专有信息,未经授权不得向任何第三方披露。
- *************************************************************************/
+package cn.ykbox.dashboard.serial;
+
+import android.util.Log;
+import tp.xmaihh.serialport.SerialHelper;
/**
- * @description: 通过串口设置班牌的功能
+ * @description: 通过串口设置班牌的功能。本类提供一个静态方法用于发送单次命令。
* @author: Hu Zhang
* @date: 2024/1/5
**/
-
-package com.nb6868.classtv.serial;
-
-import android.os.Handler;
-import android.os.Message;
-import cn.ykbox.utils.Log;
-
-import cn.ykbox.signageapi.utils.ByteArrayUtil;
-import tp.xmaihh.serialport.SerialHelper;
-import tp.xmaihh.serialport.bean.ComBean;
-
-
-public class SerialControlDevices extends SerialHelper {
+public class SerialControlDevices {
private static final String TAG = "SerialControlDevices";
+ /**
+ * 私有构造函数,防止外部实例化此类。
+ */
+ private SerialControlDevices() {}
- private OnErrorListener mErrorListener = null;
-
- public static synchronized SerialControlDevices getInstance() {
- if (control == null) {
- control = new SerialControlDevices();
+ /**
+ * 打开指定串口,发送十六进制命令,然后立即关闭串口。
+ *
+ * @param portPath 串口的设备路径 (例如, "/dev/ttyS2").
+ * @param hexCommand 要发送的十六进制格式的命令字符串.
+ * @return 如果命令发送成功则返回 true, 否则返回 false.
+ */
+ public static boolean sendCommand(String portPath, int baud, String hexCommand) {
+ if (portPath == null || portPath.isEmpty() || hexCommand == null || hexCommand.isEmpty()) {
+ Log.e(TAG, "Port path or command is empty.");
+ return false;
}
- return control;
- }
- private SerialControlDevices() {
- super("/dev/ttyS3", 9600);
- try
- {
- super.open();
- Log.i(TAG,"串口打开成功");
+ // 每次调用都创建一个新的 SerialHelper 实例
+ SerialHelper serialHelper = new SerialHelper(portPath, baud) {
+ @Override
+ protected void onDataReceived(tp.xmaihh.serialport.bean.ComBean ComRecData) {
+ // 可以在这里处理返回的数据,但对于单次发送任务,通常不需要
+ // Log.d(TAG, "Received data from " + portPath + ": " + new String(ComRecData.bRec));
+ }
+ };
+
+ try {
+ serialHelper.open();
+ serialHelper.sendHex(hexCommand);
+ Log.d(TAG, "Successfully sent command '" + hexCommand + "' to port '" + portPath + "'");
+ return true;
} catch (Exception e) {
- Log.e(TAG, "串口打开失败:" + e.getMessage());
+ Log.e(TAG, "Error sending command to port " + portPath, e);
+ return false;
+ } finally {
+ serialHelper.close();
}
}
-
- public void setErrorListener(OnErrorListener listener) {
- this.mErrorListener = listener;
- }
-
- @Override
- protected void onDataReceived(ComBean ComRecData) {
- Log.d(TAG,"onDataReceived: "+ ByteArrayUtil.toHexString(ComRecData.bRec));
-
- String serialData = ByteArrayUtil.toHexString(ComRecData.bRec);
- Message msg = workHandler.obtainMessage();
- msg.what = SERIAL_MESSAGE;
- msg.obj = serialData;
- workHandler.sendMessage(msg);
- }
-
- public void powerOffTV() {
- sendHex("AABB0600000001060304");
- }
-
- public interface OnErrorListener {
- void onError(int error);
- }
-}
+}
\ No newline at end of file
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/utils/ByteArrayUtil.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/utils/ByteArrayUtil.java
new file mode 100644
index 0000000..8c86da0
--- /dev/null
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/utils/ByteArrayUtil.java
@@ -0,0 +1,84 @@
+package cn.ykbox.dashboard.utils;
+
+import android.text.TextUtils;
+
+import java.nio.charset.StandardCharsets;
+
+public class ByteArrayUtil {
+
+ public static String toHexString(final byte[] bytes) {
+ return toHexString(bytes, "");
+ }
+
+ public static String toHexString(final byte[] bytes, String sep) {
+ return toHexString(bytes, sep, true);
+ }
+
+ /**
+ * byte 数组转化为16进制字符串
+ * @param bytes 待转换的数据
+ * @param sep 字符间插入的的符号
+ * @param upper 是否转成大写
+ * @return 格式为 0102AABBCC 的字符串
+ */
+ public static String toHexString(final byte[] bytes, String sep, boolean upper) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ sb.append(String.format("%02x", bytes[i] & 0xff));
+ if (!TextUtils.isEmpty(sep) && i != bytes.length - 1)
+ sb.append(sep);
+ }
+ if(upper)
+ return sb.toString().toUpperCase();
+ else
+ return sb.toString().toLowerCase();
+ }
+
+ public static String toHexString(final String input) {
+ return toHexString(input);
+ }
+
+ public static String toHexString(final String input, String sep) {
+ return toHexString(input, sep, true);
+ }
+
+ public static String toHexString(final String input, String sep, boolean upper) {
+ byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
+ return toHexString(bytes, sep, upper);
+ }
+
+ // 字符串转换为 16 进制
+ public static String stringToHex(String input) {
+ StringBuilder hexString = new StringBuilder();
+ for (char character : input.toCharArray()) {
+ hexString.append(Integer.toHexString((int) character));
+ }
+ return hexString.toString();
+ }
+
+ /**
+ * 16进制字符串转化为 byte 数组
+ * @param s 待转换的16进制字符串
+ * @return byte 数据
+ */
+ public static byte[] toByteArray(String s) {
+ // 去掉所有空白字符
+ s = s.replaceAll("\\s", "");
+ int len = s.length();
+
+ // 字符串长度必须为偶数
+ if(len % 2 != 0)
+ return null;
+
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ // 必须为16进制字符
+ int a = Character.digit(s.charAt(i), 16);
+ int b = Character.digit(s.charAt(i+1), 16);
+ if(a == -1 || b == -1 )
+ return null;
+ data[i / 2] = (byte) ((byte) (a << 4) + (byte)b);
+ }
+ return data;
+ }
+}
\ No newline at end of file
diff --git a/app_dashboard/src/main/java/cn/ykbox/dashboard/viewmodel/AppViewModel.java b/app_dashboard/src/main/java/cn/ykbox/dashboard/viewmodel/AppViewModel.java
new file mode 100644
index 0000000..a0ac53a
--- /dev/null
+++ b/app_dashboard/src/main/java/cn/ykbox/dashboard/viewmodel/AppViewModel.java
@@ -0,0 +1,15 @@
+package cn.ykbox.dashboard.viewmodel;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+public class AppViewModel extends ViewModel {
+ private static final String TAG = "AppViewModel";
+
+ public MutableLiveData livePowerOffTvHexString = new MutableLiveData<>();
+ public MutableLiveData liveSerialConfig = new MutableLiveData<>();
+
+ public void updateSerialConfig(String config) {
+ liveSerialConfig.postValue(config);
+ }
+}
diff --git a/app_dashboard/src/main/res/layout/activity_building_dashboard.xml b/app_dashboard/src/main/res/layout/activity_building_dashboard.xml
index 015b309..452a866 100644
--- a/app_dashboard/src/main/res/layout/activity_building_dashboard.xml
+++ b/app_dashboard/src/main/res/layout/activity_building_dashboard.xml
@@ -35,10 +35,15 @@
+
diff --git a/mod_serialport b/mod_serialport
deleted file mode 160000
index bcdf2d3..0000000
--- a/mod_serialport
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit bcdf2d3b5b8516570fce5b6aa23b01a5c6d7f532
diff --git a/mod_signageapi b/mod_signageapi
deleted file mode 160000
index 23cdd57..0000000
--- a/mod_signageapi
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 23cdd575ccffad0ea0b10687c6c18aa774ed822c
diff --git a/mod_utils b/mod_utils
deleted file mode 160000
index afdadcc..0000000
--- a/mod_utils
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit afdadcc93247b8cc983f10c94f39da9a8293f932