定时开关机优化,支持三种模式
This commit is contained in:
@@ -1,13 +1,10 @@
|
|||||||
package cn.ykbox.dashboard.activity;
|
package cn.ykbox.dashboard.activity;
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.ProgressDialog;
|
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.graphics.Bitmap;
|
||||||
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;
|
||||||
@@ -28,15 +25,9 @@ import androidx.activity.result.ActivityResultLauncher;
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import cn.ykbox.dashboard.alarm.PowerAlarmManager;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
|
|
||||||
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.perferences.PreferenceConfiguration;
|
||||||
import cn.ykbox.dashboard.receiver.CommandBroadcastReceiver;
|
|
||||||
import cn.ykbox.dashboard.serial.SerialPortDetector;
|
import cn.ykbox.dashboard.serial.SerialPortDetector;
|
||||||
|
|
||||||
public class BuildingDashboardActivity extends FullscreenActivity {
|
public class BuildingDashboardActivity extends FullscreenActivity {
|
||||||
@@ -44,22 +35,16 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
|
|
||||||
private static final boolean AUTO_HIDE = true;
|
private static final boolean AUTO_HIDE = true;
|
||||||
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
|
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 Context mContext;
|
private Context mContext;
|
||||||
private String mainUrl;
|
private String mainUrl;
|
||||||
private int retryCount = 0;
|
private int retryCount = 0;
|
||||||
private static final int MAX_RETRY = 99; // 最大重试次数
|
private static final int MAX_RETRY = 99; // 最大重试次数
|
||||||
private static final int RETRY_DELAY = 60000; // 1分钟 = 60000毫秒
|
private static final int RETRY_DELAY = 60000; // 1分钟 = 60000毫秒
|
||||||
private String configUrl;
|
|
||||||
|
|
||||||
private ConfigReader configReader;
|
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
|
||||||
private String lastAppliedSerialConfig = null;
|
|
||||||
|
|
||||||
// --- Periodic Config Loading ---
|
private PowerAlarmManager powerAlarmManager;
|
||||||
private final Handler configLoadHandler = new Handler(Looper.getMainLooper());
|
|
||||||
private Runnable configLoadRunnable;
|
|
||||||
|
|
||||||
private ActivityBuildingDashboardBinding binding;
|
private ActivityBuildingDashboardBinding binding;
|
||||||
private ActivityResultLauncher<Intent> settingsLauncher;
|
private ActivityResultLauncher<Intent> settingsLauncher;
|
||||||
@@ -95,7 +80,17 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
initSettingsLauncher();
|
initSettingsLauncher();
|
||||||
initListener();
|
initListener();
|
||||||
initWebView();
|
initWebView();
|
||||||
initConfigLoader();
|
|
||||||
|
// 获取配置 URL
|
||||||
|
SharedPreferences pre = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
String urlPrefix = pre.getString("k_url_prefix", "http://172.18.22.211:8002/Dashboard");
|
||||||
|
String configUrl = urlPrefix + "/data/config.json";
|
||||||
|
|
||||||
|
// 初始化 PowerAlarmManager
|
||||||
|
powerAlarmManager = new PowerAlarmManager(this, configUrl);
|
||||||
|
powerAlarmManager.start();
|
||||||
|
|
||||||
|
initPreferenceChangeListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -105,14 +100,9 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
SharedPreferences pre = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences pre = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
String urlPrefix = pre.getString("k_url_prefix", "http://172.18.22.211:8002/Dashboard");
|
String urlPrefix = pre.getString("k_url_prefix", "http://172.18.22.211:8002/Dashboard");
|
||||||
String urlPath = pre.getString("k_url_path", "/index.html");
|
String urlPath = pre.getString("k_url_path", "/index.html");
|
||||||
|
|
||||||
mainUrl = urlPrefix + urlPath;
|
mainUrl = urlPrefix + urlPath;
|
||||||
configUrl = urlPrefix + "/data/config.json";
|
|
||||||
|
|
||||||
Log.i(TAG, "Main: " + mainUrl);
|
Log.i(TAG, "Main: " + mainUrl);
|
||||||
Log.i(TAG, "Config: " + configUrl);
|
|
||||||
|
|
||||||
configLoadHandler.post(configLoadRunnable);
|
|
||||||
loadUrlWithRetry();
|
loadUrlWithRetry();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +226,7 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
Log.d("WebView", "加载失败,将在1分钟后重试 (第" + retryCount + "次重试)");
|
Log.d("WebView", "加载失败,将在1分钟后重试 (第" + retryCount + "次重试)");
|
||||||
|
|
||||||
// 使用Handler延迟执行重试
|
// 使用Handler延迟执行重试
|
||||||
configLoadHandler.postDelayed(new Runnable() {
|
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Log.d("WebView", "开始重试加载...");
|
Log.d("WebView", "开始重试加载...");
|
||||||
@@ -254,14 +244,18 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
configLoadHandler.removeCallbacks(configLoadRunnable);
|
// PowerAlarmManager 内部已处理配置加载的暂停
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
if (configReader != null) {
|
if (preferenceChangeListener != null) {
|
||||||
configReader.release();
|
PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||||
|
}
|
||||||
|
if (powerAlarmManager != null) {
|
||||||
|
powerAlarmManager.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,109 +343,25 @@ public class BuildingDashboardActivity extends FullscreenActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initConfigLoader() {
|
private void initPreferenceChangeListener() {
|
||||||
configLoadRunnable = new Runnable() {
|
preferenceChangeListener = (sharedPreferences, key) -> {
|
||||||
@Override
|
if (key.equals("k_power_control_mode") ||
|
||||||
public void run() {
|
key.equals("k_power_on_time") ||
|
||||||
if (!TextUtils.isEmpty(configUrl)) {
|
key.equals("k_power_off_time")) {
|
||||||
Log.d(TAG, "Periodically loading config...");
|
Log.d(TAG, "Power control preference changed: " + key);
|
||||||
if (configReader != null) {
|
runOnUiThread(() -> {
|
||||||
configReader.loadConfig(configUrl);
|
if (key.equals("k_power_control_mode")) {
|
||||||
}
|
int mode = PreferenceConfiguration.getPowerControlMode(BuildingDashboardActivity.this);
|
||||||
|
powerAlarmManager.onModeChanged(mode);
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "configUrl is empty, skipping config load.");
|
// 本地时间变更
|
||||||
}
|
powerAlarmManager.onLocalTimeChanged();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
private void refreshAlarms(String serialConfigJson) {
|
.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
|
||||||
Log.d(TAG, "setExactAndAllowWhileIdle, Set repeating alarm for " + time + " with command " + hex);
|
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
|
||||||
Log.d(TAG, "setExact, Set repeating alarm for " + time + " with command " + hex);
|
|
||||||
} else {
|
|
||||||
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
|
|
||||||
Log.d(TAG, "Set repeating alarm for " + time + " with command " + hex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
private void openSettingsActivity() {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.appcompat.app.ActionBar;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.preference.EditTextPreference;
|
import androidx.preference.EditTextPreference;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ public class SettingsActivity extends AppCompatActivity {
|
|||||||
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);
|
||||||
|
|
||||||
|
ListPreference powerControlModePref = findPreference("k_power_control_mode");
|
||||||
|
EditTextPreference powerOnTimePref = findPreference("k_power_on_time");
|
||||||
|
EditTextPreference powerOffTimePref = findPreference("k_power_off_time");
|
||||||
|
|
||||||
Preference clearDevicePref = findPreference("k_clear_device");
|
Preference clearDevicePref = findPreference("k_clear_device");
|
||||||
if (clearDevicePref != null) {
|
if (clearDevicePref != null) {
|
||||||
clearDevicePref.setOnPreferenceClickListener(this);
|
clearDevicePref.setOnPreferenceClickListener(this);
|
||||||
@@ -55,6 +60,29 @@ public class SettingsActivity extends AppCompatActivity {
|
|||||||
if (testPowerOffPref != null) {
|
if (testPowerOffPref != null) {
|
||||||
testPowerOffPref.setOnPreferenceClickListener(this);
|
testPowerOffPref.setOnPreferenceClickListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (powerControlModePref != null) {
|
||||||
|
powerControlModePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
int mode = Integer.parseInt((String) newValue);
|
||||||
|
boolean isLocalMode = (mode == 2);
|
||||||
|
if (powerOnTimePref != null) {
|
||||||
|
powerOnTimePref.setEnabled(isLocalMode);
|
||||||
|
}
|
||||||
|
if (powerOffTimePref != null) {
|
||||||
|
powerOffTimePref.setEnabled(isLocalMode);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int mode = PreferenceConfiguration.getPowerControlMode(getContext());
|
||||||
|
boolean isLocalMode = (mode == 2);
|
||||||
|
if (powerOnTimePref != null) {
|
||||||
|
powerOnTimePref.setEnabled(isLocalMode);
|
||||||
|
}
|
||||||
|
if (powerOffTimePref != null) {
|
||||||
|
powerOffTimePref.setEnabled(isLocalMode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,357 @@
|
|||||||
|
package cn.ykbox.dashboard.alarm;
|
||||||
|
|
||||||
|
import android.app.AlarmManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import cn.ykbox.dashboard.ConfigReader;
|
||||||
|
import cn.ykbox.dashboard.perferences.PreferenceConfiguration;
|
||||||
|
import cn.ykbox.dashboard.receiver.CommandBroadcastReceiver;
|
||||||
|
import cn.ykbox.dashboard.serial.SerialPortDetector;
|
||||||
|
|
||||||
|
public class PowerAlarmManager {
|
||||||
|
private static final String TAG = "PowerAlarmManager";
|
||||||
|
private static final int MAX_COMMAND_ALARMS = 20;
|
||||||
|
private static final int ALARM_ID_POWER_ON = 0;
|
||||||
|
private static final int ALARM_ID_POWER_OFF = 1;
|
||||||
|
private static final long CONFIG_LOAD_INTERVAL = 10 * 60 * 1000; // 10 minutes
|
||||||
|
|
||||||
|
private static final int POWER_MODE_NONE = 0;
|
||||||
|
private static final int POWER_MODE_REMOTE = 1;
|
||||||
|
private static final int POWER_MODE_LOCAL = 2;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private String lastAppliedSerialConfig = null;
|
||||||
|
private ConfigReader configReader;
|
||||||
|
private String configUrl;
|
||||||
|
private Handler configLoadHandler;
|
||||||
|
private Runnable configLoadRunnable;
|
||||||
|
|
||||||
|
public PowerAlarmManager(Context context, String configUrl) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.configUrl = configUrl;
|
||||||
|
this.configLoadHandler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动管理器,根据当前模式初始化
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
int mode = PreferenceConfiguration.getPowerControlMode(context);
|
||||||
|
Log.d(TAG, "PowerAlarmManager starting, mode: " + mode);
|
||||||
|
|
||||||
|
refreshAlarms();
|
||||||
|
|
||||||
|
if (mode == POWER_MODE_REMOTE) {
|
||||||
|
// 服务器配置模式,初始化 ConfigLoader
|
||||||
|
initConfigLoader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止管理器,释放资源
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
if (configLoadRunnable != null) {
|
||||||
|
configLoadHandler.removeCallbacks(configLoadRunnable);
|
||||||
|
}
|
||||||
|
if (configReader != null) {
|
||||||
|
configReader.release();
|
||||||
|
configReader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模式变更时调用
|
||||||
|
*/
|
||||||
|
public void onModeChanged(int newMode) {
|
||||||
|
Log.d(TAG, "Mode changed to: " + newMode);
|
||||||
|
|
||||||
|
if (newMode == POWER_MODE_REMOTE) {
|
||||||
|
// 切换到服务器配置模式
|
||||||
|
if (configReader == null) {
|
||||||
|
initConfigLoader();
|
||||||
|
}
|
||||||
|
clearServerConfigCache();
|
||||||
|
if (configLoadRunnable != null) {
|
||||||
|
configLoadHandler.post(configLoadRunnable);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 切换到其他模式,停止配置加载
|
||||||
|
if (configLoadRunnable != null) {
|
||||||
|
configLoadHandler.removeCallbacks(configLoadRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshAlarms();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地时间变更时调用
|
||||||
|
*/
|
||||||
|
public void onLocalTimeChanged() {
|
||||||
|
int mode = PreferenceConfiguration.getPowerControlMode(context);
|
||||||
|
if (mode == POWER_MODE_LOCAL) {
|
||||||
|
// 本地控制模式,刷新闹钟
|
||||||
|
refreshAlarms();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据当前模式刷新闹钟
|
||||||
|
*/
|
||||||
|
public void refreshAlarms() {
|
||||||
|
int mode = PreferenceConfiguration.getPowerControlMode(context);
|
||||||
|
Log.d(TAG, "Refreshing alarms, mode: " + mode);
|
||||||
|
|
||||||
|
cancelAllAlarms();
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case POWER_MODE_NONE: // 不控制
|
||||||
|
Log.d(TAG, "Power control: No control mode. No alarms set.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case POWER_MODE_REMOTE: // 根据服务器配置
|
||||||
|
Log.d(TAG, "Power control: Server config mode.");
|
||||||
|
// 使用已加载的配置或请求立即加载
|
||||||
|
if (configReader != null) {
|
||||||
|
String serialConfig = configReader.getSavedSerialConfig();
|
||||||
|
refreshWithServerConfig(serialConfig, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case POWER_MODE_LOCAL: // 本地控制
|
||||||
|
Log.d(TAG, "Power control: Local control mode.");
|
||||||
|
setupLocalControlAlarms();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log.w(TAG, "Unknown power control mode: " + mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用服务器配置刷新闹钟
|
||||||
|
* @param serialConfigJson 服务器配置 JSON
|
||||||
|
* @param forceRefresh 是否强制刷新(模式切换时为 true)
|
||||||
|
*/
|
||||||
|
private void refreshWithServerConfig(String serialConfigJson, boolean forceRefresh) {
|
||||||
|
cancelAllAlarms();
|
||||||
|
|
||||||
|
if (serialConfigJson == null) {
|
||||||
|
Log.w(TAG, "Server config is null.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forceRefresh && serialConfigJson.equals(lastAppliedSerialConfig)) {
|
||||||
|
Log.d(TAG, "Server config has not changed. Skipping alarm refresh.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupServerConfigAlarms(serialConfigJson);
|
||||||
|
this.lastAppliedSerialConfig = serialConfigJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消所有闹钟
|
||||||
|
*/
|
||||||
|
private void cancelAllAlarms() {
|
||||||
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
for (int i = 0; i < MAX_COMMAND_ALARMS; i++) {
|
||||||
|
Intent intent = new Intent(context, CommandBroadcastReceiver.class);
|
||||||
|
intent.setAction(CommandBroadcastReceiver.ACTION_SEND_COMMAND);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, i, intent,
|
||||||
|
PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE
|
||||||
|
);
|
||||||
|
if (pendingIntent != null) {
|
||||||
|
alarmManager.cancel(pendingIntent);
|
||||||
|
pendingIntent.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置服务器配置的闹钟
|
||||||
|
*/
|
||||||
|
private void setupServerConfigAlarms(String serialConfigJson) {
|
||||||
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject serialConfig = new JSONObject(serialConfigJson);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
setupAlarm(alarmManager, i, time, hex, hour, minute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Server config alarms set successfully.");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Failed to parse server config", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置本地控制的闹钟
|
||||||
|
*/
|
||||||
|
private void setupLocalControlAlarms() {
|
||||||
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
String onTime = PreferenceConfiguration.getPowerOnTime(context);
|
||||||
|
String offTime = PreferenceConfiguration.getPowerOffTime(context);
|
||||||
|
|
||||||
|
int[] onParts = parseTimeParts(onTime);
|
||||||
|
int[] offParts = parseTimeParts(offTime);
|
||||||
|
|
||||||
|
if (onParts != null) {
|
||||||
|
setupAlarm(alarmManager, ALARM_ID_POWER_ON, onTime,
|
||||||
|
SerialPortDetector.TEST_CMD_ON,
|
||||||
|
onParts[0], onParts[1]);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Invalid power-on time, alarm skipped: " + onTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offParts != null) {
|
||||||
|
setupAlarm(alarmManager, ALARM_ID_POWER_OFF, offTime,
|
||||||
|
SerialPortDetector.TEST_CMD_OFF,
|
||||||
|
offParts[0], offParts[1]);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Invalid power-off time, alarm skipped: " + offTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Local control alarms set: ON=" + onTime + ", OFF=" + offTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 "HH:mm" 格式时间字符串
|
||||||
|
* @return int[]{hour, minute},解析失败返回 null
|
||||||
|
*/
|
||||||
|
private int[] parseTimeParts(String time) {
|
||||||
|
if (time == null || time.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String[] parts = time.split(":");
|
||||||
|
if (parts.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
int hour = Integer.parseInt(parts[0].trim());
|
||||||
|
int minute = Integer.parseInt(parts[1].trim());
|
||||||
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new int[]{hour, minute};
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(TAG, "Failed to parse time: " + time, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单个闹钟
|
||||||
|
*/
|
||||||
|
private void setupAlarm(AlarmManager alarmManager, int alarmId, String timeStr,
|
||||||
|
String cmdHex, int hour, int minute) {
|
||||||
|
Intent intent = new Intent(context, CommandBroadcastReceiver.class);
|
||||||
|
intent.setAction(CommandBroadcastReceiver.ACTION_SEND_COMMAND);
|
||||||
|
intent.putExtra(CommandBroadcastReceiver.EXTRA_COMMAND_HEX, cmdHex);
|
||||||
|
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||||
|
context, alarmId, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
alarmManager.setExactAndAllowWhileIdle(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
calendar.getTimeInMillis(), pendingIntent);
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
alarmManager.setExact(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
calendar.getTimeInMillis(), pendingIntent);
|
||||||
|
} else {
|
||||||
|
alarmManager.set(
|
||||||
|
AlarmManager.RTC_WAKEUP,
|
||||||
|
calendar.getTimeInMillis(), pendingIntent);
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Alarm set: " + timeStr + " with command " + cmdHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 ConfigLoader
|
||||||
|
*/
|
||||||
|
private void initConfigLoader() {
|
||||||
|
if (configReader != null) {
|
||||||
|
return; // 已初始化
|
||||||
|
}
|
||||||
|
|
||||||
|
configLoadRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!TextUtils.isEmpty(configUrl)) {
|
||||||
|
Log.d(TAG, "Periodically loading config...");
|
||||||
|
configReader.loadConfig(configUrl);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "configUrl is empty, skipping config load.");
|
||||||
|
}
|
||||||
|
configLoadHandler.postDelayed(this, CONFIG_LOAD_INTERVAL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
configReader = new ConfigReader(context);
|
||||||
|
configReader.setOnConfigLoadListener(new ConfigReader.OnConfigLoadListener() {
|
||||||
|
@Override
|
||||||
|
public void onConfigLoaded() {
|
||||||
|
Log.i(TAG, "Config loaded successfully. Refreshing alarms.");
|
||||||
|
String serialConfig = configReader.getSavedSerialConfig();
|
||||||
|
refreshWithServerConfig(serialConfig, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigLoadFailed(String error) {
|
||||||
|
Log.e(TAG, "Config load failed: " + error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 立即执行一次
|
||||||
|
configLoadRunnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除缓存的服务器配置
|
||||||
|
*/
|
||||||
|
private void clearServerConfigCache() {
|
||||||
|
this.lastAppliedSerialConfig = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,9 @@ public class PreferenceConfiguration {
|
|||||||
private static final String KEY_SERIAL_PORT_PATH = "k_serial_port_path";
|
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_SERIAL_PORT_BAUD_RATE = "k_serial_baud";
|
||||||
private static final String KEY_SEND_POWER_ON_CMD = "k_send_power_on_cmd";
|
private static final String KEY_SEND_POWER_ON_CMD = "k_send_power_on_cmd";
|
||||||
|
private static final String KEY_POWER_CONTROL_MODE = "k_power_control_mode";
|
||||||
|
private static final String KEY_POWER_ON_TIME = "k_power_on_time";
|
||||||
|
private static final String KEY_POWER_OFF_TIME = "k_power_off_time";
|
||||||
|
|
||||||
public static String getSerialPortPath(Context context) {
|
public static String getSerialPortPath(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
@@ -39,4 +42,20 @@ public class PreferenceConfiguration {
|
|||||||
public static int getSerialCmdLoop(Context context) {
|
public static int getSerialCmdLoop(Context context) {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getPowerControlMode(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
String mode = prefs.getString(KEY_POWER_CONTROL_MODE, "1");
|
||||||
|
return Integer.parseInt(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPowerOnTime(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getString(KEY_POWER_ON_TIME, "07:30");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPowerOffTime(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getString(KEY_POWER_OFF_TIME, "20:30");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import tp.xmaihh.serialport.bean.ComBean;
|
|||||||
public class SerialPortDetector {
|
public class SerialPortDetector {
|
||||||
private static final String TAG = "SerialPortDetector";
|
private static final String TAG = "SerialPortDetector";
|
||||||
// 测试命令
|
// 测试命令
|
||||||
private static final String TEST_CMD_OFF = "ACEAB500ED"; // 关闭设备
|
public static final String TEST_CMD_OFF = "ACEAB500ED"; // 关闭设备
|
||||||
private static final String TEST_CMD_ON = "ACEAB400ED"; // 打开设备
|
public static final String TEST_CMD_ON = "ACEAB400ED"; // 打开设备
|
||||||
|
|
||||||
// 响应超时时间(毫秒)
|
// 响应超时时间(毫秒)
|
||||||
private static final long RESPONSE_TIMEOUT = 4000;
|
private static final long RESPONSE_TIMEOUT = 4000;
|
||||||
@@ -61,9 +61,9 @@ public class SerialPortDetector {
|
|||||||
serialHelper.open();
|
serialHelper.open();
|
||||||
for(int i = 0; i < loop; i++) {
|
for(int i = 0; i < loop; i++) {
|
||||||
serialHelper.sendHex(hexCommand);
|
serialHelper.sendHex(hexCommand);
|
||||||
|
Log.d(TAG, "Successfully sent command '" + hexCommand + "' to port '" + portPath + "'");
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Successfully sent command '" + hexCommand + "' to port '" + portPath + "'");
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error sending command to port " + portPath, e);
|
Log.e(TAG, "Error sending command to port " + portPath, e);
|
||||||
@@ -118,10 +118,9 @@ public class SerialPortDetector {
|
|||||||
|
|
||||||
for(int i = 0; i < loop; i ++) {
|
for(int i = 0; i < loop; i ++) {
|
||||||
serialHelper.sendHex(commandHex);
|
serialHelper.sendHex(commandHex);
|
||||||
|
Log.d(TAG, "Sent command: " + commandHex);
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Sent command: " + commandHex);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Error sending command: " + e.getMessage());
|
Log.e(TAG, "Error sending command: " + e.getMessage());
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -3,4 +3,15 @@
|
|||||||
<string name="title_activity_building_dashboard">Building Dashboard</string>
|
<string name="title_activity_building_dashboard">Building Dashboard</string>
|
||||||
<string name="title_activity_settings">Settings</string>
|
<string name="title_activity_settings">Settings</string>
|
||||||
<string name="url_end_point_default_value" translatable="false">/</string>
|
<string name="url_end_point_default_value" translatable="false">/</string>
|
||||||
|
|
||||||
|
<string-array name="power_control_mode_entries">
|
||||||
|
<item>不控制</item>
|
||||||
|
<item>根据服务器配置</item>
|
||||||
|
<item>本地控制</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="power_control_mode_values">
|
||||||
|
<item>0</item>
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -29,6 +29,25 @@
|
|||||||
app:title="APP 启动时重启电源插座"
|
app:title="APP 启动时重启电源插座"
|
||||||
app:summary="APP 启动时先关闭电源,5秒后再打开电源"
|
app:summary="APP 启动时先关闭电源,5秒后再打开电源"
|
||||||
app:defaultValue="true" />
|
app:defaultValue="true" />
|
||||||
|
<ListPreference
|
||||||
|
app:key="k_power_control_mode"
|
||||||
|
app:title="电源开关方式"
|
||||||
|
app:entries="@array/power_control_mode_entries"
|
||||||
|
app:entryValues="@array/power_control_mode_values"
|
||||||
|
app:defaultValue="1"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<EditTextPreference
|
||||||
|
app:key="k_power_on_time"
|
||||||
|
app:title="开电源时间"
|
||||||
|
app:defaultValue="07:30"
|
||||||
|
app:summary="设置自动开电源的时间,格式:HH:mm"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<EditTextPreference
|
||||||
|
app:key="k_power_off_time"
|
||||||
|
app:title="关电源时间"
|
||||||
|
app:defaultValue="20:30"
|
||||||
|
app:summary="设置自动关电源的时间,格式:HH:mm"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
<Preference
|
<Preference
|
||||||
app:key="k_clear_device"
|
app:key="k_clear_device"
|
||||||
app:title="串口设备路径"
|
app:title="串口设备路径"
|
||||||
|
|||||||
Reference in New Issue
Block a user