From 4de70ff57a27ba47551de93b5394503d90c2f557 Mon Sep 17 00:00:00 2001 From: BBIT-Kai <2911862937@qq.com> Date: Mon, 10 Aug 2026 10:39:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97=E3=80=81?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=8E=A7=E5=88=B6=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../com/example/iot_controlhost/MyApp.java | 8 +- .../iot_controlhost/adapter/LogAdapter.java | 3 +- .../iot_controlhost/base/BaseDialog.java | 21 ++++ .../model/controller/relay/Fan.java | 18 ++- .../model/thread/QueueIOTask.java | 8 +- .../model/thread/QueueIOUITask.java | 26 +++- .../iot_controlhost/service/MQTTService.java | 3 +- .../ui/activity/DebugActivity.java | 66 +++++++++- .../ui/activity/MainActivity.java | 76 +++++++++--- .../ui/activity/SetActivity.java | 27 ++-- .../ui/dialog/SetAutoParamDialog.java | 18 ++- .../ui/dialog/SetBaseDialog.java | 6 +- .../ui/dialog/SetDeviceBaseDialog.java | 16 ++- .../ui/dialog/SetPWMDialog.java | 2 +- .../ui/dialog/SetPortDialog.java | 3 +- .../ui/dialog/SetSensorDialog.java | 11 +- .../ui/dialog/THBiasDialog.java | 2 +- .../iot_controlhost/utils/DialogUtil.java | 32 ++++- .../example/iot_controlhost/utils/MyUtil.java | 20 +++ .../iot_controlhost/utils/PollingTask.java | 117 ++++++++++++++---- .../utils/control/Control.java | 42 +++++-- .../control/humidity/HumidityController.java | 66 ++++++++-- .../temperature/strategy/ControlStrategy.java | 3 + .../ventilator/VentilatorController.java | 78 ++++++++---- .../utils/global/RoomSensor.java | 13 +- .../iot_controlhost/utils/log/MyLog.java | 15 ++- .../iot_controlhost/utils/log/UserLog.java | 54 +++++--- .../utils/network/TopicClass.java | 81 +++++++++++- 29 files changed, 668 insertions(+), 168 deletions(-) diff --git a/.gitignore b/.gitignore index d6e0696..374e4e4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ app/build/ build/ .kotlin/ *.pdf +.gradle-home/ diff --git a/app/src/main/java/com/example/iot_controlhost/MyApp.java b/app/src/main/java/com/example/iot_controlhost/MyApp.java index 1e5ed6a..0307f0b 100644 --- a/app/src/main/java/com/example/iot_controlhost/MyApp.java +++ b/app/src/main/java/com/example/iot_controlhost/MyApp.java @@ -27,6 +27,7 @@ import com.scwang.smart.refresh.footer.ClassicsFooter; import com.scwang.smart.refresh.header.ClassicsHeader; import com.scwang.smart.refresh.layout.SmartRefreshLayout; +import org.xutils.BuildConfig; import org.xutils.x; import java.io.File; @@ -53,6 +54,11 @@ public class MyApp extends Application { // 初始化日志库 Timber.plant(new MyLog()); Timber.plant(new UserLog()); + UserLog.system("软件生命周期", "软件启动,版本=" + BuildConfig.VERSION_NAME); + MyLog.app("软件生命周期:进程启动,版本=" + BuildConfig.VERSION_NAME + + ",versionCode=" + BuildConfig.VERSION_CODE + + ",Android=" + android.os.Build.VERSION.RELEASE + + ",SDK=" + android.os.Build.VERSION.SDK_INT); if (OldSet.isFirstTime()) { OldSet.importOldSet(getApplicationContext()); } @@ -152,4 +158,4 @@ public class MyApp extends Application { return mSession; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java index fb3d13d..ccf49be 100644 --- a/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java +++ b/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java @@ -6,7 +6,6 @@ import com.example.iot_controlhost.base.BaseRecyclerAdapter; import com.example.iot_controlhost.databinding.ItemLogBinding; import com.example.iot_controlhost.model.Log; import com.example.iot_controlhost.utils.global.RoomSetting; -import com.example.iot_controlhost.utils.log.UserLog; import java.util.List; @@ -28,7 +27,7 @@ public class LogAdapter extends BaseRecyclerAdapter { data.getLevel() == android.util.Log.ERROR ? R.color.red : RoomSetting.isDarkTheme() ? R.color.white : R.color.black); binding.tvMessage.setTextColor(targetColorId); binding.tvDate.setTextColor(targetColorId); - binding.tvMessage.setText("[" + UserLog.getCategoryName(data.getTag()) + "] " + data.getMessage()); + binding.tvMessage.setText("[" + data.getTag() + "] " + data.getMessage()); binding.tvDate.setText(TimeUtils.date2String(data.getDatetime())); } } diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java b/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java index c3ddc8a..8ee8b39 100644 --- a/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java @@ -11,6 +11,7 @@ import androidx.annotation.NonNull; import androidx.databinding.ViewDataBinding; import com.example.iot_controlhost.R; +import com.example.iot_controlhost.ui.activity.MainActivity; import com.example.iot_controlhost.utils.log.MyLog; import java.lang.reflect.Method; @@ -27,11 +28,31 @@ import es.dmoral.toasty.Toasty; public abstract class BaseDialog extends Dialog { protected Binding binding; protected Context mContext; + private boolean lockTimerNotified; public BaseDialog(@NonNull Context context) { this(context, false); } + @Override + public void show() { + super.show(); + if (!lockTimerNotified && mContext instanceof MainActivity) { + lockTimerNotified = true; + ((MainActivity) mContext).onDialogShown(); + } + } + + @Override + public void dismiss() { + boolean needNotify = lockTimerNotified; + super.dismiss(); + if (needNotify && mContext instanceof MainActivity) { + lockTimerNotified = false; + ((MainActivity) mContext).onDialogDismissed(); + } + } + /** * 构造函数 * diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java index 749b75a..5e2d396 100644 --- a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java @@ -28,18 +28,24 @@ public class Fan extends Relay { public boolean setPowerSupply(boolean target) { boolean result = false; if (target) { - if (SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode()) - || SpinnerList.VENTILATION_MODES[1].equals(AutoModelSet.getVentilatorMode())) { + if ((SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode()) + || SpinnerList.VENTILATION_MODES[1].equals(AutoModelSet.getVentilatorMode())) + && fans[0].isAvailable()) { result = MyUtil.autoControlOperateThird(fans[0], true,false); } - if (SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode()) - || SpinnerList.VENTILATION_MODES[2].equals(AutoModelSet.getVentilatorMode())) { + if ((SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode()) + || SpinnerList.VENTILATION_MODES[2].equals(AutoModelSet.getVentilatorMode())) + && fans[1].isAvailable()) { result = MyUtil.autoControlOperateThird(fans[1], true,false) || result; } } else { //防止出现“仅开进气扇-开启-设置为仅开排气扇-关闭”此时进气扇无法关闭的情况 所以每次都得全部关闭(各种换气扇) - result = MyUtil.autoControlOperateThird(fans[0], false,false); - result = MyUtil.autoControlOperateThird(fans[1], false,false) || result; + if (fans[0].isAvailable()) { + result = MyUtil.autoControlOperateThird(fans[0], false,false); + } + if (fans[1].isAvailable()) { + result = MyUtil.autoControlOperateThird(fans[1], false,false) || result; + } } return result; } diff --git a/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java index 10ef8e4..5da543a 100644 --- a/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java +++ b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java @@ -1,6 +1,7 @@ package com.example.iot_controlhost.model.thread; import com.example.iot_controlhost.base.BaseQueue; +import com.example.iot_controlhost.utils.log.MyLog; /** * 任务队列-IO任务 @@ -22,7 +23,12 @@ public class QueueIOTask extends BaseQueue { @Override public void run() { - task.run(); + try { + task.run(); + } catch (Throwable throwable) { + MyLog.controllerError("控制队列任务异常:异常=" + throwable.getClass().getSimpleName() + + ",原因=" + throwable.getMessage()); + } } } diff --git a/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java index 7e9f309..a33092a 100644 --- a/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java +++ b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java @@ -2,6 +2,7 @@ package com.example.iot_controlhost.model.thread; import com.example.iot_controlhost.base.BaseQueue; import com.example.iot_controlhost.base.IRxIOTask; +import com.example.iot_controlhost.utils.log.MyLog; import com.xuexiang.rxutil2.rxjava.RxJavaUtils; import com.xuexiang.rxutil2.rxjava.impl.IRxUITask; import com.xuexiang.rxutil2.rxjava.task.RxUITask; @@ -19,12 +20,31 @@ public abstract class QueueIOUITask extends BaseQueue implements IRxIOTask RxJavaUtils.doInUIThread(new RxUITask(outData) { @Override public void doInUIThread(T o) { - QueueIOUITask.this.doInUIThread(o); + try { + QueueIOUITask.this.doInUIThread(o); + } catch (Throwable throwable) { + MyLog.appError("队列UI回调异常:异常=" + throwable.getClass().getSimpleName() + + ",原因=" + throwable.getMessage()); + QueueIOUITask.this.onError(throwable); + } + } + }); + } catch (Throwable throwable) { + MyLog.controllerError("队列任务执行异常:异常=" + throwable.getClass().getSimpleName() + + ",原因=" + throwable.getMessage()); + RxJavaUtils.doInUIThread(new RxUITask(null) { + @Override + public void doInUIThread(Object o) { + QueueIOUITask.this.onError(throwable); } }); - } catch (Exception e) { - e.printStackTrace(); } } + /** + * IO任务或UI回调失败时执行,默认只记录日志;需要清理界面的任务可重写。 + */ + public void onError(Throwable throwable) { + } + } diff --git a/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java b/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java index aadc24a..874f530 100644 --- a/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java +++ b/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java @@ -160,6 +160,7 @@ public class MQTTService extends Service { MyLog.network("服务器连接成功"); HardwareSetting.setRegistered(); setMQTTConnect(true); + TopicClass.flushPendingLogs(); try { // 订阅myTopic话题 client.subscribe(TopicClass.arealistGet(), 1); @@ -245,4 +246,4 @@ public class MQTTService extends Service { public IBinder onBind(Intent intent) { return null; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java index fd7f831..08fa63b 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java @@ -2,6 +2,8 @@ package com.example.iot_controlhost.ui.activity; import android.text.Editable; import android.text.TextWatcher; +import android.os.Handler; +import android.os.Looper; import android.view.View; import com.blankj.utilcode.util.StringUtils; @@ -18,6 +20,7 @@ import com.example.iot_controlhost.utils.MyQueue; import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.global.RoomSensor; import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.MyLog; import java.util.ArrayList; import java.util.List; @@ -32,6 +35,10 @@ import es.dmoral.toasty.Toasty; */ public class DebugActivity extends BaseActivity { + private static final long TEST_TIMEOUT_MS = 10_000L; + private final Handler mainHandler = new Handler(Looper.getMainLooper()); + private int testRequestGeneration; + /** * 当前设备 */ @@ -176,22 +183,64 @@ public class DebugActivity extends BaseActivity { + if (requestGeneration != testRequestGeneration) { + return; + } + testRequestGeneration++; + binding.tvParse.setText("测试超时,请检查串口、设备地址或队列状态"); + binding.btnReadAndParse.setEnabled(true); + hideLoadingDialog(); + MyLog.test("设备调试超时:设备=" + sensorType + ",型号=" + sensorModel + + ",地址=" + sensorAddress + ",等待=" + TEST_TIMEOUT_MS + "ms"); + }; + mainHandler.postDelayed(timeoutTask, TEST_TIMEOUT_MS); + MyLog.test("设备调试开始:设备=" + sensorType + ",型号=" + sensorModel + ",地址=" + sensorAddress); MyQueue.start(MyQueue.TYPE_SENSOR, new QueueIOUITask() { @Override public Boolean doInQueueThread() throws Exception { try { - return setAddress.test(sensorModel, sensorAddress); - } catch (Exception e) { + return testDevice.test(sensorModel, sensorAddress); + } catch (Throwable throwable) { + MyLog.test("设备调试执行异常:设备=" + sensorType + ",型号=" + sensorModel + + ",地址=" + sensorAddress + ",异常=" + + throwable.getClass().getSimpleName() + ",原因=" + throwable.getMessage()); return false; } } @Override public void doInUIThread(Boolean aBoolean) { - binding.tvSend.setText(SerialPortUtil.T); - binding.tvReceive.setText(SerialPortUtil.R); - binding.tvParse.setText(aBoolean ? toSingleLine(setAddress.getTestState()) : "数据采集失败"); + if (requestGeneration != testRequestGeneration) { + return; + } + mainHandler.removeCallbacks(timeoutTask); + try { + binding.tvSend.setText(SerialPortUtil.T); + binding.tvReceive.setText(SerialPortUtil.R); + binding.tvParse.setText(aBoolean ? toSingleLine(testDevice.getTestState()) : "数据采集失败"); + MyLog.test("设备调试完成:设备=" + sensorType + ",型号=" + sensorModel + + ",地址=" + sensorAddress + ",结果=" + (aBoolean ? "成功" : "失败") + + ",耗时=" + (System.currentTimeMillis() - startTime) + "ms"); + } finally { + binding.btnReadAndParse.setEnabled(true); + hideLoadingDialog(); + } + } + + @Override + public void onError(Throwable throwable) { + if (requestGeneration != testRequestGeneration) { + return; + } + mainHandler.removeCallbacks(timeoutTask); + binding.tvParse.setText("测试执行异常:" + throwable.getClass().getSimpleName()); + binding.btnReadAndParse.setEnabled(true); hideLoadingDialog(); } }); @@ -266,4 +315,11 @@ public class DebugActivity extends BaseActivity 0 || binding.mainLock.getVisibility() == View.VISIBLE) { + return; } - mainLockExecutorService = Executors.newSingleThreadScheduledExecutor(); - mainLockExecutorService.schedule(() -> lock(), RoomSetting.getMainActivityOperateLockTimeOut(), TimeUnit.SECONDS); + lockHandler.postDelayed(lockRunnable, RoomSetting.getMainActivityOperateLockTimeOut() * 1000L); } private void lock() { + if (!mainPageResumed || showingDialogCount > 0 || binding.mainLock.getVisibility() == View.VISIBLE) { + return; + } MyLog.app("屏幕已锁"); - mainLockExecutorService.shutdownNow(); - runOnUiThread(() -> { - binding.mainLock.setVisibility(View.VISIBLE); - binding.mainKey.setImageDrawable(getDrawable(R.mipmap.lock)); - }); + stopLockTimer(); + binding.mainLock.setVisibility(View.VISIBLE); + binding.mainKey.setImageDrawable(getDrawable(R.mipmap.lock)); } private void showLockPasswordDialog() { @@ -310,15 +313,50 @@ public class MainActivity extends BaseActivity - new SetDeviceBaseDialog(mContext, getString(R.string.serial_port_sensor), SpinnerList.SERIAL_ADDRESS, getString(R.string.serial_port_sensor), SpinnerList.BAUD, getString(R.string.baud), (serial, baud) -> { + new SetDeviceBaseDialog(mContext, getString(R.string.serial_port_sensor), + SpinnerList.SERIAL_ADDRESS, getString(R.string.serial_port_sensor), HardwareSetting.getSensorSerialCom(), + SpinnerList.BAUD, getString(R.string.baud), HardwareSetting.getSensorSerialComBaud(), (serial, baud) -> { HardwareSetting.setSensorSerialCom(serial); HardwareSetting.setSensorSerialComBaud(baud); setController(); }).show())); setList.add(new Set(getString(R.string.serial_port_controller), HardwareSetting.getControllerSerialCom() + "\t:\t" + HardwareSetting.getControllerSerialBaud(), view -> - new SetDeviceBaseDialog(mContext, getString(R.string.serial_port_controller), SpinnerList.SERIAL_ADDRESS, getString(R.string.serial_port_sensor), - SpinnerList.BAUD, getString(R.string.baud), (serial, baud) -> { + new SetDeviceBaseDialog(mContext, getString(R.string.serial_port_controller), + SpinnerList.SERIAL_ADDRESS, getString(R.string.serial_port_sensor), HardwareSetting.getControllerSerialCom(), + SpinnerList.BAUD, getString(R.string.baud), HardwareSetting.getControllerSerialBaud(), (serial, baud) -> { HardwareSetting.setControllerSerialCom(serial); HardwareSetting.setControllerSerialBaud(baud); setController(); @@ -268,7 +271,7 @@ public class SetActivity extends BaseActivity new SetDeviceBaseDialog(mContext, getString(R.string.set_relay), RoomController.relay.getModels(), - getString(R.string.mode), SpinnerList.ADDRESS, getString(R.string.address), (model, address) -> { + getString(R.string.mode), RoomController.relay.getModel(), SpinnerList.ADDRESS, getString(R.string.address), RoomController.relay.getAddress(), (model, address) -> { RoomController.relay.setModel(model); RoomController.relay.setAddress(address); setRelay(); @@ -308,7 +311,8 @@ public class SetActivity extends BaseActivity new SetDeviceBaseDialog(mContext, "485除湿机基础配置", dehumidifier485.getModels(), - getString(R.string.select_model), Dehumidifier485.ADDRESS_LIST, getString(R.string.select_address), (model, address) -> { + getString(R.string.select_model), dehumidifier485.getModel(), Dehumidifier485.ADDRESS_LIST, + getString(R.string.select_address), dehumidifier485.getAddress(), (model, address) -> { dehumidifier485.setModel(model); dehumidifier485.setAddress(address); setDehumidifier485(); @@ -330,7 +334,8 @@ public class SetActivity extends BaseActivity new SetDeviceBaseDialog(mContext, getString(R.string.set_base_infrared), deHumidityInfrared.getModels(), - getString(R.string.select_model), SpinnerList.ADDRESS, getString(R.string.select_address), (model, address) -> { + getString(R.string.select_model), deHumidityInfrared.getModel(), SpinnerList.ADDRESS, + getString(R.string.select_address), deHumidityInfrared.getAddress(), (model, address) -> { deHumidityInfrared.setModel(model); deHumidityInfrared.setAddress(address); setDeHumidityInfrared(); @@ -394,7 +399,8 @@ public class SetActivity extends BaseActivity new SetDeviceBaseDialog(mContext, getString(R.string.set_base_infrared), airConditionInfrared.getModels(), - getString(R.string.select_model), SpinnerList.ADDRESS, getString(R.string.select_address), (model, address) -> { + getString(R.string.select_model), airConditionInfrared.getModel(), SpinnerList.ADDRESS, + getString(R.string.select_address), airConditionInfrared.getAddress(), (model, address) -> { airConditionInfrared.setModel(model); airConditionInfrared.setAddress(address); setAirConditionDetail(airId, airConditionInfrared); @@ -464,8 +470,9 @@ public class SetActivity extends BaseActivity - new SetDeviceBaseDialog(mContext, "地暖" + RoomSetting.getFloorName(), RoomController.floor.getModels(), - getString(R.string.mode), SpinnerList.ADDRESS, getString(R.string.address), (model, address) -> { + new SetDeviceBaseDialog(mContext, "地暖" + RoomSetting.getFloorName(), RoomController.floor.getModels(), + getString(R.string.mode), RoomController.floor.getModel(), SpinnerList.ADDRESS, + getString(R.string.address), RoomController.floor.getAddress(), (model, address) -> { RoomController.floor.setModel(model); RoomController.floor.setAddress(address); setFloor(); @@ -608,7 +615,7 @@ public class SetActivity extends BaseActivity { MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, max); MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, min); MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, enabled); - UserLog.operate("本机屏幕", "修改自动温度参数"); + UserLog.operate("本机屏幕", "自动温度参数变化:启用=" + oldEnabled + " → " + enabled + + ",目标=" + oldTarget + " → " + target + "℃,上限=" + oldMax + " → " + max + + "℃,下限=" + oldMin + " → " + min + "℃"); MyLog.app("自动温度参数修改:启用=" + oldEnabled + "→" + enabled + ",目标=" + oldTarget + "→" + target + "℃,上限=" + oldMax + "→" + max + "℃,下限=" + oldMin + "→" + min + "℃"); @@ -185,7 +187,9 @@ public class SetAutoParamDialog extends BaseDialog { MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, min); MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, max); MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, humidityEnabled); - UserLog.operate("本机屏幕", "修改自动湿度参数"); + UserLog.operate("本机屏幕", "自动湿度参数变化:启用=" + humidityOldEnabled + " → " + humidityEnabled + + ",目标=" + humidityOldTarget + " → " + target + "% ,上限=" + humidityOldMax + + " → " + max + "% ,下限=" + humidityOldMin + " → " + min + "%"); MyLog.app("自动湿度参数修改:启用=" + humidityOldEnabled + "→" + humidityEnabled + ",目标=" + humidityOldTarget + "→" + target + "% ,上限=" + humidityOldMax + "→" + max + "% ,下限=" + humidityOldMin + "→" + min + "%"); @@ -213,7 +217,9 @@ public class SetAutoParamDialog extends BaseDialog { MMKVUtil.put(AutoModelSet.CYCLE_STOP, stop); MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, maxCo2); MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, ventilatorEnabled); - UserLog.operate("本机屏幕", "修改自动换气参数"); + UserLog.operate("本机屏幕", "自动换气参数变化:启用=" + ventilatorOldEnabled + " → " + + ventilatorEnabled + ",开启=" + oldStart + " → " + start + "分钟,关闭=" + + oldStop + " → " + stop + "分钟,CO₂上限=" + oldMaxCo2 + " → " + maxCo2 + "ppm"); MyLog.app("自动换气参数修改:启用=" + ventilatorOldEnabled + "→" + ventilatorEnabled + ",开启=" + oldStart + "→" + start + "分钟,关闭=" + oldStop + "→" + stop + "分钟,CO₂上限=" + oldMaxCo2 + "→" + maxCo2 + "ppm"); @@ -247,7 +253,8 @@ public class SetAutoParamDialog extends BaseDialog { mode.setTargetTemp(temperature); mode.setTargetHumi(humidity); MyAIModeUtil.update(mode); - UserLog.operate("本机屏幕", "修改智能模式当前龄期温湿度参数"); + UserLog.operate("本机屏幕", "智能模式当前龄期参数变化:温度=" + oldTemperature + " → " + + temperature + "℃,湿度=" + oldHumidity + " → " + humidity + "%"); MyLog.app("智能模式当前龄期参数修改:温度=" + oldTemperature + "→" + temperature + "℃,湿度=" + oldHumidity + "→" + humidity + "%"); RxBusUtils.get().post(RxTag.AI_INFO, 0); @@ -277,7 +284,8 @@ public class SetAutoParamDialog extends BaseDialog { mode.setTargetVentOpen(start); mode.setTargetVentClose(stop); MyAIModeUtil.update(mode); - UserLog.operate("本机屏幕", "修改智能模式当前龄期换气参数"); + UserLog.operate("本机屏幕", "智能模式当前龄期换气参数变化:开启=" + oldStart + " → " + + start + "分钟,关闭=" + oldStop + " → " + stop + "分钟"); MyLog.app("智能模式当前龄期换气参数修改:开启=" + oldStart + "→" + start + "分钟,关闭=" + oldStop + "→" + stop + "分钟"); RxBusUtils.get().post(RxTag.AI_INFO, 0); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java index 7f21a80..fa7d02e 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java @@ -186,7 +186,9 @@ public class SetBaseDialog extends BaseDialog { MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, heatMode); MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, target - bias); MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, target + bias); - UserLog.operate("本机屏幕", "修改设置:温度控制模式"); + UserLog.operate("本机屏幕", "设置变化:温度控制模式=" + oldMode + " → " + selection + + ",温度下限=" + min + " → " + (target - bias) + "℃,温度上限=" + + max + " → " + (target + bias) + "℃"); MyLog.app("温度控制模式修改:" + oldMode + "→" + selection + ",自动调整目标上下限为" + (target - bias) + "~" + (target + bias) + "℃"); updateTemperatureControl(); @@ -294,7 +296,7 @@ public class SetBaseDialog extends BaseDialog { } private void logSettingChanged(String name, Object oldValue, Object newValue) { - UserLog.operate("本机屏幕", "修改设置:" + name); + UserLog.operate("本机屏幕", "设置变化:" + name + "=" + oldValue + " → " + newValue); MyLog.app("设置修改:" + name + "=" + oldValue + "→" + newValue); } } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java index e57c2f7..96ba0e1 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java @@ -32,7 +32,10 @@ public class SetDeviceBaseDialog extends BaseDialog { } int oldPwm = RoomController.floor.getPwm(gear).getPwm(); RoomController.floor.getPwm(gear).setPwm(pwm); - UserLog.operate("本机屏幕", "修改地暖PWM:" + gear + "档"); + UserLog.operate("本机屏幕", "地暖PWM变化:" + gear + "档=" + oldPwm + " → " + pwm); MyLog.app("地暖PWM修改:档位=" + gear + ",PWM=" + oldPwm + "→" + pwm); } catch (Exception e) { Toasty.error(mContext, "输入格式有误,请检查").show(); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java index 29f87ed..e698e81 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java @@ -85,7 +85,8 @@ public class SetPortDialog extends BaseDialog { } //确定后关闭弹窗 RoomController.relay.setPowerSupply(false, port); - UserLog.operate("本机屏幕", "修改继电器端口配置:端口" + port); + UserLog.operate("本机屏幕", "继电器端口配置变化:端口" + port + "=" + oldDeviceName + + " → " + (deviceName.isEmpty() ? "未配置" : deviceName)); MyLog.app("继电器端口配置修改:端口=" + port + ",设备=" + oldDeviceName + "→" + (deviceName.isEmpty() ? "未配置" : deviceName)); dismiss(); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java index e8bc2df..99d8029 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java @@ -70,6 +70,9 @@ public class SetSensorDialog extends BaseDialog { }); binding.btnYes.setOnClickListener(button -> { //设置事件 + boolean oldEnable = sensor.isEnable(); + String oldModel = sensor.getModel(); + String oldAddress = sensor.getAddress(); boolean enable = binding.switcher.isChecked(); if (enable) { String address = binding.acAddress.getText().toString(); @@ -89,9 +92,11 @@ public class SetSensorDialog extends BaseDialog { sensor.setEnable(enable); //刷新页面 listener.run(sensor); - UserLog.operate("本机屏幕", "修改传感器配置:" + sensor.getName()); - MyLog.app("传感器配置修改:名称=" + sensor.getName() + ",启用=" + sensor.isEnable() - + ",型号=" + sensor.getModel() + ",地址=" + sensor.getAddress()); + String change = "传感器配置变化:名称=" + sensor.getName() + ",启用=" + oldEnable + " → " + + sensor.isEnable() + ",型号=" + oldModel + " → " + sensor.getModel() + + ",地址=" + oldAddress + " → " + sensor.getAddress(); + UserLog.operate("本机屏幕", change); + MyLog.app(change); dismiss(); }); } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java index 52f6fa7..661affb 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java @@ -43,7 +43,7 @@ public class THBiasDialog extends BaseDialog { double newValue = Double.parseDouble(binding.tiSet1.getText().toString()); MMKVUtil.put(isTemp ? RoomSetting.BIAS_TEMP : RoomSetting.BIAS_HUMIDITY, newValue); String typeName = isTemp ? "温度" : "湿度"; - UserLog.operate("本机屏幕", "修改" + typeName + "校准偏差"); + UserLog.operate("本机屏幕", typeName + "校准偏差变化:" + oldValue + " → " + newValue); MyLog.app(typeName + "校准偏差修改:" + oldValue + "→" + newValue); dismiss(); }); diff --git a/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java index 6e776bc..61b5a70 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java @@ -9,6 +9,7 @@ import android.view.WindowManager; import android.widget.TextView; import com.example.iot_controlhost.R; +import com.example.iot_controlhost.ui.activity.MainActivity; import com.google.android.material.progressindicator.LinearProgressIndicator; import com.xuexiang.rxutil2.rxjava.RxJavaUtils; import com.xuexiang.rxutil2.rxjava.task.RxUITask; @@ -17,6 +18,7 @@ public class DialogUtil { private static Dialog progressDialog; private static TextView textViewInfo; private static LinearProgressIndicator progressIndicator; + private static Context progressContext; /** * 显示加载中弹窗,支持中途改变提示文字 @@ -26,10 +28,10 @@ public class DialogUtil { */ public static void showLoadingDialog(Context context, String info) { if (progressDialog != null) { - progressDialog.dismiss(); // 如果之前有Dialog显示,则先关闭 - progressDialog = null; // 释放旧Dialog + hideLoadingDialog(); // 如果之前有Dialog显示,则先关闭并同步锁屏计数 } progressDialog = new Dialog(context, R.style.MyDialogTheme); + progressContext = context; progressDialog.setContentView(R.layout.dialog_loading); progressDialog.setCancelable(false); textViewInfo = progressDialog.findViewById(R.id.tv_loading); @@ -44,6 +46,7 @@ public class DialogUtil { layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; progressDialog.getWindow().setAttributes(layoutParams); progressDialog.show(); + notifyDialogShown(context); } /** @@ -69,6 +72,9 @@ public class DialogUtil { public static void hideLoadingDialog() { if (progressDialog != null) { progressDialog.dismiss(); + notifyDialogDismissed(progressContext); + progressDialog = null; + progressContext = null; } } @@ -79,7 +85,11 @@ public class DialogUtil { * @param info 提示信息 */ public static void showProgressIndicatorDialog(Context context, String info) { + if (progressDialog != null) { + hideLoadingDialog(); + } progressDialog = new Dialog(context, R.style.MyDialogTheme); + progressContext = context; progressDialog.setContentView(R.layout.dialog_process_indicator); progressDialog.setCancelable(false); textViewInfo = progressDialog.findViewById(R.id.tv_loading); @@ -97,6 +107,7 @@ public class DialogUtil { } textViewInfo.setText(info); progressDialog.show(); + notifyDialogShown(context); } /** @@ -118,7 +129,22 @@ public class DialogUtil { public static void hideProgressIndicatorDialog() { if (progressDialog != null) { progressDialog.dismiss(); + notifyDialogDismissed(progressContext); + progressDialog = null; + progressContext = null; } } -} \ No newline at end of file + private static void notifyDialogShown(Context context) { + if (context instanceof MainActivity) { + ((MainActivity) context).onDialogShown(); + } + } + + private static void notifyDialogDismissed(Context context) { + if (context instanceof MainActivity) { + ((MainActivity) context).onDialogDismissed(); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java index 65867bb..c4df67e 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java @@ -276,6 +276,7 @@ public class MyUtil { */ public static void exitApp() { UserLog.system("本机屏幕", "关闭软件"); + MyLog.app("软件生命周期:收到本机关闭软件指令,开始停止自动控制和设备输出"); MyUtil.stopAppControl("正在关闭软件中", () -> AppUtils.exitApp()); } @@ -288,6 +289,7 @@ public class MyUtil { public static void relaunchApp(String source) { UserLog.system(source, "重启软件"); + MyLog.app("软件生命周期:收到重启软件指令,来源=" + source + ",开始停止自动控制和设备输出"); MyUtil.stopAppControl("正在重启软件中", () -> { // AppUtils.relaunchApp(); // 这个方法重启不干净 ProcessPhoenix.triggerRebirth(MyApp.getAppContext()); @@ -303,6 +305,7 @@ public class MyUtil { public static void shutDown(String source) { UserLog.system(source, "关闭设备"); + MyLog.app("主机生命周期:收到关闭设备指令,来源=" + source + ",开始停止自动控制和设备输出"); //先关闭所有设备 MyUtil.stopAppControl("正在关闭设备中", () -> MyAPIContext.getInstance().shutDown()); } @@ -316,6 +319,7 @@ public class MyUtil { public static void reboot(String source) { UserLog.system(source, "重启设备"); + MyLog.app("主机生命周期:收到重启设备指令,来源=" + source + ",开始停止自动控制和设备输出"); MyUtil.stopAppControl("正在重启设备中", () -> MyAPIContext.getInstance().reBoot()); } @@ -370,8 +374,14 @@ public class MyUtil { */ public static boolean autoControlOperateThird(Controller controller, boolean targetState, boolean needAutoLog) { if (!controller.isAvailable()) { + // 聚合设备(例如换气扇总控)会自行跳过未安装的子设备,避免每轮自动控制重复报错。 + if (needAutoLog) { + MyLog.autoError("自动控制跳过:设备=" + controller.getName() + ",目标=" + + (targetState ? "开启" : "关闭") + ",原因=设备不可用"); + } return false; } + boolean previousState = controller.isPowerSupply(); int maxCount = 3; int count = 0; boolean success = false; @@ -390,6 +400,16 @@ public class MyUtil { if (!success) { MyLog.autoError((needAutoLog ? "自动" : "系统联动") + "操作(连续" + maxCount + "次)失败:" + (targetState ? "开启" : "关闭") + controller.getName()); + if (needAutoLog) { + UserLog.systemError("自动控制", "设备操作失败:" + controller.getName() + ",状态=" + + (previousState ? "开启" : "关闭") + " → " + (targetState ? "开启" : "关闭")); + } + } else if (needAutoLog && previousState != targetState) { + UserLog.system("自动控制", "设备状态变化:" + controller.getName() + "," + + (previousState ? "开启" : "关闭") + " → " + (targetState ? "开启" : "关闭")); + MyLog.auto("自动控制状态确认:设备=" + controller.getName() + ",原状态=" + + (previousState ? "开启" : "关闭") + ",目标状态=" + + (targetState ? "开启" : "关闭") + ",尝试次数=" + count + ",结果=成功"); } //自动操作完设备需要更新UI RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); diff --git a/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java b/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java index a3aa3f1..1d325c2 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java @@ -5,36 +5,39 @@ import android.os.Looper; import androidx.annotation.NonNull; import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; import com.xuexiang.rxutil2.rxjava.impl.IRxUITask; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class PollingTask { - private static Map instances = new HashMap<>(); + private static final Map instances = new ConcurrentHashMap<>(); + private final String instanceId; private ScheduledExecutorService scheduler; - private Map> taskMap; + private final Map> taskMap = new ConcurrentHashMap<>(); + private final Map lastUserErrorTime = new ConcurrentHashMap<>(); + private volatile boolean stopped; // 私有化构造函数,确保外部不能直接实例化 - private PollingTask() { + private PollingTask(String instanceId) { + this.instanceId = instanceId; createNewScheduler(); } // 创建新的 ScheduledExecutorService 实例 - private void createNewScheduler() { + private synchronized void createNewScheduler() { if (scheduler != null && !scheduler.isShutdown()) { - scheduler.shutdown(); + scheduler.shutdownNow(); } scheduler = Executors.newScheduledThreadPool(3); - taskMap = new HashMap<>(); + taskMap.clear(); + stopped = false; } /** @@ -48,7 +51,7 @@ public class PollingTask { // 获取基于ID的单例实例 public static synchronized PollingTask getInstance(String id) { if (!instances.containsKey(id)) { - instances.put(id, new PollingTask()); + instances.put(id, new PollingTask(id)); } return instances.get(id); } @@ -59,13 +62,22 @@ public class PollingTask { * @param delaySeconds 延迟时间(以秒为单位) * @param task 需要执行的任务 */ - public void startDelayedTask(String taskId, long delaySeconds, @NonNull final Runnable task) { + public synchronized void startDelayedTask(String taskId, long delaySeconds, @NonNull final Runnable task) { stopPollingTask(taskId); // 如果已有相同ID的任务,先停止 - - ScheduledFuture future = scheduler.schedule(() -> { - task.run(); // 执行任务 - }, delaySeconds, TimeUnit.SECONDS); - taskMap.put(taskId, future); // 保存任务的ScheduledFuture + if (stopped) { + MyLog.auto("调度器已停止,忽略延迟任务:控制器=" + instanceId + ",任务=" + taskId); + return; + } + ensureScheduler(); + try { + ScheduledFuture future = scheduler.schedule( + safeRunnable(taskId, task), Math.max(0, delaySeconds), TimeUnit.SECONDS); + taskMap.put(taskId, future); // 保存任务的ScheduledFuture + MyLog.auto("调度任务已创建:控制器=" + instanceId + ",任务=" + taskId + + ",延迟=" + Math.max(0, delaySeconds) + "秒"); + } catch (RejectedExecutionException e) { + recordTaskError(taskId, "调度延迟任务被拒绝", e); + } } /** @@ -120,17 +132,72 @@ public class PollingTask { * @param intervalSeconds 轮询间隔时间(以秒为单位) * @param task 需要执行的任务 */ - private void startPollingTask(String taskId, long intervalSeconds, @NonNull final Runnable task) { + private synchronized void startPollingTask(String taskId, long intervalSeconds, @NonNull final Runnable task) { stopPollingTask(taskId); // 如果已有相同ID的任务,先停止 + if (stopped) { + MyLog.auto("调度器已停止,忽略轮询任务:控制器=" + instanceId + ",任务=" + taskId); + return; + } + ensureScheduler(); + long safeInterval = Math.max(1, intervalSeconds); + try { + ScheduledFuture future = scheduler.scheduleAtFixedRate( + safeRunnable(taskId, task), 0, safeInterval, TimeUnit.SECONDS); + taskMap.put(taskId, future); // 保存任务的ScheduledFuture + MyLog.auto("轮询任务已创建:控制器=" + instanceId + ",任务=" + taskId + + ",间隔=" + safeInterval + "秒"); + } catch (RejectedExecutionException e) { + recordTaskError(taskId, "调度轮询任务被拒绝", e); + } + } - ScheduledFuture future = scheduler.scheduleAtFixedRate(task, 0, intervalSeconds, TimeUnit.SECONDS); - taskMap.put(taskId, future); // 保存任务的ScheduledFuture + private Runnable safeRunnable(String taskId, Runnable task) { + return () -> { + try { + task.run(); + } catch (Throwable throwable) { + recordTaskError(taskId, "任务执行异常,调度器将继续运行", throwable); + } + }; + } + + private void recordTaskError(String taskId, String action, Throwable throwable) { + String detail = action + ":控制器=" + instanceId + ",任务=" + taskId + + ",异常=" + throwable.getClass().getSimpleName() + ",原因=" + throwable.getMessage(); + MyLog.autoError(detail); + long now = System.currentTimeMillis(); + long lastTime = lastUserErrorTime.getOrDefault(taskId, 0L); + if (now - lastTime >= TimeUnit.MINUTES.toMillis(5)) { + lastUserErrorTime.put(taskId, now); + UserLog.systemError("任务调度", action + ":" + instanceId + "/" + taskId); + } + } + + private void ensureScheduler() { + if (scheduler == null || scheduler.isShutdown() || scheduler.isTerminated()) { + MyLog.auto("自动任务调度器已失效,正在重建:控制器=" + instanceId); + createNewScheduler(); + } + } + + public boolean isAvailable() { + return !stopped && scheduler != null && !scheduler.isShutdown() && !scheduler.isTerminated(); + } + + public boolean hasActiveTasks() { + if (!isAvailable()) { + return false; + } + taskMap.entrySet().removeIf(entry -> entry.getValue() == null + || entry.getValue().isCancelled() || entry.getValue().isDone()); + return !taskMap.isEmpty(); } // 停止所有轮询任务 - public void stopAllPollingTasks() { + public synchronized void stopAllPollingTasks() { + stopped = true; for (String taskId : taskMap.keySet()) { - MyLog.test("关闭轮询任务" + taskId); + MyLog.auto("停止调度任务:控制器=" + instanceId + ",任务=" + taskId); } if (scheduler != null && !scheduler.isShutdown()) { // scheduler.shutdown(); // 停止所有任务 @@ -144,14 +211,14 @@ public class PollingTask { taskMap.clear(); // 清空任务映射 } // 从实例映射中移除当前实例 - instances.values().remove(this); + instances.remove(instanceId, this); } /** * 停止轮询任务 * * @param taskId 任务ID */ - public void stopPollingTask(String taskId) { + public synchronized void stopPollingTask(String taskId) { ScheduledFuture future = taskMap.get(taskId); if (future != null) { future.cancel(true); // 取消任务,中断正在执行的任务 diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java b/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java index d2b29db..b568865 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java @@ -14,19 +14,33 @@ public abstract class Control { /** * 是否在开始中 */ - protected boolean start = false; + protected volatile boolean start = false; protected PollingTask pollingTask ; /** * 开始控制 */ - public void start() { - if (start) { + public synchronized void start() { + if (start && pollingTask != null && pollingTask.hasActiveTasks()) { return; } + if (start) { + MyLog.autoError("检测到自动控制任务已无有效调度,尝试自恢复:" + getClass().getSimpleName()); + } pollingTask = PollingTask.getInstance(getClass().getSimpleName()); - start = startMethod(); + try { + start = startMethod(); + if (start) { + MyLog.auto("自动控制启动成功:" + getClass().getSimpleName()); + } else { + MyLog.autoError("自动控制未启动:" + getClass().getSimpleName() + ",设备不可用或配置不满足"); + } + } catch (Throwable throwable) { + start = false; + MyLog.autoError("自动控制启动异常:" + getClass().getSimpleName() + ",异常=" + + throwable.getClass().getSimpleName() + ",原因=" + throwable.getMessage()); + } } /** @@ -39,16 +53,28 @@ public abstract class Control { /** * 停止控制 */ - public void stop() { + public synchronized void stop() { if(pollingTask != null){ pollingTask.stopAllPollingTasks(); } if (!start) { return; } - stopMethod(); - RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); - start = false; + try { + stopMethod(); + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); + MyLog.auto("自动控制停止成功:" + getClass().getSimpleName()); + } catch (Throwable throwable) { + MyLog.autoError("自动控制停止异常:" + getClass().getSimpleName() + ",异常=" + + throwable.getClass().getSimpleName() + ",原因=" + throwable.getMessage()); + } finally { + start = false; + pollingTask = null; + } + } + + public boolean isRunning() { + return start && pollingTask != null && pollingTask.hasActiveTasks(); } /** diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java index a232657..1b51dae 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java @@ -15,8 +15,7 @@ import com.example.iot_controlhost.utils.global.RxTag; import com.example.iot_controlhost.utils.global.SpinnerList; import com.example.iot_controlhost.utils.global.Variable; import com.example.iot_controlhost.utils.log.MyLog; -import com.xuexiang.rxutil2.rxjava.DisposablePool; -import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.example.iot_controlhost.utils.log.UserLog; /** * @Description 自动模式-控制逻辑-湿度 @@ -29,6 +28,9 @@ public class HumidityController extends Control { */ private static boolean needOpenToTarget = false; private static boolean needDehumidifyToTarget = false; + private Boolean lastHumidifierTarget; + private Boolean lastDehumidifierTarget; + private boolean invalidHumidityLogged; /** * 目标湿度 */ @@ -53,7 +55,23 @@ public class HumidityController extends Control { return false; } pollingTask.startPollingTaskOnIOThread(RxTag.AUTO_HUMIDITY, Variable.AUTO_CHECK_TIME, () -> { - double cur = RoomSensor.getAverageIndoorHumidity(); + // 自动控制必须使用未叠加校准偏差的真实湿度;校准值仅用于界面显示和上报。 + double cur = RoomSensor.getBeforeCalibratedIndoorHumidity(); + double calibrated = RoomSensor.getAverageIndoorHumidity(); + if (cur <= 0) { + MyLog.autoError("湿度自动控制跳过:没有有效真实湿度,校准湿度=" + calibrated + "%"); + if (!invalidHumidityLogged) { + UserLog.systemError("自动控制", "湿度数据无效,已停止加湿以避免持续运行"); + invalidHumidityLogged = true; + } + needOpenToTarget = false; + if (RoomController.humidifier.isAvailable() && RoomController.humidifier.isPowerSupply()) { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> + MyUtil.autoControlOperateThird(RoomController.humidifier, false))); + } + return; + } + invalidHumidityLogged = false; double min = 0; double max = 0; if (RoomSetting.getMode() == 1) { @@ -85,10 +103,10 @@ public class HumidityController extends Control { needDehumidifyToTarget = false; needDehumidify = false; } - // 真实湿度已经接近 100%,禁止自动除湿 - if (RoomSensor.getBeforeCalibratedIndoorHumidity() > 99) { - needDehumidify = false; - needDehumidifyToTarget = false; + // 真实湿度已接近100%时,即使负校准偏差导致显示值较低,也绝不继续加湿。 + if (cur >= 99) { + needHumidify = false; + needOpenToTarget = false; } // === 互斥处理 === @@ -99,6 +117,18 @@ public class HumidityController extends Control { } boolean humidifierState = needHumidify; boolean dehumidifierState = needDehumidify; + if (lastHumidifierTarget == null || lastHumidifierTarget != humidifierState) { + MyLog.auto("湿度控制决策变化:加湿器=" + formatState(lastHumidifierTarget) + "→" + + formatState(humidifierState) + ",真实湿度=" + cur + "%" + + ",校准湿度=" + calibrated + "% ,目标=" + target + "% ,区间=" + min + "~" + max + "%"); + lastHumidifierTarget = humidifierState; + } + if (lastDehumidifierTarget == null || lastDehumidifierTarget != dehumidifierState) { + MyLog.auto("湿度控制决策变化:除湿机=" + formatState(lastDehumidifierTarget) + "→" + + formatState(dehumidifierState) + ",真实湿度=" + cur + "%" + + ",校准湿度=" + calibrated + "% ,目标=" + target + "% ,区间=" + min + "~" + max + "%"); + lastDehumidifierTarget = dehumidifierState; + } Controller autoDehumidifier = RoomController.getAutoDehumidifier(); int dehumidifierTargetHumidity = (int) Math.round(target); boolean needRefreshDehumidifierSetting = autoDehumidifier instanceof Dehumidifier485 @@ -109,16 +139,20 @@ public class HumidityController extends Control { || autoDehumidifier.isPowerSupply() == dehumidifierState && !needRefreshDehumidifierSetting; if (humidifierKeep && dehumidifierKeep) { - MyLog.auto("湿度-控制维持当前状态"); + // 状态维持时不重复输出,避免淹没真正的状态变化和异常日志。 } else { // 加湿器控制 if (RoomController.humidifier.isAvailable()) { - MyLog.auto("自动:" + (humidifierState ? "开启" : "关闭") + "加湿器--"); MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { - MyUtil.autoControlOperateThird(RoomController.humidifier, humidifierState); + boolean humidifierResult = MyUtil.autoControlOperateThird(RoomController.humidifier, humidifierState); + MyLog.auto("湿度控制执行结果:设备=加湿器,目标=" + formatState(humidifierState) + + ",结果=" + (humidifierResult ? "成功" : "失败") + ",真实湿度=" + cur + + "% ,校准湿度=" + calibrated + "%"); String airExchangeMode = AutoModelSet.getAirExchangeMode(); if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[2])) { - MyUtil.autoControlOperateThird(RoomController.airExchange, humidifierState); + boolean airExchangeResult = MyUtil.autoControlOperateThird(RoomController.airExchange, humidifierState); + MyLog.auto("湿度联动执行结果:设备=新风,目标=" + formatState(humidifierState) + + ",结果=" + (airExchangeResult ? "成功" : "失败")); } })); } @@ -143,6 +177,9 @@ public class HumidityController extends Control { protected void stopMethod() { needOpenToTarget = false; needDehumidifyToTarget = false; + lastHumidifierTarget = null; + lastDehumidifierTarget = null; + invalidHumidityLogged = false; MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { MyUtil.autoControlOperateThird(RoomController.humidifier); Controller autoDehumidifier = RoomController.getAutoDehumidifier(); @@ -157,4 +194,11 @@ public class HumidityController extends Control { MyLog.auto("停止自动控制湿度————————————————————————————————————————————————————"); } + private String formatState(Boolean state) { + if (state == null) { + return "未初始化"; + } + return state ? "开启" : "关闭"; + } + } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java index 34240e9..2bdb022 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java @@ -9,6 +9,7 @@ import com.example.iot_controlhost.utils.global.RoomSetting; import com.example.iot_controlhost.utils.global.RxTag; import com.example.iot_controlhost.utils.global.Variable; import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; import com.xuexiang.rxutil2.rxbus.RxBusUtils; /** @@ -153,12 +154,14 @@ public abstract class ControlStrategy { //防止重复发送相同的提示日志 tipsTempStr = tips.toString(); MyLog.auto("自动温控:" + tips); + UserLog.system("自动控制", "温度控制状态变化:" + tips.toString().trim()); } } else { if (mode != -1 && temp != -1 && RoomSetting.isAirConditionForceClose()) { tips.append(",在消毒期间无法开启空调"); } MyLog.autoError("自动温控操作失败:" + tips); + UserLog.systemError("自动控制", "温度控制操作失败:" + tips.toString().trim()); } } } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java index 633753f..2b9a20e 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java @@ -47,7 +47,12 @@ public class VentilatorController extends Control { protected boolean startMethod() { //每次开启时获取最新参数 refreshRule(); - MyLog.auto("自动:首先关闭换气扇"); + if (!RoomController.fan.isAvailable()) { + MyLog.autoError("换气自动控制无法启动:换气扇不可用"); + return false; + } + MyLog.auto("换气自动控制初始化:先关闭换气扇,开启时长=" + targetOpen + + "分钟,关闭时长=" + targetClose + "分钟"); stopFan(0); return true; } @@ -63,25 +68,35 @@ public class VentilatorController extends Control { } private void startFan(int minutes) { - MyLog.auto("自动:" + minutes + "分钟后开启换气扇"); + MyLog.auto("换气调度:计划" + minutes + "分钟后开启换气扇"); pollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_START_FUN, minutes * 60, () -> { - refreshRule(); - if (targetOpen == 0 && targetClose == 0) { - //如果开始停止时间均为0,则3分钟后再次检测配置项 - MyLog.auto("开始停止时间均为0,3分钟后再次检测配置项"); - startFan(3); - return; - } - // 开启换气扇 - MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { - String airExchangeMode = AutoModelSet.getAirExchangeMode(); - RoomController.fan.setPowerSupply(true); - if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { - MyUtil.autoControlOperateThird(RoomController.airExchange, true); + try { + refreshRule(); + if (targetOpen == 0 && targetClose == 0) { + //如果开始停止时间均为0,则3分钟后再次检测配置项 + MyLog.auto("换气控制暂停:开启和关闭时长均为0,3分钟后重新读取配置"); + startFan(3); + return; } - })); - MyLog.auto("自动:" + targetOpen + "分钟后关闭换气扇"); - stopFan(targetOpen); + // 开启换气扇 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + String airExchangeMode = AutoModelSet.getAirExchangeMode(); + boolean fanResult = MyUtil.autoControlOperateThird(RoomController.fan, true); + MyLog.auto("换气控制执行结果:设备=换气扇,目标=开启,结果=" + + (fanResult ? "成功" : "失败") + ",计划运行=" + targetOpen + "分钟"); + if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { + boolean airExchangeResult = MyUtil.autoControlOperateThird(RoomController.airExchange, true); + MyLog.auto("换气联动执行结果:设备=新风,目标=开启,结果=" + + (airExchangeResult ? "成功" : "失败")); + } + })); + MyLog.auto("换气调度:计划" + targetOpen + "分钟后关闭换气扇"); + stopFan(targetOpen); + } catch (Throwable throwable) { + MyLog.autoError("换气扇开启阶段异常:异常=" + throwable.getClass().getSimpleName() + + ",原因=" + throwable.getMessage() + ",1分钟后尝试恢复调度"); + startFan(1); + } }); } @@ -93,14 +108,25 @@ public class VentilatorController extends Control { private void stopFan(int minutes) { // 开启换气扇,持续targetOpen分钟 pollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_STOP_FUN, minutes * 60, () -> { - // 关闭换气扇 - MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { - if (RoomController.airExchange.isAvailable() && AutoModelSet.getAirExchangeMode().equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { - MyUtil.autoControlOperateThird(RoomController.airExchange, false); - } - RoomController.fan.setPowerSupply(false); - })); - startFan(targetClose); + try { + refreshRule(); + // 关闭换气扇 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + if (RoomController.airExchange.isAvailable() && AutoModelSet.getAirExchangeMode().equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { + boolean airExchangeResult = MyUtil.autoControlOperateThird(RoomController.airExchange, false); + MyLog.auto("换气联动执行结果:设备=新风,目标=关闭,结果=" + + (airExchangeResult ? "成功" : "失败")); + } + boolean fanResult = MyUtil.autoControlOperateThird(RoomController.fan, false); + MyLog.auto("换气控制执行结果:设备=换气扇,目标=关闭,结果=" + + (fanResult ? "成功" : "失败") + ",计划停运=" + targetClose + "分钟"); + })); + startFan(targetClose); + } catch (Throwable throwable) { + MyLog.autoError("换气扇关闭阶段异常:异常=" + throwable.getClass().getSimpleName() + + ",原因=" + throwable.getMessage() + ",1分钟后尝试恢复调度"); + startFan(1); + } }); } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java index 5efbb4a..f644f31 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java @@ -199,14 +199,14 @@ public class RoomSensor { } /** - * 获取室内平均湿度 + * 获取未叠加校准偏差的室内平均真实湿度,供自动控制和安全保护使用。 */ public static double getBeforeCalibratedIndoorHumidity() { double sumOfHumidities = 0.0; int numberOfEnabledSensors = 0; for (THSensor sensor : dynamicTHSensorList) { - if (sensor.humidity == 0 || sensor.humidity == 100) { + if (sensor.humidity <= 0 || sensor.humidity > 100) { continue; } sumOfHumidities += sensor.humidity; @@ -214,7 +214,7 @@ public class RoomSensor { } for (GasSensor sensor : dynamicCO2SensorList) { - if (sensor.getHumidity() == 0 || sensor.getHumidity() == 100) { + if (sensor.getHumidity() <= 0 || sensor.getHumidity() > 100) { continue; } sumOfHumidities += sensor.getHumidity(); @@ -222,7 +222,7 @@ public class RoomSensor { } for (GasSensor sensor : dynamicAmmoniaSensorList) { - if (sensor.getHumidity() == 0 || sensor.getHumidity() == 100) { + if (sensor.getHumidity() <= 0 || sensor.getHumidity() > 100) { continue; } sumOfHumidities += sensor.getHumidity(); @@ -235,8 +235,11 @@ public class RoomSensor { return sumOfHumidities / numberOfEnabledSensors; } + /** + * 获取叠加校准偏差后的室内平均湿度,供界面显示和上报使用。 + */ public static double getAverageIndoorHumidity() { - double averageHumidity = getAverageIndoorHumidity(); + double averageHumidity = getBeforeCalibratedIndoorHumidity(); if (averageHumidity == 0.0) { return 0.0; diff --git a/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java b/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java index 0d261a6..27388e3 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java @@ -66,8 +66,21 @@ public class MyLog extends Timber.Tree { public static final String TAG_FRP = "远程协助"; public static List getUserQueryTag() { + return getSystemLogTags(); + } + + /** + * 系统诊断日志的全部落库标签,包含普通和错误级别。 + */ + public static List getSystemLogTags() { List tags = new ArrayList<>(); - Collections.addAll(tags, TAG_APP, TAG_REMOTE, TAG_CONTROLLER, TAG_WARNING,TAG_SENSOR, TAG_NETWORK, TAG_AUTO); + List baseTags = new ArrayList<>(); + Collections.addAll(baseTags, TAG_TEST, TAG_WARNING, TAG_SENSOR, TAG_CONTROLLER, TAG_NETWORK, + TAG_APP, TAG_REMOTE, TAG_AUTO, TAG_FACE, TAG_FRP); + for (String tag : baseTags) { + tags.add(tag); + tags.add(tag + TAG_ERROR); + } return tags; } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java b/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java index 69ccb25..88e734a 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java @@ -17,11 +17,19 @@ import timber.log.Timber; * 日志用户查看用 */ public class UserLog extends Timber.Tree { - public static final String FILTER_ALL = "全部上报日志"; + public static final String FILTER_ALL = "全部日志"; + public static final String FILTER_USER_ALL = "全部用户日志"; + public static final String FILTER_DIAGNOSTIC = "系统诊断日志"; + public static final String FILTER_DIAGNOSTIC_AUTO = "自动控制诊断"; + public static final String FILTER_DIAGNOSTIC_CONTROLLER = "控制器诊断"; + public static final String FILTER_DIAGNOSTIC_SENSOR = "传感器诊断"; + public static final String FILTER_DIAGNOSTIC_NETWORK = "网络诊断"; public static final String FILTER_OPERATE = "操作日志"; + public static final String FILTER_USER_SENSOR = "用户传感器日志"; public static final String FILTER_SENSOR = "传感器日志"; + public static final String FILTER_CONTROLLER = "控制器日志"; public static final String FILTER_TASK = "作业日志"; - public static final String FILTER_SYSTEM = "系统日志"; + public static final String FILTER_SYSTEM = "用户系统日志"; /** * 用户-操作类 @@ -47,17 +55,34 @@ public class UserLog extends Timber.Tree { } public static List getUserQueryFilters() { - return Arrays.asList(FILTER_ALL, FILTER_OPERATE, FILTER_SENSOR, FILTER_TASK, FILTER_SYSTEM); + return Arrays.asList(FILTER_ALL, FILTER_USER_ALL, FILTER_OPERATE, FILTER_USER_SENSOR, + FILTER_TASK, FILTER_SYSTEM, FILTER_DIAGNOSTIC, FILTER_DIAGNOSTIC_AUTO, + FILTER_CONTROLLER, FILTER_SENSOR, FILTER_DIAGNOSTIC_NETWORK); } /** * 将界面筛选项转换为实际存储标签,成功和错误级别都包含在同一分类中。 */ public static List getStorageTagsForFilter(String filter) { + if (FILTER_DIAGNOSTIC.equals(filter)) { + return MyLog.getSystemLogTags(); + } + if (FILTER_DIAGNOSTIC_AUTO.equals(filter)) { + return tagsWithError(MyLog.TAG_AUTO); + } + if (FILTER_DIAGNOSTIC_CONTROLLER.equals(filter) || FILTER_CONTROLLER.equals(filter)) { + return tagsWithError(MyLog.TAG_CONTROLLER); + } + if (FILTER_DIAGNOSTIC_SENSOR.equals(filter) || FILTER_SENSOR.equals(filter)) { + return tagsWithError(MyLog.TAG_SENSOR); + } + if (FILTER_DIAGNOSTIC_NETWORK.equals(filter)) { + return tagsWithError(MyLog.TAG_NETWORK); + } if (FILTER_OPERATE.equals(filter) || TAG_USER_OPERATE.equals(filter)) { return tagsWithError(TAG_USER_OPERATE); } - if (FILTER_SENSOR.equals(filter) || TAG_USER_SENSOR.equals(filter)) { + if (FILTER_USER_SENSOR.equals(filter) || TAG_USER_SENSOR.equals(filter)) { return tagsWithError(TAG_USER_SENSOR); } if (FILTER_TASK.equals(filter) || TAG_USER_TASK.equals(filter)) { @@ -70,6 +95,9 @@ public class UserLog extends Timber.Tree { for (String userTag : getUserLogTags()) { tags.addAll(tagsWithError(userTag)); } + if (FILTER_ALL.equals(filter) || filter == null) { + tags.addAll(MyLog.getSystemLogTags()); + } return tags; } @@ -78,19 +106,7 @@ public class UserLog extends Timber.Tree { } public static String getCategoryName(String tag) { - if (tag == null) { - return "未知"; - } - if (tag.contains(TAG_USER_OPERATE)) { - return "操作"; - } - if (tag.contains(TAG_USER_SENSOR)) { - return "传感器"; - } - if (tag.contains(TAG_USER_TASK)) { - return "作业"; - } - return "系统"; + return tag == null ? "未知" : tag; } public static void operate(String msg) { @@ -137,6 +153,10 @@ public class UserLog extends Timber.Tree { Timber.tag(TAG_USER_SYSTEM + MyLog.TAG_ERROR).e(msg); } + public static void systemError(String source, String msg) { + systemError(formatSource(source, msg)); + } + /** * 崩溃场景使用非阻塞入口,防止数据库繁忙时阻断进程重启。 */ diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java b/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java index 6f372f1..0fc46a1 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java @@ -35,6 +35,8 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; /** * MQTT 主题 @@ -47,6 +49,8 @@ public class TopicClass { private static String areaDataTemp = ""; private static String sensorDataTemp = ""; private static String sensorInfoDataTemp = ""; + private static final int MAX_PENDING_LOGS = 200; + private static final Queue PENDING_LOGS = new ConcurrentLinkedQueue<>(); //————————————————————————————————————————————————————————————————————————————————获取区域集合———————————————————————————————————————————————————————————————— @@ -309,16 +313,56 @@ public class TopicClass { } public static void uploadLog(int tag, int priority, String message, Date datetime) { + PendingLog pendingLog = new PendingLog(tag, priority, message, datetime); + if (!MQTTService.getMQTTConnect()) { + while (PENDING_LOGS.size() >= MAX_PENDING_LOGS) { + PENDING_LOGS.poll(); + } + PENDING_LOGS.offer(pendingLog); + MyLog.network("用户日志等待MQTT连接后上传:类型=" + tag + ",级别=" + priority + + ",待上传=" + PENDING_LOGS.size() + "条"); + return; + } + publishLog(pendingLog); + } + + private static void publishLog(PendingLog pendingLog) { JSONObject json = new JSONObject(); try { json.put("areacode", RoomSetting.getAreaCode()); - json.put("tage", tag); - json.put("msglevel", priority); - json.put("msg", message); - json.put("datetime", MyUtil.getDateTime(datetime)); + json.put("tage", pendingLog.tag); + json.put("msglevel", pendingLog.priority); + json.put("msg", pendingLog.message); + json.put("datetime", MyUtil.getDateTime(pendingLog.datetime)); MQTTService.publish(DeviceRunLogsPost(), json.toString()); } catch (JSONException e) { - e.printStackTrace(); + MyLog.networkError("用户日志序列化失败:类型=" + pendingLog.tag + ",原因=" + e.getMessage()); + } + } + + public static void flushPendingLogs() { + int count = 0; + PendingLog pendingLog; + while (MQTTService.getMQTTConnect() && (pendingLog = PENDING_LOGS.poll()) != null) { + publishLog(pendingLog); + count++; + } + if (count > 0) { + MyLog.network("MQTT连接恢复,已补传本次进程缓存的用户日志" + count + "条"); + } + } + + private static final class PendingLog { + private final int tag; + private final int priority; + private final String message; + private final Date datetime; + + private PendingLog(int tag, int priority, String message, Date datetime) { + this.tag = tag; + this.priority = priority; + this.message = message; + this.datetime = datetime; } } @@ -564,6 +608,18 @@ public class TopicClass { //唯一区域码 之前通过唯一区域码获得索引,现在就一个房间,所以不需要 String code = jsons.getString("code"); if (RoomSetting.getAreaCode().equals(code)) { + String oldProgram = AIModelSet.getAiSchemeName(); + double oldTempMax = AutoModelSet.getMaxTemperature(); + double oldTempMin = AutoModelSet.getMinTemperature(); + double oldHumidityMax = AutoModelSet.getMaxHumidity(); + double oldHumidityMin = AutoModelSet.getMinHumidity(); + boolean oldAutoTemperature = AutoModelSet.isAutoTemperature(); + double oldTargetTemperature = AutoModelSet.getTargetTemperature(); + boolean oldAutoHumidity = AutoModelSet.isAutoHumidity(); + double oldTargetHumidity = AutoModelSet.getTargetHumidity(); + boolean oldAutoVentilator = AutoModelSet.isAutoVentilator(); + int oldVentilatorOpen = AutoModelSet.getCycleStart(); + int oldVentilatorClose = AutoModelSet.getCycleStop(); //预警值 MMKVUtil.put(AIModelSet.AI_SCHEME_NAME, jsons.getString("autocontrolprogram")); MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, jsons.getDouble("tempmaxwarning")); @@ -604,7 +660,20 @@ public class TopicClass { //2025年5月7日:取消以下逻辑 // //切换为自动模式 // RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3); - UserLog.operate("远程控制", "自动模式参数更新成功"); + String changeSummary = "自动模式参数更新:方案=" + oldProgram + " → " + AIModelSet.getAiSchemeName() + + ";温控启用=" + oldAutoTemperature + " → " + AutoModelSet.isAutoTemperature() + + ",目标=" + oldTargetTemperature + " → " + AutoModelSet.getTargetTemperature() + "℃" + + ",区间=" + oldTempMin + "~" + oldTempMax + " → " + + AutoModelSet.getMinTemperature() + "~" + AutoModelSet.getMaxTemperature() + "℃" + + ";湿控启用=" + oldAutoHumidity + " → " + AutoModelSet.isAutoHumidity() + + ",目标=" + oldTargetHumidity + " → " + AutoModelSet.getTargetHumidity() + "%" + + ",区间=" + oldHumidityMin + "~" + oldHumidityMax + " → " + + AutoModelSet.getMinHumidity() + "~" + AutoModelSet.getMaxHumidity() + "%" + + ";换气启用=" + oldAutoVentilator + " → " + AutoModelSet.isAutoVentilator() + + ",开启=" + oldVentilatorOpen + " → " + AutoModelSet.getCycleStart() + "分钟" + + ",关闭=" + oldVentilatorClose + " → " + AutoModelSet.getCycleStop() + "分钟"; + UserLog.operate("远程控制", changeSummary); + MyLog.remote(changeSummary + ",区域码=" + code); if (RoomSetting.getMode() == 1) { // 如果是自动模式,则刷新自动模式参数 RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3);