diff --git a/app/build.gradle b/app/build.gradle index 797dc46..d166a53 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.example.iot_controlhost" minSdkVersion 25 targetSdkVersion 30 - versionCode 160 - versionName "3.5.0.160" + versionCode 161 + versionName "3.5.0.161" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ndk { moduleName "mcu" diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java index 7ebc352..18755b6 100644 --- a/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java +++ b/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java @@ -82,10 +82,12 @@ public class AutoControllerAdapter 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(data.getMessage()); + binding.tvMessage.setText("[" + UserLog.getCategoryName(data.getTag()) + "] " + data.getMessage()); binding.tvDate.setText(TimeUtils.date2String(data.getDatetime())); } } diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java index 93e6e95..0454e85 100644 --- a/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java +++ b/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java @@ -65,10 +65,12 @@ public class ManualControllerAdapter extends BaseRecyclerAdapter//显示详细信息 startActivity(new Intent(mContext, InfoActivity.class)); case 7 ->//切换为手动模式 - new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_manual), () -> presenter.initMode(0)).show(); + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_manual), + () -> presenter.initMode(0, MainActivityPresenter.MODE_SOURCE_LOCAL)).show(); case 8 ->//切换为自动模式 - new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_auto), () -> presenter.initMode(1)).show(); + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_auto), + () -> presenter.initMode(1, MainActivityPresenter.MODE_SOURCE_LOCAL)).show(); case 9 ->//切换为智能模式 new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_intelligence), () -> { if (!SilkwormDBManager.isStarting()) { new ConfirmDialog(mContext, getString(R.string.error), "当前未在" + (RoomSetting.getType() == 4 ? "催青" : "共育") + ",无法进入智能模式", null).show(); } else { - presenter.initMode(2); + presenter.initMode(2, MainActivityPresenter.MODE_SOURCE_LOCAL); } }).show(); case 10 ->//刷新共育状态 @@ -180,7 +182,7 @@ public class MainActivity extends BaseActivity noticeLiveData = new SingleLiveEvent<>(); private MutableLiveData workListMutableLiveData = new SingleLiveEvent<>(); private MutableLiveData workNewListMutableLiveData = new SingleLiveEvent<>(); @@ -97,15 +102,21 @@ public class MainActivityPresenter extends BasePresenter { roomName.setValue(RoomSetting.getRoomName()); if (!RoomSetting.isRoomEnable()) { //刷新切换为手动模式 - initMode(0); + initMode(0, MODE_SOURCE_REMOTE + "-区域禁用联动"); } } case 3 -> //刷新切换为自动模式 - initMode(1); + initMode(1, MODE_SOURCE_WORKFLOW); case 5 -> //刷新切换为手动模式 - initMode(0); + initMode(0, MODE_SOURCE_WORKFLOW); case 6 -> //刷新切换为智能模式 - initMode(2); + initMode(2, MODE_SOURCE_SYSTEM); + case 13 -> //远程切换为自动模式 + initMode(1, MODE_SOURCE_REMOTE); + case 15 -> //远程切换为手动模式 + initMode(0, MODE_SOURCE_REMOTE); + case 16 -> //远程切换为智能模式 + initMode(2, MODE_SOURCE_REMOTE); case 7 -> //刷新共育信息 并刷新模式 noticeLiveData.setValue(10); } @@ -126,33 +137,81 @@ public class MainActivityPresenter extends BasePresenter { * @param targetMode 新模式 */ public void initMode(int targetMode) { - String modeName; - if (targetMode == 0) { - modeName = "手动模式"; - } else if (targetMode == 1) { - modeName = "自动模式"; - } else { - modeName = "智能模式"; + initMode(targetMode, MODE_SOURCE_SYSTEM); + } + + public void initMode(int targetMode, String source) { + int previousMode = RoomSetting.getMode(); + String previousModeName = getModeName(previousMode); + String modeName = getModeName(targetMode); + if (previousMode == targetMode) { + MyLog.app("模式切换未执行:来源=" + source + ",当前已是" + modeName); + return; } showLoadingDialog("正在切换为" + modeName + "中"); - MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask<>() { + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { @Override - public Object doInQueueThread() { - ModelsAdapter.getModeByPosition(RoomSetting.getMode()).stopMode(); - MMKVUtil.put(RoomSetting.MODE, targetMode); - UserLog.operate("切换为:" + modeName); - return null; + public ModeChangeResult doInQueueThread() { + try { + ModelsAdapter.getModeByPosition(previousMode).stopMode(); + MMKVUtil.put(RoomSetting.MODE, targetMode); + return ModeChangeResult.success(); + } catch (Exception e) { + return ModeChangeResult.failure(e.getMessage()); + } } @Override - public void doInUIThread(Object o) { - changeMode.setValue(targetMode); - ModelsAdapter.getModeByPosition(targetMode).initMode(); + public void doInUIThread(ModeChangeResult result) { + if (result.success) { + try { + changeMode.setValue(targetMode); + ModelsAdapter.getModeByPosition(targetMode).initMode(); + UserLog.operate(source, "模式切换成功:" + previousModeName + " → " + modeName); + } catch (Exception e) { + UserLog.operateError(source, "模式切换失败:" + previousModeName + " → " + modeName); + MyLog.appError("模式切换后初始化失败:来源=" + source + "," + previousModeName + + " → " + modeName + ",原因=" + e.getMessage()); + errorTips("模式初始化失败,请检查设备状态"); + } + } else { + UserLog.operateError(source, "模式切换失败:" + previousModeName + " → " + modeName); + MyLog.appError("模式切换失败:来源=" + source + "," + previousModeName + " → " + + modeName + ",原因=" + result.message); + errorTips("模式切换失败,请稍后重试"); + } hideLoadingDialog(); } }); } + private String getModeName(int mode) { + return switch (mode) { + case 0 -> "手动模式"; + case 1 -> "自动模式"; + case 2 -> "智能模式"; + default -> "未知模式(" + mode + ")"; + }; + } + + private static class ModeChangeResult { + private final boolean success; + private final String message; + + private ModeChangeResult(boolean success, String message) { + this.success = success; + this.message = message; + } + + private static ModeChangeResult success() { + return new ModeChangeResult(true, null); + } + + private static ModeChangeResult failure(String message) { + return new ModeChangeResult(false, message == null ? "未知异常" : message); + } + } + public MutableLiveData getNoticeLiveData() { return noticeLiveData; @@ -240,7 +299,8 @@ public class MainActivityPresenter extends BasePresenter { @Override public void error(Throwable e) { errorTips("作业列表获取失败,请检查网络"); - UserLog.taskError("作业列表获取失败" + e.getMessage()); + UserLog.taskError("作业列表获取失败"); + MyLog.networkError("作业列表获取失败:" + e.getMessage()); } @Override @@ -275,4 +335,4 @@ public class MainActivityPresenter extends BasePresenter { }); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java index 66a6cce..4d24518 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java @@ -51,6 +51,8 @@ import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.global.RoomSensor; import com.example.iot_controlhost.utils.global.RoomSetting; import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import java.util.ArrayList; import java.util.Collections; @@ -171,8 +173,10 @@ public class SetActivity extends BaseActivity " + getText(R.string.set_app)); setList.clear(); setList.add(new Set(getString(R.string.set_password), "******", view -> - new PasswordDialog(mContext, getString(R.string.set_password), null, ti -> - MMKVUtil.put(RoomSetting.SET_PASSWORD, ti.getText().toString())).show())); + new PasswordDialog(mContext, getString(R.string.set_password), null, ti -> { + MMKVUtil.put(RoomSetting.SET_PASSWORD, ti.getText().toString()); + UserLog.operate("本机屏幕", "系统设置密码修改成功(密码内容未记录)"); + }).show())); setList.add(new Set(getString(R.string.set_password_main_lock), "******", view -> new PasswordDialog(mContext, getString(R.string.set_password_main_lock), null, ti -> { if (ti.getText().toString().length() != 6) { @@ -180,6 +184,7 @@ public class SetActivity extends BaseActivity new SpinnerDialog(mContext, "导入配置", "请选择配置来源", new String[]{"预设", "基地", "会东", "旧版本", "兴文石海", "主干继电器-从机模式"}, @@ -257,7 +262,9 @@ public class SetActivity extends BaseActivity { + boolean oldValue = RoomController.relay.isEnable(); RoomController.relay.setEnable(b); + logSettingChanged("继电器启用状态", oldValue, b); setRelay(); })); setList.add(new Set(getString(R.string.set_base), RoomController.relay.toString(), view -> new SetDeviceBaseDialog(mContext, getString(R.string.set_relay), RoomController.relay.getModels(), @@ -294,7 +301,9 @@ public class SetActivity extends BaseActivity 除湿机配置 > 485除湿机"); setList.clear(); setList.add(new Set("启用485除湿机", dehumidifier485.isEnable(), (compoundButton, b) -> { + boolean oldValue = dehumidifier485.isEnable(); dehumidifier485.setEnable(b); + logSettingChanged("485除湿机启用状态", oldValue, b); setDehumidifier485(); })); setList.add(new Set("基础配置", dehumidifier485.toString(), view -> @@ -314,7 +323,9 @@ public class SetActivity extends BaseActivity 除湿机配置"); setList.clear(); setList.add(new Set(getString(R.string.enable_air_condition_infrared), deHumidityInfrared.isEnable(), (compoundButton, b) -> { + boolean oldValue = deHumidityInfrared.isEnable(); deHumidityInfrared.setEnable(b); + logSettingChanged("红外除湿机启用状态", oldValue, b); setDeHumidityInfrared(); })); setList.add(new Set(getString(R.string.set_base_infrared), deHumidityInfrared.toString(), view -> @@ -345,18 +356,24 @@ public class SetActivity extends BaseActivity { + boolean oldValue = RoomSetting.isAirStartFirst(); MMKVUtil.put(RoomSetting.CONTROL_AIR_START_FIRST, b); + logSettingChanged("空调先开物理开关", oldValue, b); setAirCondition(); })); setList.add(new Set("自动控制时:循环发送控制指令", RoomSetting.isAirRecycleControl(), (compoundButton, b) -> { + boolean oldValue = RoomSetting.isAirRecycleControl(); MMKVUtil.put(RoomSetting.AIR_RECYCLE_CONTROL, b); + logSettingChanged("空调循环发送控制指令", oldValue, b); setAirCondition(); })); setAdapter.setDataList(setList); @@ -370,7 +387,9 @@ public class SetActivity extends BaseActivity " + getText(R.string.set_air_condition) + " > " + airConditionInfrared.getName()); setList.clear(); setList.add(new Set(getString(R.string.enable_air_condition_infrared), airConditionInfrared.isEnable(), (compoundButton, b) -> { + boolean oldValue = airConditionInfrared.isEnable(); airConditionInfrared.setEnable(b); + logSettingChanged(airConditionInfrared.getName() + "启用状态", oldValue, b); setAirConditionDetail(airId, airConditionInfrared); })); setList.add(new Set(getString(R.string.set_base_infrared), airConditionInfrared.toString(), view -> @@ -420,17 +439,23 @@ public class SetActivity extends BaseActivity " + RoomSetting.getFloorName() + "配置"); setList.clear(); setList.add(new Set("开启" + RoomSetting.getFloorName(), RoomController.floor.isEnable(), (compoundButton, b) -> { + boolean oldValue = RoomController.floor.isEnable(); RoomController.floor.setEnable(b); + logSettingChanged(RoomSetting.getFloorName() + "启用状态", oldValue, b); setFloor(); })); setList.add(new Set("地暖别名", RoomSetting.getFloorName(), view -> new SpinnerDialog(mContext, "地暖别名", "请选择显示名称", new String[]{"地暖", "升温板"}, str -> { + String oldValue = RoomSetting.getFloorName(); MMKVUtil.put(RoomSetting.FLOOR_NAME, str); + logSettingChanged("地暖显示名称", oldValue, str); setFloor(); }).show())); setList.add(new Set(getString(R.string.max_gear), RoomController.floor.getMaxGear() + "档", v1 -> new SpinnerDialog(mContext, getString(R.string.max_gear), "请选择" + RoomSetting.getFloorName() + "最大档位", new String[]{"1", "2", "3", "4", "5"}, selection -> { + int oldValue = RoomController.floor.getMaxGear(); RoomController.floor.setMaxGear(Integer.parseInt(selection)); + logSettingChanged(RoomSetting.getFloorName() + "最大档位", oldValue, selection); setFloor(); successTips("最大档位已设置为:" + Integer.parseInt(selection) + "档"); }).show())); @@ -556,6 +581,7 @@ public class SetActivity extends BaseActivity " + getText(R.string.set_advanced)); setList.clear(); setList.add(new Set("恢复出厂设置", view -> new ConfirmDialog(mContext, getString(R.string.error), "确定恢复出厂设置吗?\n设备将在初始化后重启", () -> { + UserLog.operate("本机屏幕", "已确认恢复出厂设置,设备即将重置并重启"); MMKVUtil.clear(); //清空数据后需要恢复主机码 否则无法上传最新配置 MMKVUtil.put(HardwareSetting.HOST_ID, Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)); @@ -581,4 +607,9 @@ public class SetActivity extends BaseActivity permissions, boolean allGranted) { if (allGranted) { processLiveData.postValue(new ProcessInfo(100, "系统启动成功")); - UserLog.operate("系统启动成功"); + // 索引首次创建可能较慢,放在启动完成后后台执行,不阻塞启动页。 + LogDBManager.prepareAsync(); + MyLog.app("系统启动成功"); } else { MyLog.appError("获取部分权限成功,但部分权限未正常授予"); XXPermissions.startPermissionActivity(getApplication(), permissions); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java index c8e7ef2..ad15247 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java @@ -108,14 +108,14 @@ public class AirDialog extends BaseDialog { public void doInUIThread(Boolean result) { if (result) { successTips(air.getState()); - UserLog.operate("手动:操作空调" + air.getState()); + UserLog.operate("本机屏幕", "空调操作成功:" + air.getState()); } else { String errorTips = "手动:操作空调失败"; if (RoomSetting.isAirConditionForceClose()) { errorTips += ",在消毒期间无法开启空调"; } errorTips(errorTips); - UserLog.operateError(errorTips); + UserLog.operateError("本机屏幕", errorTips); } refreshAir(); DialogUtil.hideLoadingDialog(); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/Dehumidifier485Dialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/Dehumidifier485Dialog.java index 3884d5a..ed0ab73 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/Dehumidifier485Dialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/Dehumidifier485Dialog.java @@ -19,6 +19,7 @@ import com.example.iot_controlhost.utils.MyQueue; import com.example.iot_controlhost.utils.global.RoomSetting; import com.example.iot_controlhost.utils.global.RxTag; import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.xuexiang.rxutil2.rxbus.RxBusUtils; import android_serialport_api.SerialPortUtil; @@ -124,10 +125,11 @@ public class Dehumidifier485Dialog extends BaseDialog { if (aBoolean) { preGear = floor.getGear(); successTips(floor.getState()); - UserLog.operate("手动:切换地暖档位:" + floor.getGear() + "档"); + UserLog.operate("本机屏幕", "地暖档位切换成功:" + floor.getGear() + "档"); } else { binding.rs.setValues(preGear); if (gear > floor.getMaxGear()) { errorTips("目标档位大于设定最大档位,切换失败"); } else { - UserLog.operateError("手动:切换地暖档位:" + gear + "档失败"); + UserLog.operateError("本机屏幕", "地暖档位切换失败:" + gear + "档"); errorTips("切换档位失败"); } } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java index 0f3edda..3774f91 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java @@ -13,83 +13,122 @@ import com.example.iot_controlhost.model.Log; import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; import com.example.iot_controlhost.utils.DialogUtil; import com.example.iot_controlhost.utils.database.LogDBManager; -import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; import com.xuexiang.rxutil2.rxjava.RxJavaUtils; import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask; +import java.util.Collections; import java.util.List; /** - * 体质弹窗 + * 日志管理弹窗。 */ public class LogDialog extends BaseDialog { - /** - * 当前主界面日志所在页数 - */ - private int logPage = 1; - List logs; - LogAdapter adapter; + private final LogAdapter adapter; + private String currentFilter = UserLog.FILTER_ALL; + private Log pageCursor; + private boolean loading; + private int queryGeneration; - public LogDialog(Context mContext, List logs) { - super(mContext); - this.logs = logs; - adapter = new LogAdapter(logs, ItemLogBinding.class); - //初始化界面控件 - binding.rvLog.setLayoutManager(new LinearLayoutManager(mContext)); + public LogDialog(Context context, List initialLogs) { + super(context); + List safeLogs = initialLogs == null ? Collections.emptyList() : initialLogs; + adapter = new LogAdapter(safeLogs, ItemLogBinding.class); + pageCursor = getLastLog(safeLogs); + + binding.rvLog.setLayoutManager(new LinearLayoutManager(context)); binding.rvLog.setAdapter(adapter); - binding.acInput.setHint(mContext.getString(R.string.select_log_tag)); - binding.acInput.setAdapter(new CustomSpinnerAdapter(mContext, MyLog.getUserQueryTag().toArray(new String[0]))); - binding.refreshLayout.setOnRefreshListener(refreshlayout -> RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { - @Override - public List doInIOThread(Object o) { - logPage = 1; - return LogDBManager.queryList(binding.acInput.getText().toString(), logPage); - } + binding.acInput.setHint(context.getString(R.string.select_log_tag)); + binding.acInput.setAdapter(new CustomSpinnerAdapter( + context, UserLog.getUserQueryFilters().toArray(new String[0]))); + binding.acInput.setText(UserLog.FILTER_ALL, false); - @Override - public void doInUIThread(List logs) { - adapter.setDataList(logs); - if (logs.size() == 0) { - refreshlayout.finishLoadMoreWithNoMoreData(); - } else { - refreshlayout.finishLoadMore(); - } - refreshlayout.finishRefresh(); - } - })); - binding.refreshLayout.setOnLoadMoreListener(refreshlayout -> RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { - @Override - public List doInIOThread(Object o) { - return LogDBManager.queryList(binding.acInput.getText().toString(), ++logPage); - } + binding.refreshLayout.setOnRefreshListener(refreshLayout -> refreshLogs(false)); + binding.refreshLayout.setOnLoadMoreListener(refreshLayout -> loadNextPage()); + binding.btnQuery.setOnClickListener(view -> refreshLogs(true)); + } - @Override - public void doInUIThread(List logs) { - adapter.addDataList(logs); - if (logs.size() == 0) { - refreshlayout.finishLoadMoreWithNoMoreData(); - } else { - refreshlayout.finishLoadMore(); - } - } - })); - binding.btnQuery.setOnClickListener(view -> { - logPage = 1; + private void refreshLogs(boolean showLoading) { + if (loading) { + binding.refreshLayout.finishRefresh(); + return; + } + loading = true; + int generation = ++queryGeneration; + String requestedFilter = getSelectedFilter(); + binding.refreshLayout.resetNoMoreData(); + if (showLoading) { DialogUtil.showLoadingDialog(mContext, "正在读取日志..."); - RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { - @Override - public List doInIOThread(Object o) { - return LogDBManager.queryList(binding.acInput.getText().toString(), logPage); - } + } + RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { + @Override + public List doInIOThread(Object o) { + return LogDBManager.queryLatest(requestedFilter); + } - @Override - public void doInUIThread(List logs) { + @Override + public void doInUIThread(List logs) { + if (generation == queryGeneration) { + currentFilter = requestedFilter; + pageCursor = getLastLog(logs); adapter.setDataList(logs); + updateLoadMoreState(logs); + } + loading = false; + binding.refreshLayout.finishRefresh(); + if (showLoading) { DialogUtil.hideLoadingDialog(); } - }); + } }); } + private void loadNextPage() { + if (loading || pageCursor == null) { + binding.refreshLayout.finishLoadMoreWithNoMoreData(); + return; + } + loading = true; + int generation = queryGeneration; + Log requestedCursor = pageCursor; + String requestedFilter = currentFilter; + RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { + @Override + public List doInIOThread(Object o) { + return LogDBManager.queryNext(requestedFilter, requestedCursor); + } + + @Override + public void doInUIThread(List logs) { + if (generation == queryGeneration) { + if (!logs.isEmpty()) { + adapter.addDataList(logs); + pageCursor = getLastLog(logs); + } + updateLoadMoreState(logs); + } else { + binding.refreshLayout.finishLoadMore(); + } + loading = false; + } + }); + } + + private void updateLoadMoreState(List logs) { + if (LogDBManager.isFullPage(logs)) { + binding.refreshLayout.finishLoadMore(); + } else { + binding.refreshLayout.finishLoadMoreWithNoMoreData(); + } + } + + private String getSelectedFilter() { + String filter = binding.acInput.getText().toString().trim(); + return filter.isEmpty() ? UserLog.FILTER_ALL : filter; + } + + private Log getLastLog(List logs) { + return logs == null || logs.isEmpty() ? null : logs.get(logs.size() - 1); + } } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java index 62d0f00..c71e3b1 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java @@ -18,6 +18,8 @@ import com.example.iot_controlhost.utils.global.AutoModelSet; 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.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.xuexiang.rxutil2.rxbus.RxBusUtils; import java.util.List; @@ -131,7 +133,7 @@ public class SetAutoParamDialog extends BaseDialog { binding.btnMinus1.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet1, setType, false)); } binding.btnYes.setOnClickListener(view -> { - double min = Double.parseDouble(binding.tiSet3.getText().toString()); + double min = setType <= 2 ? Double.parseDouble(binding.tiSet3.getText().toString()) : 0; double max = Double.parseDouble(binding.tiSet2.getText().toString()); double target = Double.parseDouble(binding.tiSet1.getText().toString()); boolean needRefresh = false; @@ -146,10 +148,20 @@ public class SetAutoParamDialog extends BaseDialog { return; } //保存自动模式-温度设置 + double oldTarget = AutoModelSet.getTargetTemperature(); + double oldMax = AutoModelSet.getMaxTemperature(); + double oldMin = AutoModelSet.getMinTemperature(); + boolean oldEnabled = AutoModelSet.isAutoTemperature(); + boolean enabled = binding.ivAuto.getDrawable().getConstantState() + .equals(context.getDrawable(R.mipmap.on).getConstantState()); MMKVUtil.put(AutoModelSet.TARGET_TEMPERATURE, target); MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, max); MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, min); - MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, binding.ivAuto.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState())); + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, enabled); + UserLog.operate("本机屏幕", "修改自动温度参数"); + MyLog.app("自动温度参数修改:启用=" + oldEnabled + "→" + enabled + + ",目标=" + oldTarget + "→" + target + "℃,上限=" + oldMax + "→" + max + + "℃,下限=" + oldMin + "→" + min + "℃"); dismiss(); needRefresh = true; break; @@ -163,10 +175,20 @@ public class SetAutoParamDialog extends BaseDialog { return; } //保存自动模式-湿度设置 + double humidityOldTarget = AutoModelSet.getTargetHumidity(); + double humidityOldMin = AutoModelSet.getMinHumidity(); + double humidityOldMax = AutoModelSet.getMaxHumidity(); + boolean humidityOldEnabled = AutoModelSet.isAutoHumidity(); + boolean humidityEnabled = binding.ivAuto.getDrawable().getConstantState() + .equals(context.getDrawable(R.mipmap.on).getConstantState()); MMKVUtil.put(AutoModelSet.TARGET_HUMIDITY, target); MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, min); MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, max); - MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, binding.ivAuto.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState())); + MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, humidityEnabled); + UserLog.operate("本机屏幕", "修改自动湿度参数"); + MyLog.app("自动湿度参数修改:启用=" + humidityOldEnabled + "→" + humidityEnabled + + ",目标=" + humidityOldTarget + "→" + target + "% ,上限=" + humidityOldMax + "→" + max + + "% ,下限=" + humidityOldMin + "→" + min + "%"); dismiss(); needRefresh = true; } @@ -178,10 +200,23 @@ public class SetAutoParamDialog extends BaseDialog { && checkInput(context, binding.tvSet3, binding.tiSet3, 500, 3000)) { //保存自动模式-换气设置 try { - MMKVUtil.put(AutoModelSet.CYCLE_START, Integer.parseInt(binding.tiSet1.getText().toString())); - MMKVUtil.put(AutoModelSet.CYCLE_STOP, Integer.parseInt(binding.tiSet2.getText().toString())); - MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, Integer.parseInt(binding.tiSet3.getText().toString())); - MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, binding.ivAuto.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState())); + int start = Integer.parseInt(binding.tiSet1.getText().toString()); + int stop = Integer.parseInt(binding.tiSet2.getText().toString()); + int maxCo2 = Integer.parseInt(binding.tiSet3.getText().toString()); + int oldStart = AutoModelSet.getCycleStart(); + int oldStop = AutoModelSet.getCycleStop(); + int oldMaxCo2 = AutoModelSet.getMaxDensityCO2(); + boolean ventilatorOldEnabled = AutoModelSet.isAutoVentilator(); + boolean ventilatorEnabled = binding.ivAuto.getDrawable().getConstantState() + .equals(context.getDrawable(R.mipmap.on).getConstantState()); + MMKVUtil.put(AutoModelSet.CYCLE_START, start); + MMKVUtil.put(AutoModelSet.CYCLE_STOP, stop); + MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, maxCo2); + MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, ventilatorEnabled); + UserLog.operate("本机屏幕", "修改自动换气参数"); + MyLog.app("自动换气参数修改:启用=" + ventilatorOldEnabled + "→" + ventilatorEnabled + + ",开启=" + oldStart + "→" + start + "分钟,关闭=" + oldStop + "→" + + stop + "分钟,CO₂上限=" + oldMaxCo2 + "→" + maxCo2 + "ppm"); dismiss(); needRefresh = true; } catch (NumberFormatException e) { @@ -206,10 +241,15 @@ public class SetAutoParamDialog extends BaseDialog { } } AITHMode mode = MyAIModeUtil.getMyAiMode().get(age); + double oldTemperature = mode.getTargetTemp(); + double oldHumidity = mode.getTargetHumi(); //修改当前龄期的湿度 mode.setTargetTemp(temperature); mode.setTargetHumi(humidity); MyAIModeUtil.update(mode); + UserLog.operate("本机屏幕", "修改智能模式当前龄期温湿度参数"); + MyLog.app("智能模式当前龄期参数修改:温度=" + oldTemperature + "→" + temperature + + "℃,湿度=" + oldHumidity + "→" + humidity + "%"); RxBusUtils.get().post(RxTag.AI_INFO, 0); dismiss(); needRefresh = true; @@ -232,9 +272,14 @@ public class SetAutoParamDialog extends BaseDialog { } } AITHMode mode = MyAIModeUtil.getMyAiMode().get(age); + int oldStart = mode.getTargetVentOpen(); + int oldStop = mode.getTargetVentClose(); mode.setTargetVentOpen(start); mode.setTargetVentClose(stop); MyAIModeUtil.update(mode); + UserLog.operate("本机屏幕", "修改智能模式当前龄期换气参数"); + MyLog.app("智能模式当前龄期换气参数修改:开启=" + oldStart + "→" + start + + "分钟,关闭=" + oldStop + "→" + stop + "分钟"); RxBusUtils.get().post(RxTag.AI_INFO, 0); dismiss(); needRefresh = true; 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 04f9f21..7f21a80 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 @@ -26,6 +26,8 @@ import com.example.iot_controlhost.utils.global.RoomSetting; 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.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.example.iot_controlhost.utils.timer.ThemeTaskUtil; import com.example.iot_controlhost.utils.timer.UVTaskUtil; import com.xuexiang.rxutil2.rxbus.RxBusUtils; @@ -77,7 +79,9 @@ public class SetBaseDialog extends BaseDialog { new ConfirmDialog(mContext, mContext.getString(R.string.tip), "时间不可小于5s,请重新输入", null).show(); return; } + long oldTime = RoomSetting.getMainActivityOperateLockTimeOut(); MMKVUtil.put(RoomSetting.MAIN_ACTIVITY_OPERATE_LOCK_TIMEOUT, time); + logSettingChanged("自动锁屏等待时间", oldTime + "秒", time + "秒"); setMain(); } catch (Exception e) { new ConfirmDialog(mContext, mContext.getString(R.string.tip), "输入格式有误,设置失败", null).show(); @@ -85,12 +89,16 @@ public class SetBaseDialog extends BaseDialog { }, true).show())); setList.add(new Set(mContext.getString(R.string.notice_type), RoomSetting.getNoticeType(), view -> new SpinnerDialog(mContext, mContext.getString(R.string.notice_type), "请选择通知类型", RoomSetting.NOTICE_TYPE_SPINNER, sel -> { + String oldValue = RoomSetting.getNoticeType(); MMKVUtil.put(RoomSetting.NOTICE_TYPE, sel); + logSettingChanged("通知类型", oldValue, sel); setMain(); }).show())); setList.add(new Set(mContext.getString(R.string.light_night), RoomSetting.getAutoLightNight() ? "根据日出日落时间自动设置" : RoomSetting.isDarkTheme() ? "总是暗色调" : "总是亮色调", v2 -> new SpinnerDialog(mContext, mContext.getString(R.string.light_night), "请选择色调调节方式", new String[]{"总是亮色调", "总是暗色调", "根据日出日落时间自动设置"}, sel -> { + String oldValue = RoomSetting.getAutoLightNight() ? "根据日出日落时间自动设置" + : RoomSetting.isDarkTheme() ? "总是暗色调" : "总是亮色调"; boolean isDark; if ("根据日出日落时间自动设置".equals(sel)) { MMKVUtil.put(RoomSetting.AUTO_LIGHT_NIGHT, true); @@ -101,6 +109,7 @@ public class SetBaseDialog extends BaseDialog { } dismiss(); this.dismiss(); + logSettingChanged("界面色调", oldValue, sel); MyUtil.switchDarkMode(isDark); }).show())); setList.add(new Set(getString(R.string.set_password_main_lock), "******", view -> new PasswordDialog(mContext, "请先输入原锁屏密码", @@ -116,11 +125,14 @@ public class SetBaseDialog extends BaseDialog { return; } MMKVUtil.put(RoomSetting.MAIN_ACTIVITY_LOCK, ti2.getText().toString()); + UserLog.operate("本机屏幕", "锁屏密码修改成功(密码内容未记录)"); Toasty.success(mContext, "锁屏新密码设置成功").show(); }).show(); }).show())); setList.add(new Set("消毒作业期间自动关闭空调", RoomSetting.isAirConditionForceCloseEnable(), (compoundButton, b) -> { + boolean oldValue = RoomSetting.isAirConditionForceCloseEnable(); MMKVUtil.put(RoomSetting.IS_AIR_CONDITION_FORCE_CLOSE_ENABLE, b); + logSettingChanged("消毒作业期间自动关闭空调", oldValue, b); setMain(); })); setList.add(new Set(mContext.getString(R.string.log), view -> showLogDialog())); @@ -135,16 +147,21 @@ public class SetBaseDialog extends BaseDialog { setList.add(new Set("返回上一级", view -> setMain())); setList.add(new Set("升温设备", AutoModelSet.getAutoTemperatureHeat(), view -> new SpinnerDialog(mContext, mContext.getString(R.string.temperature_control_heat), "请选择升温设备", SpinnerList.TEMPERATURE_HEAT, selection -> { + String oldValue = AutoModelSet.getAutoTemperatureHeat(); MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, selection); + logSettingChanged("自动升温设备", oldValue, selection); updateTemperatureControl(); }).show())); setList.add(new Set("降温设备", AutoModelSet.getAutoTemperatureCool(), view -> new SpinnerDialog(mContext, mContext.getString(R.string.temperature_control_heat), "请选择降温设备", SpinnerList.TEMPERATURE_COOL, selection -> { + String oldValue = AutoModelSet.getAutoTemperatureCool(); MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_COOL, selection); + logSettingChanged("自动降温设备", oldValue, selection); updateTemperatureControl(); }).show())); setList.add(new Set("温度控制模式", AutoModelSet.getAutoTemperatureMode(), view -> new SpinnerDialog(mContext, mContext.getString(R.string.temperature_control_mode), "请选择温控模式", SpinnerList.TEMPERATURE_MODE, selection -> { + String oldMode = AutoModelSet.getAutoTemperatureMode(); double target = AutoModelSet.getTargetTemperature(); double min = AutoModelSet.getMinTemperature(); double max = AutoModelSet.getMaxTemperature(); @@ -159,6 +176,7 @@ public class SetBaseDialog extends BaseDialog { heatMode = SpinnerList.TEMPERATURE_HEAT[2]; } else { MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, selection); + logSettingChanged("温度控制模式", oldMode, selection); updateTemperatureControl(); return; } @@ -168,11 +186,15 @@ 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("本机屏幕", "修改设置:温度控制模式"); + MyLog.app("温度控制模式修改:" + oldMode + "→" + selection + + ",自动调整目标上下限为" + (target - bias) + "~" + (target + bias) + "℃"); updateTemperatureControl(); }).show(); } else { MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, selection); MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, heatMode); + logSettingChanged("温度控制模式", oldMode, selection); updateTemperatureControl(); } }).show() @@ -185,24 +207,32 @@ public class SetBaseDialog extends BaseDialog { })); setList.add(new Set("匀风扇控制-启动时机", AutoModelSet.getAutoEvenMode(), view -> new SpinnerDialog(mContext, "匀风扇控制", "请选择匀风扇启动时机", SpinnerList.AUTO_EVEN_MODES, selection -> { + String oldValue = AutoModelSet.getAutoEvenMode(); MMKVUtil.put(AutoModelSet.AUTO_EVEN_MODE, selection); + logSettingChanged("匀风扇启动时机", oldValue, selection); RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); setAutoParam(); }).show())); setList.add(new Set("匀风扇控制-在加湿期间不运行", AutoModelSet.isAutoEvenModeDuringHumidify()?"是" : "否", view -> new SpinnerDialog(mContext, "匀风扇在加湿期间不运行", "请选择", SpinnerList.YES_OR_NO, selection -> { + boolean oldValue = AutoModelSet.isAutoEvenModeDuringHumidify(); MMKVUtil.put(AutoModelSet.AUTO_EVEN_MODE_DURING_HUMIDIFY, selection.equals("是")); + logSettingChanged("匀风扇加湿期间停运", oldValue, selection.equals("是")); RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); setAutoParam(); }).show())); setList.add(new Set("换气扇控制", AutoModelSet.getVentilatorMode(), view -> new SpinnerDialog(mContext, "换气扇控制", "请选择换气扇控制方法", SpinnerList.VENTILATION_MODES, selection -> { + String oldValue = AutoModelSet.getVentilatorMode(); MMKVUtil.put(AutoModelSet.VENTILATOR_MODE, selection); + logSettingChanged("换气扇控制方式", oldValue, selection); setAutoParam(); }).show())); setList.add(new Set("新风控制", AutoModelSet.getAirExchangeMode(), view -> new SpinnerDialog(mContext, "新风控制", "请选择新风控制方法", SpinnerList.AIR_EXCHANGE_MODES, selection -> { + String oldValue = AutoModelSet.getAirExchangeMode(); MMKVUtil.put(AutoModelSet.AIR_EXCHANGE_MODE, selection); + logSettingChanged("新风控制方式", oldValue, selection); setAutoParam(); }).show())); setList.add(new Set("地暖最大温度限制", AutoModelSet.getAutoFlorMaxTemp() + "℃", view -> @@ -213,7 +243,9 @@ public class SetBaseDialog extends BaseDialog { new ConfirmDialog(mContext, mContext.getString(R.string.tip), "地暖温度区间需为35~45℃,请重新设置", null).show(); return; } + double oldTemp = AutoModelSet.getAutoFlorMaxTemp(); MMKVUtil.put(AutoModelSet.AUTO_FLOOR_MAX_TEMP, temp); + logSettingChanged("地暖最大温度限制", oldTemp + "℃", temp + "℃"); setAutoParam(); } catch (Exception e) { new ConfirmDialog(mContext, mContext.getString(R.string.tip), "输入格式有误,设置失败", null).show(); @@ -250,7 +282,7 @@ public class SetBaseDialog extends BaseDialog { RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { @Override public List doInIOThread(Object o) { - return LogDBManager.queryList(null, 1); + return LogDBManager.queryLatest(UserLog.FILTER_ALL); } @Override @@ -260,4 +292,9 @@ public class SetBaseDialog extends BaseDialog { } }); } + + private void logSettingChanged(String name, Object oldValue, Object newValue) { + UserLog.operate("本机屏幕", "修改设置:" + name); + 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 a87c0bf..e57c2f7 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 @@ -9,6 +9,8 @@ import com.example.iot_controlhost.base.BaseDialog; import com.example.iot_controlhost.databinding.DialogDeviceCustomSetBinding; import com.example.iot_controlhost.ui.activity.SetActivity; import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import es.dmoral.toasty.Toasty; @@ -50,6 +52,9 @@ public class SetDeviceBaseDialog extends BaseDialog { binding.btnYes.setText(mContext.getText(R.string.set)); binding.btnYes.setOnClickListener(view -> { //移除之前设备的端口设置 - Relay relay = RoomController.getRelay(MyUtil.getPortToDeviceName(port)); + String oldDeviceName = MyUtil.getPortToDeviceName(port); + Relay relay = RoomController.getRelay(oldDeviceName); if (relay != null) { relay.removePort(port); } else { @@ -82,6 +85,9 @@ public class SetPortDialog extends BaseDialog { } //确定后关闭弹窗 RoomController.relay.setPowerSupply(false, port); + UserLog.operate("本机屏幕", "修改继电器端口配置:端口" + port); + MyLog.app("继电器端口配置修改:端口=" + port + ",设备=" + oldDeviceName + + "→" + (deviceName.isEmpty() ? "未配置" : deviceName)); dismiss(); afterSetRelay.run(); }); 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 8008b83..e8bc2df 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 @@ -13,6 +13,8 @@ import com.example.iot_controlhost.model.thread.QueueIOUITask; import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; import com.example.iot_controlhost.utils.MyQueue; import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import android_serialport_api.SerialPortUtil; @@ -87,6 +89,9 @@ 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()); dismiss(); }); } @@ -95,4 +100,4 @@ public class SetSensorDialog extends BaseDialog { void run(Sensor sensor); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java index 80310b6..f664f37 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java @@ -25,6 +25,8 @@ import com.example.iot_controlhost.utils.global.RxTag; import com.example.iot_controlhost.utils.network.Url; import com.example.iot_controlhost.utils.network.XHttpManager; import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.xuexiang.rxutil2.rxbus.RxBusUtils; import org.xutils.http.RequestParams; @@ -143,6 +145,10 @@ public class StartCultivateDialog extends BaseDialog { binding.btnYes.setOnClickListener(view -> { InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); - MMKVUtil.put(isTemp ? RoomSetting.BIAS_TEMP : RoomSetting.BIAS_HUMIDITY, Double.parseDouble(binding.tiSet1.getText().toString())); + double oldValue = isTemp ? RoomSetting.getBiasTemp() : RoomSetting.getBiasHumidity(); + double newValue = Double.parseDouble(binding.tiSet1.getText().toString()); + MMKVUtil.put(isTemp ? RoomSetting.BIAS_TEMP : RoomSetting.BIAS_HUMIDITY, newValue); + String typeName = isTemp ? "温度" : "湿度"; + UserLog.operate("本机屏幕", "修改" + typeName + "校准偏差"); + MyLog.app(typeName + "校准偏差修改:" + oldValue + "→" + newValue); dismiss(); }); } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java index ecad012..f8102b3 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java @@ -14,6 +14,7 @@ import com.example.iot_controlhost.utils.MMKVUtil; import com.example.iot_controlhost.utils.MyUtil; import com.example.iot_controlhost.utils.global.AutoModelSet; import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; import com.xuexiang.rxutil2.rxbus.RxBusUtils; import java.util.List; @@ -34,7 +35,9 @@ public class UvTaskDialog extends BaseDialog { MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoDisinfect(), binding.llAutoSetParam,binding.llAutoSetParamNone); binding.ivAuto.setOnClickListener(v -> { //修改自动模式-消毒灯计划 - MMKVUtil.put(AutoModelSet.AUTO_DISINFECT, !AutoModelSet.isAutoDisinfect()); + boolean oldValue = AutoModelSet.isAutoDisinfect(); + MMKVUtil.put(AutoModelSet.AUTO_DISINFECT, !oldValue); + UserLog.operate("本机屏幕", (!oldValue ? "启用" : "停用") + "自动消毒计划"); MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoDisinfect(), binding.llAutoSetParam,binding.llAutoSetParamNone); RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); }); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java index fa8915b..2b46776 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java @@ -18,6 +18,7 @@ import com.example.iot_controlhost.utils.MyUtil; import com.example.iot_controlhost.utils.control.disinfect.DisinfectController; import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.timer.UVTaskUtil; +import com.example.iot_controlhost.utils.log.UserLog; import java.util.Date; @@ -62,6 +63,7 @@ public class UvTaskInsertDialog extends BaseDialog { DisinfectController.isBetween(task.getStartTimeHour(), task.getStartTimeMinute(), task.getEndTimeHour(), task.getEndTimeMinute())); MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> MyUtil.autoControlOperateThird(RoomController.uvLight, target))); adapter.setDataList(UVTaskUtil.getUvTask()); + UserLog.operate("本机屏幕", "新增消毒计划成功:" + startTimeStr + "→" + endTimeStr); dismiss(); } }); diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java index f018905..fc11adf 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java @@ -31,6 +31,7 @@ import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.global.RoomSetting; import com.example.iot_controlhost.utils.global.RxTag; import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.example.iot_controlhost.utils.network.Url; import com.example.iot_controlhost.utils.network.XHttpManager; import com.example.iot_controlhost.utils.network.XHttpShorthand; @@ -281,6 +282,7 @@ public class WorkDialog extends BaseDialog { DialogUtil.hideLoadingDialog(); //记录此次蚕期 SilkwormDBManager.updateCurDate(); + UserLog.task("结束" + (RoomSetting.getType() == 4 ? "催青" : "共育") + "登记成功"); //停止共育后刷新共育状态 RxBusUtils.get().post(RxTag.UPDATE_MAIN, 7); // MyUtil.relaunchApp(); @@ -288,6 +290,9 @@ public class WorkDialog extends BaseDialog { @Override public void error(Throwable e) { + String cultivateType = RoomSetting.getType() == 4 ? "催青" : "共育"; + UserLog.taskError("结束" + cultivateType + "登记失败"); + MyLog.networkError("结束" + cultivateType + "登记失败:" + e.getMessage()); errorTips("服务器-结束失败"); } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java index 24a6a41..0deb14f 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java @@ -43,6 +43,7 @@ import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.global.RoomSetting; import com.example.iot_controlhost.utils.global.RxTag; import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.example.iot_controlhost.utils.network.Url; import com.example.iot_controlhost.utils.network.XHttpManager; import com.example.iot_controlhost.utils.network.XHttpShorthand; @@ -90,14 +91,16 @@ public class WorkNewDialog extends BaseDialog { }); binding.btnStopStage.setOnClickListener(v -> { // 结束阶段 + String stoppedStage = binding.tvStageCurrent.getText().toString(); binding.tvStageCurrent.setText(""); binding.tvStageContent.setText(""); if (RoomSetting.getMode() != 0) { // 进入手动模式 - RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3); + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 5); } - RxBusUtils.get().post(RxTag.UPDATE_MAIN, 5); TempTHVDBManager.INSTANCE.setStage(null, null); + UserLog.task("结束作业阶段:" + (stoppedStage.isEmpty() ? "未命名阶段" : stoppedStage)); + MyLog.app("结束作业阶段,控制模式切换为手动模式:" + stoppedStage); }); binding.btnStopWork.setOnClickListener(v -> { // 结束作业 @@ -177,7 +180,7 @@ public class WorkNewDialog extends BaseDialog { @Override public void error(Throwable e) { errorTips("作业列表获取失败,请检查网络"); - UserLog.taskError("作业列表获取失败" + e.getMessage()); + MyLog.networkError("作业列表获取失败:" + e.getMessage()); } @Override @@ -200,7 +203,7 @@ public class WorkNewDialog extends BaseDialog { @Override public void error(Throwable e) { errorTips("阶段列表获取失败,请检查网络"); - UserLog.taskError("阶段列表获取失败" + e.getMessage()); + MyLog.networkError("阶段列表获取失败:" + e.getMessage()); } @Override @@ -338,6 +341,7 @@ public class WorkNewDialog extends BaseDialog { DialogUtil.hideLoadingDialog(); //记录此次蚕期 SilkwormDBManager.updateCurDate(); + UserLog.task("结束" + (RoomSetting.getType() == 4 ? "催青" : "共育") + "登记成功"); //停止共育后刷新共育状态 RxBusUtils.get().post(RxTag.UPDATE_MAIN, 7); // MyUtil.relaunchApp(); @@ -345,6 +349,9 @@ public class WorkNewDialog extends BaseDialog { @Override public void error(Throwable e) { + String cultivateType = RoomSetting.getType() == 4 ? "催青" : "共育"; + UserLog.taskError("结束" + cultivateType + "登记失败"); + MyLog.networkError("结束" + cultivateType + "登记失败:" + e.getMessage()); errorTips("服务器-结束失败"); } diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java index edc928b..c984414 100644 --- a/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java @@ -24,6 +24,8 @@ import com.example.iot_controlhost.utils.database.SilkwormDBManager; import com.example.iot_controlhost.utils.global.AIModelSet; import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.log.MyLog; import com.example.iot_controlhost.utils.timer.UVTaskUtil; import com.github.mikephil.charting.charts.LineChart; import com.github.mikephil.charting.components.Legend; @@ -147,6 +149,7 @@ public class IntelligenceFragment extends BaseFragment //退回半天 new ConfirmDialog(mContext, getString(R.string.warning), "确定将"+name+"计划返回至12小时前吗", () -> { + int oldBias = AIModelSet.getAiBiasTime(); MMKVUtil.put(AIModelSet.AI_BIAS_TIME, AIModelSet.getAiBiasTime() - 12); + UserLog.operate("本机屏幕", name + "计划后移12小时"); + MyLog.app(name + "计划偏移量修改:" + oldBias + "→" + AIModelSet.getAiBiasTime() + "小时"); refreshInfo(); }).show(); case 3 -> //提前半天 new ConfirmDialog(mContext, getString(R.string.warning), "确定将"+name+"计划提前到12小时后吗", () -> { + int oldBias = AIModelSet.getAiBiasTime(); MMKVUtil.put(AIModelSet.AI_BIAS_TIME, AIModelSet.getAiBiasTime() + 12); + UserLog.operate("本机屏幕", name + "计划前移12小时"); + MyLog.app(name + "计划偏移量修改:" + oldBias + "→" + AIModelSet.getAiBiasTime() + "小时"); refreshInfo(); }).show(); case 4 -> {//消毒 @@ -359,4 +368,4 @@ public class IntelligenceFragment extends BaseFragment AppUtils.exitApp()); } @@ -279,7 +283,11 @@ public class MyUtil { * 重启APP */ public static void relaunchApp() { - UserLog.system("重启APP"); + relaunchApp("本机屏幕"); + } + + public static void relaunchApp(String source) { + UserLog.system(source, "重启软件"); MyUtil.stopAppControl("正在重启软件中", () -> { // AppUtils.relaunchApp(); // 这个方法重启不干净 ProcessPhoenix.triggerRebirth(MyApp.getAppContext()); @@ -290,7 +298,11 @@ public class MyUtil { * 关闭设备 */ public static void shutDown() { - UserLog.system("关闭设备"); + shutDown("本机屏幕"); + } + + public static void shutDown(String source) { + UserLog.system(source, "关闭设备"); //先关闭所有设备 MyUtil.stopAppControl("正在关闭设备中", () -> MyAPIContext.getInstance().shutDown()); } @@ -299,7 +311,11 @@ public class MyUtil { * 重启设备 */ public static void reboot() { - UserLog.system("重启设备"); + reboot("本机屏幕"); + } + + public static void reboot(String source) { + UserLog.system(source, "重启设备"); MyUtil.stopAppControl("正在重启设备中", () -> MyAPIContext.getInstance().reBoot()); } @@ -362,7 +378,6 @@ public class MyUtil { if (controller.isPowerSupply() != targetState) { // 只有在状态不一致时方才输出日志 MyLog.auto((needAutoLog ? "自动:" : "") + (targetState ? "开启" : "关闭") + controller.getName()); - UserLog.operate((needAutoLog ? "自动:" : "") + (targetState ? "开启" : "关闭") + controller.getName()); } while (count < maxCount) { count++; // 将计数器递增放在循环开始处 @@ -373,8 +388,8 @@ public class MyUtil { } } if (!success) { - MyLog.auto((needAutoLog ? "自动" : "") + "自动操作(连续" + maxCount + "次)失败:" + (targetState ? "开启" : "关闭") + controller.getName()); - UserLog.operateError((needAutoLog ? "自动" : "") + "操作(连续" + maxCount + "次)失败:" + (targetState ? "开启" : "关闭") + controller.getName()); + MyLog.autoError((needAutoLog ? "自动" : "系统联动") + "操作(连续" + maxCount + "次)失败:" + + (targetState ? "开启" : "关闭") + controller.getName()); } //自动操作完设备需要更新UI RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java b/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java index 4d6a004..53394f5 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java @@ -77,7 +77,7 @@ public class RemoteControl { //切换模式 int newMode = OldSet.oldModeToNewMode(value); if (RoomSetting.getMode() != newMode) { - int targetSignal = newMode == 0 ? 5 : newMode == 1 ? 3 : 6; + int targetSignal = newMode == 0 ? 15 : newMode == 1 ? 13 : 16; RxBusUtils.get().post(RxTag.UPDATE_MAIN, targetSignal); MyLog.remote("远程设置" + MyUtil.getModeName(newMode) + "模式"); } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java index 1a8b0a8..082d9c7 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java @@ -7,7 +7,6 @@ import com.example.iot_controlhost.utils.MyUtil; import com.example.iot_controlhost.utils.control.Control; import com.example.iot_controlhost.utils.global.RoomController; import com.example.iot_controlhost.utils.log.MyLog; -import com.example.iot_controlhost.utils.log.UserLog; import com.example.iot_controlhost.utils.timer.UVTaskUtil; import java.util.Calendar; @@ -72,7 +71,6 @@ public class DisinfectController extends Control { @Override protected void stopMethod() { MyLog.auto("停止自动控制消毒————————————————————————————————————————————————————"); - UserLog.operate("自动:停止自动控制消毒"); for (MyTask task : UVTaskUtil.getUvTask()) { UVTaskUtil.stopTask(task); } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java index 2ce7ba1..b246947 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java @@ -12,7 +12,6 @@ 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.example.iot_controlhost.utils.log.UserLog; import com.xuexiang.rxutil2.rxjava.DisposablePool; import com.xuexiang.rxutil2.rxjava.RxJavaUtils; @@ -77,6 +76,5 @@ public class EvenController extends Control { MyUtil.autoControlOperateThird(RoomController.evenFan); })); MyLog.auto("停止自动控制匀风————————————————————————————————————————————————————"); - UserLog.operate("自动:停止自动控制匀风"); } } 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 b0e3272..4e0e7cc 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,7 +15,6 @@ 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.example.iot_controlhost.utils.log.UserLog; import com.xuexiang.rxutil2.rxjava.DisposablePool; import com.xuexiang.rxutil2.rxjava.RxJavaUtils; @@ -150,7 +149,6 @@ public class HumidityController extends Control { } })); MyLog.auto("停止自动控制湿度————————————————————————————————————————————————————"); - UserLog.operate("自动:停止自动控制湿度"); } } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java index 573604c..76372f1 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java @@ -17,7 +17,6 @@ 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.example.iot_controlhost.utils.log.UserLog; /** * @Description 自动模式-控制逻辑-温度 策略模式+工厂方法 @@ -78,10 +77,6 @@ public class TemperatureController extends Control { "\n-算法" + AutoModelSet.getAutoTemperatureMode() + "\n-加热方式" + AutoModelSet.getAutoTemperatureHeat() + "\n-降温方式" + AutoModelSet.getAutoTemperatureCool()); - UserLog.operate("手动:更新温控参数为:" + - "\n-算法" + AutoModelSet.getAutoTemperatureMode() + - "\n-加热方式:" + AutoModelSet.getAutoTemperatureHeat() + - "\n-降温方式:" + AutoModelSet.getAutoTemperatureCool()); start(); strategy = getStrategy(); start = startMethod(); @@ -121,7 +116,6 @@ public class TemperatureController extends Control { * 停止自动控制 */ protected void stopMethod() { - UserLog.operate("自动:停止自动控制温度,关闭空调,关闭地暖"); MyLog.auto("停止自动控制温度————————————————————————————————————————————————————"); MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { MyUtil.autoControlOperateThird(RoomController.airConditionInfrared); 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 fe844b0..34240e9 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,7 +9,6 @@ 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,13 +152,13 @@ public abstract class ControlStrategy { if (!tipsTempStr.equals(tips.toString())) { //防止重复发送相同的提示日志 tipsTempStr = tips.toString(); - UserLog.operate("自动:" + tips); + MyLog.auto("自动温控:" + tips); } } else { if (mode != -1 && temp != -1 && RoomSetting.isAirConditionForceClose()) { tips.append(",在消毒期间无法开启空调"); } - UserLog.operateError("自动操作失败:" + tips); + MyLog.autoError("自动温控操作失败:" + tips); } } } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java b/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java index a43adba..6dec73b 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java @@ -1,142 +1,262 @@ package com.example.iot_controlhost.utils.database; -import com.blankj.utilcode.util.StringUtils; +import android.util.Log; + import com.example.iot_controlhost.MyApp; -import com.example.iot_controlhost.model.Log; import com.example.iot_controlhost.model.LogDao; import com.example.iot_controlhost.utils.log.MyLog; import com.example.iot_controlhost.utils.log.UserLog; -import com.xuexiang.rxutil2.rxjava.RxJavaUtils; -import com.xuexiang.rxutil2.rxjava.task.RxIOTask; import org.greenrobot.greendao.query.QueryBuilder; import org.greenrobot.greendao.query.WhereCondition; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class LogDBManager { +/** + * 日志数据库访问入口。 + * + *

所有日志数据库操作都在同一个线程中串行执行,避免高频日志写入时创建大量线程, + * 同时保证用户日志在上传服务器前已经完成本地落库。

+ */ +public final class LogDBManager { - private static ExecutorService executor = Executors.newCachedThreadPool(); + private static final String LOGCAT_TAG = "LogDBManager"; + private static final int PAGE_SIZE = 20; + private static final int RETENTION_DAYS = 15; + private static final String CREATE_DATETIME_INDEX = + "CREATE INDEX IF NOT EXISTS IDX_LOG_DATETIME_ID ON LOG (DATETIME DESC, _id DESC)"; + private static final String CREATE_TAG_DATETIME_INDEX = + "CREATE INDEX IF NOT EXISTS IDX_LOG_TAG_DATETIME_ID ON LOG (TAG, DATETIME DESC, _id DESC)"; - /** - * 增加 - */ - public static void insertLog(Log log) { - executor.execute(() -> logDao.insert(log)); + private static volatile Thread databaseThread; + private static volatile boolean indexesReady; + + private static final ExecutorService EXECUTOR = Executors.newSingleThreadExecutor(runnable -> { + Thread thread = new Thread(() -> { + databaseThread = Thread.currentThread(); + runnable.run(); + }, "log-database"); + thread.setPriority(Thread.NORM_PRIORITY - 1); + return thread; + }); + + private static final LogDao LOG_DAO = MyApp.getDaoSession().getLogDao(); + + private LogDBManager() { } /** - * 每页的长度 + * 后台准备日志索引。首次升级可能需要一定时间,但不会阻塞应用启动流程。 */ - private static int pageSize = 20; - /** - * 日志操作类 - */ - private static LogDao logDao = MyApp.getDaoSession().getLogDao(); + public static void prepareAsync() { + EXECUTOR.execute(LogDBManager::ensureIndexes); + } /** - * 删除 + * 异步增加普通诊断日志。 */ + public static void insertLog(com.example.iot_controlhost.model.Log log) { + EXECUTOR.execute(() -> insertInternal(log)); + } + + /** + * 同步落库,供需要确保“本地成功后再上传”的用户日志使用。 + */ + public static boolean insertLogSync(com.example.iot_controlhost.model.Log log) { + return executeSync(() -> insertInternal(log), false); + } + + private static boolean insertInternal(com.example.iot_controlhost.model.Log log) { + try { + LOG_DAO.insert(log); + return true; + } catch (Exception e) { + Log.e(LOGCAT_TAG, "日志写入数据库失败", e); + return false; + } + } + + /** + * 查询最新一页日志。 + */ + public static List queryLatest(String filter) { + return queryPage(filter, null); + } + + /** + * 使用最后一条日志作为游标查询下一页,避免 OFFSET 在并发插入时出现重复或漏项。 + */ + public static List queryNext(String filter, + com.example.iot_controlhost.model.Log cursor) { + if (cursor == null) { + return Collections.emptyList(); + } + return queryPage(filter, cursor); + } + + public static boolean isFullPage(List logs) { + return logs != null && logs.size() == PAGE_SIZE; + } + + private static List queryPage( + String filter, com.example.iot_controlhost.model.Log cursor) { + return executeSync(() -> { + ensureIndexes(); + QueryBuilder qb = LOG_DAO.queryBuilder(); + List tags = UserLog.getStorageTagsForFilter(filter); + qb.where(LogDao.Properties.Tag.in(tags)); + if (cursor != null) { + WhereCondition olderDate = LogDao.Properties.Datetime.lt(cursor.getDatetime()); + WhereCondition sameDateAndOlderId = qb.and( + LogDao.Properties.Datetime.eq(cursor.getDatetime()), + LogDao.Properties.Id.lt(cursor.getId())); + qb.where(qb.or(olderDate, sameDateAndOlderId)); + } + return qb.orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) + .limit(PAGE_SIZE) + .list(); + }, Collections.emptyList()); + } + + /** + * 兼容旧调用;新界面应使用游标分页。 + */ + public static List queryList(String filter, int currentPage) { + return executeSync(() -> { + ensureIndexes(); + return LOG_DAO.queryBuilder() + .where(LogDao.Properties.Tag.in(UserLog.getStorageTagsForFilter(filter))) + .orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) + .offset(Math.max(0, currentPage - 1) * PAGE_SIZE) + .limit(PAGE_SIZE) + .list(); + }, Collections.emptyList()); + } + + public static List queryDateRange(Date startDate, Date endDate) { + return executeSync(() -> { + ensureIndexes(); + return LOG_DAO.queryBuilder() + .where(LogDao.Properties.Datetime.between(startDate, endDate)) + .orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) + .list(); + }, Collections.emptyList()); + } + + /** + * 导出最近七天系统和网络诊断日志。 + */ + public static List queryAllList() { + return executeSync(() -> { + ensureIndexes(); + Calendar calendar = Calendar.getInstance(); + Date currentDate = calendar.getTime(); + calendar.add(Calendar.DAY_OF_YEAR, -7); + Date oneWeekAgo = calendar.getTime(); + return LOG_DAO.queryBuilder() + .where(LogDao.Properties.Datetime.between(oneWeekAgo, currentDate)) + .where(LogDao.Properties.Tag.in( + MyLog.TAG_APP, + MyLog.TAG_APP + MyLog.TAG_ERROR, + MyLog.TAG_NETWORK, + MyLog.TAG_NETWORK + MyLog.TAG_ERROR)) + .orderDesc(LogDao.Properties.Datetime, LogDao.Properties.Id) + .list(); + }, Collections.emptyList()); + } + + public static List queryWorkLogs(int currentPage) { + return queryList(UserLog.FILTER_ALL, currentPage); + } + public static void removeLog(Long id) { - logDao.deleteByKey(id); + executeSync(() -> { + LOG_DAO.deleteByKey(id); + return null; + }, null); } public static void removeAll() { - logDao.deleteAll(); + executeSync(() -> { + LOG_DAO.deleteAll(); + return null; + }, null); } - public static List queryDateRange(Date startDate, Date endDate) { - QueryBuilder qb = logDao.queryBuilder(); - // 创建日期区间查询条件 - WhereCondition dateCondition = LogDao.Properties.Datetime.between(startDate, endDate); - QueryBuilder dateRangeQueryBuilder = qb.where(dateCondition); - return dateRangeQueryBuilder.list(); - } - - - /** - * 返回某类型的所有日志 - * - * @param tagType MyLog类下的TAG常量 为空时返回全部 - */ - public static List queryList(String tagType, int currentPage) { - QueryBuilder qb = logDao.queryBuilder(); - if (!StringUtils.isEmpty(tagType)) { - qb.where(LogDao.Properties.Tag.like("%" + tagType + "%")); - }else{ - qb.whereOr( - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_OPERATE + "%"), - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SENSOR + "%"), - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_TASK + "%"), - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SYSTEM + "%") - ); - } - return qb.offset((currentPage - 1) * pageSize) - .limit(pageSize) - .orderDesc(LogDao.Properties.Datetime) - .list(); + public static void update(com.example.iot_controlhost.model.Log log) { + executeSync(() -> { + LOG_DAO.update(log); + return null; + }, null); } /** - * 返回某类型的所有日志 + * 每日后台清理十五天前的日志。 */ - public static List queryAllList() { - QueryBuilder qb = logDao.queryBuilder(); - Calendar calendar = Calendar.getInstance(); - Date currentDate = calendar.getTime(); - calendar.add(Calendar.DAY_OF_YEAR, -7); - Date oneWeekAgo = calendar.getTime(); - return qb.orderDesc(LogDao.Properties.Datetime) - .where(LogDao.Properties.Datetime.between(oneWeekAgo, currentDate)) - .whereOr( - LogDao.Properties.Tag.like("%" + MyLog.TAG_APP + "%"), - LogDao.Properties.Tag.like("%" + MyLog.TAG_NETWORK + "%") - ) - .list(); - } - - public static List queryWorkLogs(int currentPage) { - return logDao.queryBuilder() - .orderDesc(LogDao.Properties.Datetime) - .whereOr( - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_OPERATE + "%"), - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SENSOR + "%"), - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_TASK + "%"), - LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SYSTEM + "%") - ) - .offset((currentPage - 1) * pageSize) - .limit(pageSize) - .list(); - } - - /** - * 改 - */ - public static void update(Log log) { - logDao.update(log); - } - - /** - * 删除1个月前的日志 - */ - public static void clearLogsOlderThanOneMonthsSync() { - try { + public static void clearExpiredLogsAsync() { + EXECUTOR.execute(() -> { + long start = System.currentTimeMillis(); + ensureIndexes(); Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.MONTH, -1); - Date oneMonthAgo = calendar.getTime(); - - logDao.queryBuilder() - .where(LogDao.Properties.Datetime.lt(oneMonthAgo)) - .buildDelete() - .executeDeleteWithoutDetachingEntities(); + calendar.add(Calendar.DAY_OF_YEAR, -RETENTION_DAYS); + Date cutoff = calendar.getTime(); + try { + long count = LOG_DAO.queryBuilder() + .where(LogDao.Properties.Datetime.lt(cutoff)) + .count(); + if (count > 0) { + LOG_DAO.queryBuilder() + .where(LogDao.Properties.Datetime.lt(cutoff)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + LOG_DAO.detachAll(); + } + long duration = System.currentTimeMillis() - start; + MyLog.app("日志维护完成:保留" + RETENTION_DAYS + "天,清理" + count + + "条,耗时" + duration + "ms"); + } catch (Exception e) { + Log.e(LOGCAT_TAG, "清理过期日志失败", e); + } + }); + } + private static void ensureIndexes() { + if (indexesReady) { + return; + } + try { + LOG_DAO.getDatabase().execSQL(CREATE_DATETIME_INDEX); + LOG_DAO.getDatabase().execSQL(CREATE_TAG_DATETIME_INDEX); + indexesReady = true; } catch (Exception e) { - e.printStackTrace(); + Log.e(LOGCAT_TAG, "创建日志索引失败", e); } } + private static T executeSync(Callable task, T fallback) { + if (Thread.currentThread() == databaseThread) { + try { + return task.call(); + } catch (Exception e) { + Log.e(LOGCAT_TAG, "日志数据库操作失败", e); + return fallback; + } + } + try { + return EXECUTOR.submit(task).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.e(LOGCAT_TAG, "等待日志数据库操作时被中断", e); + } catch (ExecutionException e) { + Log.e(LOGCAT_TAG, "日志数据库操作失败", e.getCause()); + } + return fallback; + } } diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java index ceb52d9..ef7affa 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java @@ -300,11 +300,11 @@ public class RoomController { if (RoomController.dehumidifier.isAvailable() || RoomController.dehumidifier485Relay.isAvailable()) { PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_DEHUMIDIFIER_STATUS, 60, () -> { if (RoomController.dehumidifier.isAvailable() && RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() < 100) { - MyLog.controllerError("除湿机电源已开启但功率不足100W,现尝试再次开启除湿机物理电源"); - MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupplyForOld(true))); + MyLog.controllerError("除湿机电源已开启但功率不足100W,现尝试再次开启除湿机红外电源");//红外开关是一样的指令 所以这里等于是重新发送 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupply(true))); } else if (RoomController.dehumidifier.isAvailable() && !RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() > 100) { - MyLog.controllerError("除湿机电源已关闭但功率大于100W,现尝试再次关闭除湿机物理电源"); - MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupplyForOld(false))); + MyLog.controllerError("除湿机电源已关闭但功率大于100W,现尝试再次关闭除湿机红外电源"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupply(false))); } if (RoomController.dehumidifier485Relay.isAvailable() && RoomController.dehumidifier485.isPowerSupply() @@ -330,7 +330,7 @@ public class RoomController { doNotNeedDelayCloseAirCondition = true; // MyLog.test("空调后台线程:不采取操作"); } - + // 红外除湿机 if (dehumidifier.isAvailable() && !dehumidifierInfrared.isPowerSupply() && doNotNeedDelayCloseDehumidifier) { diff --git a/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java index 183c484..122d77b 100644 --- a/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java +++ b/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java @@ -4,7 +4,6 @@ package com.example.iot_controlhost.utils.log; import android.content.Context; import android.content.Intent; -import com.example.iot_controlhost.utils.network.TopicClass; import com.jakewharton.processphoenix.ProcessPhoenix; import com.xuexiang.rxutil2.rxjava.RxJavaUtils; @@ -36,7 +35,7 @@ public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread thread, Throwable ex) { ex.printStackTrace(); MyLog.appError("软件已崩溃,重启应用:" + getFormattedException(ex)); - TopicClass.uploadLog(4, 3, "已崩溃,系统将自动重启:" + getFormattedException(ex)); + UserLog.systemErrorImmediately("程序运行异常,系统将自动重启"); new Thread(() -> { try { Thread.sleep(500); } catch (Exception ignored) { } ProcessPhoenix.triggerRebirth(mContext); @@ -69,4 +68,4 @@ public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler { } -} \ No newline at end of file +} 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 f14fb8d..1e95688 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 @@ -10,6 +10,7 @@ import com.xuexiang.rxutil2.rxjava.RxJavaUtils; import com.xuexiang.rxutil2.rxjava.task.RxIOTask; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; @@ -20,6 +21,12 @@ import timber.log.Timber; * 日志用户查看用 */ public class UserLog extends Timber.Tree { + public static final String FILTER_ALL = "全部上报日志"; + public static final String FILTER_OPERATE = "操作日志"; + public static final String FILTER_SENSOR = "传感器日志"; + public static final String FILTER_TASK = "作业日志"; + public static final String FILTER_SYSTEM = "系统日志"; + /** * 用户-操作类 */ @@ -43,14 +50,69 @@ public class UserLog extends Timber.Tree { return tags; } + public static List getUserQueryFilters() { + return Arrays.asList(FILTER_ALL, FILTER_OPERATE, FILTER_SENSOR, FILTER_TASK, FILTER_SYSTEM); + } + + /** + * 将界面筛选项转换为实际存储标签,成功和错误级别都包含在同一分类中。 + */ + public static List getStorageTagsForFilter(String filter) { + 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)) { + return tagsWithError(TAG_USER_SENSOR); + } + if (FILTER_TASK.equals(filter) || TAG_USER_TASK.equals(filter)) { + return tagsWithError(TAG_USER_TASK); + } + if (FILTER_SYSTEM.equals(filter) || TAG_USER_SYSTEM.equals(filter)) { + return tagsWithError(TAG_USER_SYSTEM); + } + List tags = new ArrayList<>(); + for (String userTag : getUserLogTags()) { + tags.addAll(tagsWithError(userTag)); + } + return tags; + } + + private static List tagsWithError(String tag) { + return Arrays.asList(tag, tag + MyLog.TAG_ERROR); + } + + 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 "系统"; + } + public static void operate(String msg) { Timber.tag(TAG_USER_OPERATE).i(msg); } + public static void operate(String source, String msg) { + operate(formatSource(source, msg)); + } + public static void operateError(String msg) { Timber.tag(TAG_USER_OPERATE + MyLog.TAG_ERROR).e(msg); } + public static void operateError(String source, String msg) { + operateError(formatSource(source, msg)); + } + public static void task(String msg) { Timber.tag(TAG_USER_TASK).i(msg); } @@ -71,46 +133,75 @@ public class UserLog extends Timber.Tree { Timber.tag(TAG_USER_SYSTEM).i(msg); } + public static void system(String source, String msg) { + system(formatSource(source, msg)); + } + public static void systemError(String msg) { Timber.tag(TAG_USER_SYSTEM + MyLog.TAG_ERROR).e(msg); } + /** + * 崩溃场景使用同步入口,确保进程重启前日志已经落库。 + */ + public static void systemErrorImmediately(String msg) { + persistAndUpload(TAG_USER_SYSTEM + MyLog.TAG_ERROR, Log.ERROR, msg, new Date()); + } + + private static String formatSource(String source, String msg) { + return "[来源=" + source + "] " + msg; + } + @Override protected void log(int priority, String tag, String message, Throwable t) { //只有特定UserLog日志才能处理 - if (!tag.contains(TAG_USER_OPERATE) && !tag.contains(TAG_USER_SENSOR) && !tag.contains(TAG_USER_TASK) && !tag.contains(TAG_USER_SYSTEM)) { + if (tag == null || !isUserLogTag(tag)) { return; } RxJavaUtils.doInIOThread(new RxIOTask<>(null) { @Override public Void doInIOThread(Object o) { - //发送日志到服务器 - int tagInt = 0; - if (tag.contains(TAG_USER_OPERATE)) { - tagInt = 1; - } else if (tag.contains(TAG_USER_SENSOR)) { - tagInt = 2; - } else if (tag.contains(TAG_USER_TASK)) { - tagInt = 3; - } else if (tag.contains(TAG_USER_SYSTEM)) { - tagInt = 4; - } - int newPriority; - //1调试 2信息 3警告 - if (tag.contains(MyLog.TAG_ERROR)) { - newPriority = 3; - } else { - newPriority = 2; - } - //保存日志到数据库 - LogDBManager.insertLog(new com.example.iot_controlhost.model.Log(tag, priority, new Date(), message)); - //输出日志到控制台 - Log.println(priority, tag, message); - //上传日志 - TopicClass.uploadLog(tagInt, newPriority, message); + persistAndUpload(tag, priority, message, new Date()); return null; } }); } + private static boolean isUserLogTag(String tag) { + return tag.contains(TAG_USER_OPERATE) + || tag.contains(TAG_USER_SENSOR) + || tag.contains(TAG_USER_TASK) + || tag.contains(TAG_USER_SYSTEM); + } + + private static void persistAndUpload(String tag, int priority, String message, Date datetime) { + com.example.iot_controlhost.model.Log entry = + new com.example.iot_controlhost.model.Log(tag, priority, datetime, message); + boolean saved = LogDBManager.insertLogSync(entry); + Log.println(priority, tag, message); + if (!saved) { + Log.e(TAG_USER_SYSTEM, "用户日志本地落库失败,已取消上传以避免两端数据不一致"); + return; + } + TopicClass.uploadLog(toServerTag(tag), toServerPriority(tag), message, datetime); + } + + private static int toServerTag(String tag) { + if (tag.contains(TAG_USER_OPERATE)) { + return 1; + } + if (tag.contains(TAG_USER_SENSOR)) { + return 2; + } + if (tag.contains(TAG_USER_TASK)) { + return 3; + } + return 4; + } + + private static int toServerPriority(String tag) { + // 服务端:1调试、2信息、3警告 + return tag.contains(MyLog.TAG_ERROR) ? 3 : 2; + } + } 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 ce5d93e..6f372f1 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 @@ -33,6 +33,7 @@ import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; +import java.util.Date; import java.util.List; /** @@ -132,35 +133,41 @@ public class TopicClass { */ public static void hanldeSerianetMessage(String msg) { String tip = null; + boolean actionLogged = false; switch (msg) { case "重启" -> { - tip = "远程:重启设备"; - MyUtil.reboot(); + tip = "重启设备"; + MyUtil.reboot("远程控制"); + actionLogged = true; } case "关机" -> { - tip = "远程:关闭设备"; - MyUtil.shutDown(); + tip = "关闭设备"; + MyUtil.shutDown("远程控制"); + actionLogged = true; } case "重启软件" -> { - tip = "远程:重启软件"; - MyUtil.relaunchApp(); + tip = "重启软件"; + MyUtil.relaunchApp("远程控制"); + actionLogged = true; } case "更新" -> { - tip = "远程:强制更新"; + tip = "强制更新软件"; AppUpdateUtil.updateAPP(ActivityUtils.getTopActivity(), true, true); } case "下载frp" -> { - tip = "远程:更新远程控制软件"; + tip = "更新远程控制软件"; FrpUpdateUtil.check(ActivityUtils.getTopActivity(), true); } case "检测更新" -> { - tip = "远程:检测更新"; + tip = "检测软件更新"; AppUpdateUtil.updateAPP(ActivityUtils.getTopActivity(), false, false); } } if (!StringUtils.isEmpty(tip)) { TopicClass.receiveSuccess(msg); - UserLog.operate(tip); + if (!actionLogged) { + UserLog.operate("远程控制", tip); + } } } @@ -197,7 +204,10 @@ public class TopicClass { MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { boolean operateSuccess = RemoteControl.remoteControl(controlCommand, gear, value); if (operateSuccess) { - UserLog.operate("远程:" + getCommandNameById(controlCommand, value)); + String commandName = getCommandNameById(controlCommand, value); + if (controlCommand != 8) { + UserLog.operate("远程控制", "设备操作成功:" + commandName); + } //设置完远程控制后 上传最新当前状态 if (controlCommand == 8) { // 如果是切换模式,为优化速度,直接上传最新的模式,不等转换完成, @@ -206,7 +216,9 @@ public class TopicClass { uploadDeviceState(true);//设置完远程控制后 上传最新当前状态 } } else { - UserLog.operateError("远程操作失败:" + getCommandNameById(controlCommand, value)); + String commandName = getCommandNameById(controlCommand, value); + UserLog.operateError("远程控制", controlCommand == 8 ? "模式切换失败" : "设备操作失败:" + commandName); + MyLog.remoteError("远程操作失败:" + commandName + ",指令=" + controlCommand + ",值=" + value); } //更新手动模式下设备UI RxBusUtils.get().post(RxTag.UPDATE_MANUAL, 0); @@ -293,13 +305,17 @@ public class TopicClass { * 上传日志到服务器 */ public static void uploadLog(int tag, int priority, String message) { + uploadLog(tag, priority, message, new Date()); + } + + public static void uploadLog(int tag, int priority, String message, Date datetime) { 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()); + json.put("datetime", MyUtil.getDateTime(datetime)); MQTTService.publish(DeviceRunLogsPost(), json.toString()); } catch (JSONException e) { e.printStackTrace(); @@ -588,7 +604,7 @@ public class TopicClass { //2025年5月7日:取消以下逻辑 // //切换为自动模式 // RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3); - UserLog.operate("远程:已更新自动模式参数"); + UserLog.operate("远程控制", "自动模式参数更新成功"); if (RoomSetting.getMode() == 1) { // 如果是自动模式,则刷新自动模式参数 RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3);