Compare commits
4
Commits
a42d6ceec6
...
381605cf70
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
381605cf70 | ||
|
|
090b120365 | ||
|
|
4de70ff57a | ||
|
|
b5fa7e1841 |
@@ -5,3 +5,4 @@ app/build/
|
|||||||
build/
|
build/
|
||||||
.kotlin/
|
.kotlin/
|
||||||
*.pdf
|
*.pdf
|
||||||
|
.gradle-home/
|
||||||
|
|||||||
+3
-24
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
id 'com.android.application'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.0.0"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -10,8 +9,8 @@ android {
|
|||||||
applicationId "com.example.iot_controlhost"
|
applicationId "com.example.iot_controlhost"
|
||||||
minSdkVersion 25
|
minSdkVersion 25
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 163
|
versionCode 164
|
||||||
versionName "3.5.0.163"
|
versionName "3.5.0.164"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
ndk {
|
ndk {
|
||||||
moduleName "mcu"
|
moduleName "mcu"
|
||||||
@@ -66,7 +65,6 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
dataBinding true
|
dataBinding true
|
||||||
compose true
|
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '17'
|
jvmTarget = '17'
|
||||||
@@ -76,30 +74,11 @@ android {
|
|||||||
abortOnError false
|
abortOnError false
|
||||||
checkReleaseBuilds false
|
checkReleaseBuilds false
|
||||||
}
|
}
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion rootProject.composeVersion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'org.greenrobot.greendao'
|
apply plugin: 'org.greenrobot.greendao'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Compose
|
|
||||||
implementation "androidx.activity:activity-compose:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.runtime:runtime:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.ui:ui:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.foundation:foundation:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.foundation:foundation-layout:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.material:material:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.runtime:runtime-livedata:$rootProject.composeVersion"
|
|
||||||
implementation "androidx.compose.ui:ui-tooling:$rootProject.composeVersion"
|
|
||||||
implementation("androidx.compose.material3:material3:1.4.0-alpha12")
|
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
|
|
||||||
implementation("io.github.ltttttttttttt:ComposeViews:1.6.0.1")
|
|
||||||
implementation ("io.github.ehsannarmani:compose-charts:0.1.2")
|
|
||||||
// implementation "com.google.android.material:compose-theme-adapter:$rootProject.composeVersion"
|
|
||||||
|
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'com.google.android.material:material:1.8.0'
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
@@ -196,4 +175,4 @@ dependencies {
|
|||||||
//
|
//
|
||||||
// // optional - Test helpers for Lifecycle runtime
|
// // optional - Test helpers for Lifecycle runtime
|
||||||
// testImplementation "androidx.lifecycle:lifecycle-runtime-testing:$lifecycle_version"
|
// testImplementation "androidx.lifecycle:lifecycle-runtime-testing:$lifecycle_version"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,10 +82,6 @@
|
|||||||
android:name=".ui.activity.DebugActivity"
|
android:name=".ui.activity.DebugActivity"
|
||||||
android:exported="true" />
|
android:exported="true" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".ui.activity.ComposeActivity"
|
|
||||||
android:exported="true" />
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.MQTTService"
|
android:name=".service.MQTTService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -160,4 +156,4 @@
|
|||||||
android:value="true" />
|
android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.scwang.smart.refresh.footer.ClassicsFooter;
|
|||||||
import com.scwang.smart.refresh.header.ClassicsHeader;
|
import com.scwang.smart.refresh.header.ClassicsHeader;
|
||||||
import com.scwang.smart.refresh.layout.SmartRefreshLayout;
|
import com.scwang.smart.refresh.layout.SmartRefreshLayout;
|
||||||
|
|
||||||
|
import org.xutils.BuildConfig;
|
||||||
import org.xutils.x;
|
import org.xutils.x;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -53,6 +54,11 @@ public class MyApp extends Application {
|
|||||||
// 初始化日志库
|
// 初始化日志库
|
||||||
Timber.plant(new MyLog());
|
Timber.plant(new MyLog());
|
||||||
Timber.plant(new UserLog());
|
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()) {
|
if (OldSet.isFirstTime()) {
|
||||||
OldSet.importOldSet(getApplicationContext());
|
OldSet.importOldSet(getApplicationContext());
|
||||||
}
|
}
|
||||||
@@ -152,4 +158,4 @@ public class MyApp extends Application {
|
|||||||
return mSession;
|
return mSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import com.example.iot_controlhost.base.BaseRecyclerAdapter;
|
|||||||
import com.example.iot_controlhost.databinding.ItemLogBinding;
|
import com.example.iot_controlhost.databinding.ItemLogBinding;
|
||||||
import com.example.iot_controlhost.model.Log;
|
import com.example.iot_controlhost.model.Log;
|
||||||
import com.example.iot_controlhost.utils.global.RoomSetting;
|
import com.example.iot_controlhost.utils.global.RoomSetting;
|
||||||
import com.example.iot_controlhost.utils.log.UserLog;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ public class LogAdapter extends BaseRecyclerAdapter<Log, ItemLogBinding> {
|
|||||||
data.getLevel() == android.util.Log.ERROR ? R.color.red : RoomSetting.isDarkTheme() ? R.color.white : R.color.black);
|
data.getLevel() == android.util.Log.ERROR ? R.color.red : RoomSetting.isDarkTheme() ? R.color.white : R.color.black);
|
||||||
binding.tvMessage.setTextColor(targetColorId);
|
binding.tvMessage.setTextColor(targetColorId);
|
||||||
binding.tvDate.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()));
|
binding.tvDate.setText(TimeUtils.date2String(data.getDatetime()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.databinding.ViewDataBinding;
|
import androidx.databinding.ViewDataBinding;
|
||||||
|
|
||||||
import com.example.iot_controlhost.R;
|
import com.example.iot_controlhost.R;
|
||||||
|
import com.example.iot_controlhost.ui.activity.MainActivity;
|
||||||
import com.example.iot_controlhost.utils.log.MyLog;
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -27,11 +28,31 @@ import es.dmoral.toasty.Toasty;
|
|||||||
public abstract class BaseDialog<Binding extends ViewDataBinding> extends Dialog {
|
public abstract class BaseDialog<Binding extends ViewDataBinding> extends Dialog {
|
||||||
protected Binding binding;
|
protected Binding binding;
|
||||||
protected Context mContext;
|
protected Context mContext;
|
||||||
|
private boolean lockTimerNotified;
|
||||||
|
|
||||||
public BaseDialog(@NonNull Context context) {
|
public BaseDialog(@NonNull Context context) {
|
||||||
this(context, false);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
package com.example.iot_controlhost.base
|
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.blankj.utilcode.util.ActivityUtils
|
|
||||||
import com.example.iot_controlhost.utils.DialogUtil
|
|
||||||
import com.example.iot_controlhost.utils.PollingTask
|
|
||||||
import es.dmoral.toasty.Toasty
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
open class BaseViewModel : ViewModel() {
|
|
||||||
|
|
||||||
protected var pollingTask: PollingTask =
|
|
||||||
PollingTask.getInstance(javaClass.simpleName) // 使用类名作为ID
|
|
||||||
|
|
||||||
protected fun doInUIThread(task: () -> Unit) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
task()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun <T> doInIoThreadThenUI(
|
|
||||||
loadingTips: String = "正在加载中",
|
|
||||||
showDialog: Boolean = true,
|
|
||||||
onError: (Throwable) -> Unit = { },
|
|
||||||
onIO: suspend () -> T,
|
|
||||||
onUI: (T) -> Unit,
|
|
||||||
) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
val result = runCatching {
|
|
||||||
if (showDialog) {
|
|
||||||
DialogUtil.showLoadingDialog(ActivityUtils.getTopActivity(),loadingTips)
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
onIO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
if (showDialog) {
|
|
||||||
DialogUtil.hideLoadingDialog()
|
|
||||||
}
|
|
||||||
result.onSuccess { data ->
|
|
||||||
onUI(data)
|
|
||||||
}.onFailure { exception ->
|
|
||||||
exception.printStackTrace()
|
|
||||||
onError(exception)
|
|
||||||
exception.message?.let {
|
|
||||||
Toasty.error(ActivityUtils.getTopActivity(),it).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> doInIoThread(
|
|
||||||
loadingTips: String = "正在加载中",
|
|
||||||
showDialog: Boolean = true,
|
|
||||||
onError: (Throwable) -> Unit = { },
|
|
||||||
doInIO: suspend () -> T,
|
|
||||||
) {
|
|
||||||
doInIoThreadThenUI(loadingTips, showDialog, onError, doInIO) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> doInIoThreadNoDialog(
|
|
||||||
onError: (Throwable) -> Unit = { },
|
|
||||||
task: suspend () -> T,
|
|
||||||
) {
|
|
||||||
doInIoThread(showDialog = false, doInIO = task, onError = onError)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在IO线程中执行任务,可选择是否显示加载对话框
|
|
||||||
*/
|
|
||||||
fun doInIoThreadWith(showLoading: Boolean,loadingTips: String, function: suspend () -> Unit) {
|
|
||||||
if (showLoading) {
|
|
||||||
doInIoThread(loadingTips) { function() }
|
|
||||||
} else {
|
|
||||||
doInIoThreadNoDialog { function() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val scope = CoroutineScope(Dispatchers.IO)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动一个无限轮询任务
|
|
||||||
*
|
|
||||||
* @param pollingInterval 轮询间隔时间(单位:秒)
|
|
||||||
* @param pollingTask 轮询任务的挂起函数
|
|
||||||
*/
|
|
||||||
fun polling(intervalSeconds: Long, task: suspend () -> Unit) {
|
|
||||||
scope.launch {
|
|
||||||
while (true) {
|
|
||||||
task()
|
|
||||||
delay(intervalSeconds * 1000L) // 转换秒为毫秒
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun delayPolling(delaySeconds: Long, intervalSeconds: Long, task: suspend () -> Unit) {
|
|
||||||
delay(delaySeconds * 1000L)
|
|
||||||
polling(intervalSeconds, task)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import com.example.iot_controlhost.utils.MyUtil;
|
|||||||
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.RoomController;
|
import com.example.iot_controlhost.utils.global.RoomController;
|
||||||
import com.example.iot_controlhost.utils.global.SpinnerList;
|
import com.example.iot_controlhost.utils.global.SpinnerList;
|
||||||
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description 换气扇(总控所有换气扇)
|
* @Description 换气扇(总控所有换气扇)
|
||||||
@@ -27,23 +28,55 @@ public class Fan extends Relay {
|
|||||||
@Override
|
@Override
|
||||||
public boolean setPowerSupply(boolean target) {
|
public boolean setPowerSupply(boolean target) {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
|
String mode = AutoModelSet.getVentilatorMode();
|
||||||
|
boolean intakeAvailable = fans[0].isAvailable();
|
||||||
|
boolean exhaustAvailable = fans[1].isAvailable();
|
||||||
|
MyLog.auto("换气扇分路解析:目标=" + (target ? "开启" : "关闭") + ",模式=" + mode
|
||||||
|
+ ",进气扇可用=" + intakeAvailable + ",端口=" + fans[0].getPorts()
|
||||||
|
+ ";排气扇可用=" + exhaustAvailable + ",端口=" + fans[1].getPorts());
|
||||||
if (target) {
|
if (target) {
|
||||||
if (SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode())
|
if ((SpinnerList.VENTILATION_MODES[0].equals(mode)
|
||||||
|| SpinnerList.VENTILATION_MODES[1].equals(AutoModelSet.getVentilatorMode())) {
|
|| SpinnerList.VENTILATION_MODES[1].equals(mode)) && intakeAvailable) {
|
||||||
result = MyUtil.autoControlOperateThird(fans[0], true,false);
|
boolean intakeResult = MyUtil.autoControlOperateThird(fans[0], true,false);
|
||||||
|
MyLog.auto("换气扇分路执行:设备=进气扇,目标=开启,端口=" + fans[0].getPorts()
|
||||||
|
+ ",结果=" + (intakeResult ? "成功" : "失败"));
|
||||||
|
result = intakeResult;
|
||||||
}
|
}
|
||||||
if (SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode())
|
if ((SpinnerList.VENTILATION_MODES[0].equals(mode)
|
||||||
|| SpinnerList.VENTILATION_MODES[2].equals(AutoModelSet.getVentilatorMode())) {
|
|| SpinnerList.VENTILATION_MODES[2].equals(mode)) && exhaustAvailable) {
|
||||||
result = MyUtil.autoControlOperateThird(fans[1], true,false) || result;
|
boolean exhaustResult = MyUtil.autoControlOperateThird(fans[1], true,false);
|
||||||
|
MyLog.auto("换气扇分路执行:设备=排气扇,目标=开启,端口=" + fans[1].getPorts()
|
||||||
|
+ ",结果=" + (exhaustResult ? "成功" : "失败"));
|
||||||
|
result = exhaustResult || result;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//防止出现“仅开进气扇-开启-设置为仅开排气扇-关闭”此时进气扇无法关闭的情况 所以每次都得全部关闭(各种换气扇)
|
//防止出现“仅开进气扇-开启-设置为仅开排气扇-关闭”此时进气扇无法关闭的情况 所以每次都得全部关闭(各种换气扇)
|
||||||
result = MyUtil.autoControlOperateThird(fans[0], false,false);
|
if (fans[0].isAvailable()) {
|
||||||
result = MyUtil.autoControlOperateThird(fans[1], false,false) || result;
|
boolean intakeResult = MyUtil.autoControlOperateThird(fans[0], false,false);
|
||||||
|
MyLog.auto("换气扇分路执行:设备=进气扇,目标=关闭,端口=" + fans[0].getPorts()
|
||||||
|
+ ",结果=" + (intakeResult ? "成功" : "失败"));
|
||||||
|
result = intakeResult;
|
||||||
|
}
|
||||||
|
if (fans[1].isAvailable()) {
|
||||||
|
boolean exhaustResult = MyUtil.autoControlOperateThird(fans[1], false,false);
|
||||||
|
MyLog.auto("换气扇分路执行:设备=排气扇,目标=关闭,端口=" + fans[1].getPorts()
|
||||||
|
+ ",结果=" + (exhaustResult ? "成功" : "失败"));
|
||||||
|
result = exhaustResult || result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前换气模式下是否至少存在一个可开启的分路。
|
||||||
|
*/
|
||||||
|
public boolean hasAvailableFanForCurrentMode() {
|
||||||
|
String mode = AutoModelSet.getVentilatorMode();
|
||||||
|
return (SpinnerList.VENTILATION_MODES[0].equals(mode) && isAvailable())
|
||||||
|
|| (SpinnerList.VENTILATION_MODES[1].equals(mode) && fans[0].isAvailable())
|
||||||
|
|| (SpinnerList.VENTILATION_MODES[2].equals(mode) && fans[1].isAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPowerSupply() {
|
public boolean isPowerSupply() {
|
||||||
return fans[0].isPowerSupply() || fans[1].isPowerSupply();
|
return fans[0].isPowerSupply() || fans[1].isPowerSupply();
|
||||||
@@ -56,7 +89,7 @@ public class Fan extends Relay {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getVoltage() {
|
public double getVoltage() {
|
||||||
int total = 0;
|
double total = 0;
|
||||||
int number = 0;
|
int number = 0;
|
||||||
if (fans[0].isAvailable()) {
|
if (fans[0].isAvailable()) {
|
||||||
total += fans[0].getVoltage();
|
total += fans[0].getVoltage();
|
||||||
@@ -66,11 +99,11 @@ public class Fan extends Relay {
|
|||||||
total += fans[1].getVoltage();
|
total += fans[1].getVoltage();
|
||||||
number++;
|
number++;
|
||||||
}
|
}
|
||||||
return total / number;
|
return number == 0 ? 0 : total / number;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getCurrent() {
|
public double getCurrent() {
|
||||||
int total = 0;
|
double total = 0;
|
||||||
int number = 0;
|
int number = 0;
|
||||||
if (fans[0].isAvailable()) {
|
if (fans[0].isAvailable()) {
|
||||||
total += fans[0].getCurrent();
|
total += fans[0].getCurrent();
|
||||||
@@ -80,11 +113,11 @@ public class Fan extends Relay {
|
|||||||
total += fans[1].getCurrent();
|
total += fans[1].getCurrent();
|
||||||
number++;
|
number++;
|
||||||
}
|
}
|
||||||
return total / number;
|
return number == 0 ? 0 : total / number;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getPower() {
|
public double getPower() {
|
||||||
int total = 0;
|
double total = 0;
|
||||||
if (fans[0].isAvailable()) {
|
if (fans[0].isAvailable()) {
|
||||||
total += fans[0].getPower();
|
total += fans[0].getPower();
|
||||||
}
|
}
|
||||||
@@ -95,7 +128,7 @@ public class Fan extends Relay {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getEnergyConsumption() {
|
public double getEnergyConsumption() {
|
||||||
int total = 0;
|
double total = 0;
|
||||||
if (fans[0].isAvailable()) {
|
if (fans[0].isAvailable()) {
|
||||||
total += fans[0].getEnergyConsumption();
|
total += fans[0].getEnergyConsumption();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,7 +321,8 @@ public class Relay extends Controller implements SetAddress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getPowerText() {
|
public String getPowerText() {
|
||||||
return new DecimalFormat("0.##").format(power);
|
// 使用访问器以支持 Fan 等聚合继电器重写功率计算逻辑。
|
||||||
|
return new DecimalFormat("0.##").format(getPower());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPower(double power) {
|
public void setPower(double power) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.iot_controlhost.model.thread;
|
package com.example.iot_controlhost.model.thread;
|
||||||
|
|
||||||
import com.example.iot_controlhost.base.BaseQueue;
|
import com.example.iot_controlhost.base.BaseQueue;
|
||||||
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务队列-IO任务
|
* 任务队列-IO任务
|
||||||
@@ -22,7 +23,12 @@ public class QueueIOTask extends BaseQueue {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
task.run();
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
MyLog.controllerError("控制队列任务异常:异常=" + throwable.getClass().getSimpleName()
|
||||||
|
+ ",原因=" + throwable.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.example.iot_controlhost.model.thread;
|
|||||||
|
|
||||||
import com.example.iot_controlhost.base.BaseQueue;
|
import com.example.iot_controlhost.base.BaseQueue;
|
||||||
import com.example.iot_controlhost.base.IRxIOTask;
|
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.RxJavaUtils;
|
||||||
import com.xuexiang.rxutil2.rxjava.impl.IRxUITask;
|
import com.xuexiang.rxutil2.rxjava.impl.IRxUITask;
|
||||||
import com.xuexiang.rxutil2.rxjava.task.RxUITask;
|
import com.xuexiang.rxutil2.rxjava.task.RxUITask;
|
||||||
@@ -19,12 +20,31 @@ public abstract class QueueIOUITask<T> extends BaseQueue implements IRxIOTask<T>
|
|||||||
RxJavaUtils.doInUIThread(new RxUITask<T>(outData) {
|
RxJavaUtils.doInUIThread(new RxUITask<T>(outData) {
|
||||||
@Override
|
@Override
|
||||||
public void doInUIThread(T o) {
|
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<Object>(null) {
|
||||||
|
@Override
|
||||||
|
public void doInUIThread(Object o) {
|
||||||
|
QueueIOUITask.this.onError(throwable);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IO任务或UI回调失败时执行,默认只记录日志;需要清理界面的任务可重写。
|
||||||
|
*/
|
||||||
|
public void onError(Throwable throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ public class MQTTService extends Service {
|
|||||||
MyLog.network("服务器连接成功");
|
MyLog.network("服务器连接成功");
|
||||||
HardwareSetting.setRegistered();
|
HardwareSetting.setRegistered();
|
||||||
setMQTTConnect(true);
|
setMQTTConnect(true);
|
||||||
|
TopicClass.flushPendingLogs();
|
||||||
try {
|
try {
|
||||||
// 订阅myTopic话题
|
// 订阅myTopic话题
|
||||||
client.subscribe(TopicClass.arealistGet(), 1);
|
client.subscribe(TopicClass.arealistGet(), 1);
|
||||||
@@ -245,4 +246,4 @@ public class MQTTService extends Service {
|
|||||||
public IBinder onBind(Intent intent) {
|
public IBinder onBind(Intent intent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.example.iot_controlhost.ui.activity
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import com.example.iot_controlhost.ui.compose.PIDScreen
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @Author:DuanKaiji
|
|
||||||
* @Date:2023年11月22日 17:19:35
|
|
||||||
* @Declaration:设备调试界面
|
|
||||||
*/
|
|
||||||
class ComposeActivity : ComponentActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContent {
|
|
||||||
PIDScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,8 @@ package com.example.iot_controlhost.ui.activity;
|
|||||||
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import com.blankj.utilcode.util.StringUtils;
|
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.RoomController;
|
||||||
import com.example.iot_controlhost.utils.global.RoomSensor;
|
import com.example.iot_controlhost.utils.global.RoomSensor;
|
||||||
import com.example.iot_controlhost.utils.global.SpinnerList;
|
import com.example.iot_controlhost.utils.global.SpinnerList;
|
||||||
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -32,6 +35,10 @@ import es.dmoral.toasty.Toasty;
|
|||||||
*/
|
*/
|
||||||
public class DebugActivity extends BaseActivity<ActivityDebugBinding, DebugActivityPresenter> {
|
public class DebugActivity extends BaseActivity<ActivityDebugBinding, DebugActivityPresenter> {
|
||||||
|
|
||||||
|
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<ActivityDebugBinding, DebugActiv
|
|||||||
Toasty.error(mContext, "请先选择设备当前地址").show();
|
Toasty.error(mContext, "请先选择设备当前地址").show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final SetAddress testDevice = setAddress;
|
||||||
|
final int requestGeneration = ++testRequestGeneration;
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
|
binding.btnReadAndParse.setEnabled(false);
|
||||||
showLoadingDialog("正在执行测试指令");
|
showLoadingDialog("正在执行测试指令");
|
||||||
|
Runnable timeoutTask = () -> {
|
||||||
|
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<Boolean>() {
|
MyQueue.start(MyQueue.TYPE_SENSOR, new QueueIOUITask<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean doInQueueThread() throws Exception {
|
public Boolean doInQueueThread() throws Exception {
|
||||||
try{
|
try {
|
||||||
return setAddress.test(sensorModel, sensorAddress);
|
return testDevice.test(sensorModel, sensorAddress);
|
||||||
}catch (Exception e){
|
} catch (Throwable throwable) {
|
||||||
|
MyLog.test("设备调试执行异常:设备=" + sensorType + ",型号=" + sensorModel
|
||||||
|
+ ",地址=" + sensorAddress + ",异常="
|
||||||
|
+ throwable.getClass().getSimpleName() + ",原因=" + throwable.getMessage());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doInUIThread(Boolean aBoolean) {
|
public void doInUIThread(Boolean aBoolean) {
|
||||||
binding.tvSend.setText(SerialPortUtil.T);
|
if (requestGeneration != testRequestGeneration) {
|
||||||
binding.tvReceive.setText(SerialPortUtil.R);
|
return;
|
||||||
binding.tvParse.setText(aBoolean ? toSingleLine(setAddress.getTestState()) : "数据采集失败");
|
}
|
||||||
|
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();
|
hideLoadingDialog();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -266,4 +315,11 @@ public class DebugActivity extends BaseActivity<ActivityDebugBinding, DebugActiv
|
|||||||
.replaceAll("\\s+", " ");
|
.replaceAll("\\s+", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
testRequestGeneration++;
|
||||||
|
mainHandler.removeCallbacksAndMessages(null);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.example.iot_controlhost.ui.activity;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -42,9 +44,6 @@ import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import es.dmoral.toasty.Toasty;
|
import es.dmoral.toasty.Toasty;
|
||||||
|
|
||||||
@@ -56,6 +55,10 @@ public class MainActivity extends BaseActivity<ActivityIncubationBinding, MainAc
|
|||||||
AppBarViewModel appBarViewModel;
|
AppBarViewModel appBarViewModel;
|
||||||
WorkDialog workDialog;
|
WorkDialog workDialog;
|
||||||
private PasswordDialog lockPasswordDialog;
|
private PasswordDialog lockPasswordDialog;
|
||||||
|
private final Handler lockHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private final Runnable lockRunnable = this::lock;
|
||||||
|
private int showingDialogCount;
|
||||||
|
private boolean mainPageResumed;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initView() {
|
public void initView() {
|
||||||
@@ -232,9 +235,6 @@ public class MainActivity extends BaseActivity<ActivityIncubationBinding, MainAc
|
|||||||
changeModelView(RoomSetting.getMode());
|
changeModelView(RoomSetting.getMode());
|
||||||
binding.cardKey.setOnClickListener(v -> lock());
|
binding.cardKey.setOnClickListener(v -> lock());
|
||||||
binding.mainEnable.setOnTouchListener((v, event) -> true);
|
binding.mainEnable.setOnTouchListener((v, event) -> true);
|
||||||
binding.textView8.setOnClickListener(v -> {
|
|
||||||
// startActivity(new Intent(this,ComposeActivity.class));
|
|
||||||
});
|
|
||||||
binding.textView0.setOnClickListener(v -> {
|
binding.textView0.setOnClickListener(v -> {
|
||||||
// todo 消息系统开发完成后打开
|
// todo 消息系统开发完成后打开
|
||||||
// new MessageDialog(mContext).show();
|
// new MessageDialog(mContext).show();
|
||||||
@@ -280,21 +280,21 @@ public class MainActivity extends BaseActivity<ActivityIncubationBinding, MainAc
|
|||||||
* 刷新锁屏时间
|
* 刷新锁屏时间
|
||||||
*/
|
*/
|
||||||
private void refreshLock() {
|
private void refreshLock() {
|
||||||
// MyLog.app("刷新主屏幕锁时间" + RoomSetting.getMainActivityOperateLockTimeOut());
|
stopLockTimer();
|
||||||
if (mainLockExecutorService != null) {
|
if (!mainPageResumed || showingDialogCount > 0 || binding.mainLock.getVisibility() == View.VISIBLE) {
|
||||||
mainLockExecutorService.shutdownNow();
|
return;
|
||||||
}
|
}
|
||||||
mainLockExecutorService = Executors.newSingleThreadScheduledExecutor();
|
lockHandler.postDelayed(lockRunnable, RoomSetting.getMainActivityOperateLockTimeOut() * 1000L);
|
||||||
mainLockExecutorService.schedule(() -> lock(), RoomSetting.getMainActivityOperateLockTimeOut(), TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lock() {
|
private void lock() {
|
||||||
|
if (!mainPageResumed || showingDialogCount > 0 || binding.mainLock.getVisibility() == View.VISIBLE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
MyLog.app("屏幕已锁");
|
MyLog.app("屏幕已锁");
|
||||||
mainLockExecutorService.shutdownNow();
|
stopLockTimer();
|
||||||
runOnUiThread(() -> {
|
binding.mainLock.setVisibility(View.VISIBLE);
|
||||||
binding.mainLock.setVisibility(View.VISIBLE);
|
binding.mainKey.setImageDrawable(getDrawable(R.mipmap.lock));
|
||||||
binding.mainKey.setImageDrawable(getDrawable(R.mipmap.lock));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLockPasswordDialog() {
|
private void showLockPasswordDialog() {
|
||||||
@@ -310,15 +310,50 @@ public class MainActivity extends BaseActivity<ActivityIncubationBinding, MainAc
|
|||||||
lockPasswordDialog.show();
|
lockPasswordDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledExecutorService mainLockExecutorService;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||||
int action = event.getAction();
|
if (event.getAction() == MotionEvent.ACTION_DOWN
|
||||||
if (action == MotionEvent.ACTION_DOWN) {
|
&& binding.mainLock.getVisibility() != View.VISIBLE) {
|
||||||
refreshLock();
|
refreshLock();
|
||||||
}
|
}
|
||||||
return super.onTouchEvent(event);
|
return super.dispatchTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDialogShown() {
|
||||||
|
showingDialogCount++;
|
||||||
|
stopLockTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDialogDismissed() {
|
||||||
|
showingDialogCount = Math.max(0, showingDialogCount - 1);
|
||||||
|
if (showingDialogCount == 0) {
|
||||||
|
refreshLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopLockTimer() {
|
||||||
|
lockHandler.removeCallbacks(lockRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
mainPageResumed = true;
|
||||||
|
refreshLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
mainPageResumed = false;
|
||||||
|
stopLockTimer();
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
stopLockTimer();
|
||||||
|
lockHandler.removeCallbacksAndMessages(null);
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+2
-1
@@ -76,7 +76,8 @@ public class MainActivityPresenter extends BasePresenter {
|
|||||||
@Override
|
@Override
|
||||||
public Void doInIOThread(Object x) {
|
public Void doInIOThread(Object x) {
|
||||||
// 刷新共育状态
|
// 刷新共育状态
|
||||||
pollingTask.startPollingTaskOnIOThread(RxTag.REFRESH_CUL_STATE, Variable.REFRESH_CULTURE_STATE_TIME, () -> noticeLiveData.setValue(11));
|
pollingTask.startPollingTaskOnUIThread(RxTag.REFRESH_CUL_STATE,
|
||||||
|
Variable.REFRESH_CULTURE_STATE_TIME, () -> noticeLiveData.setValue(11));
|
||||||
//更新主界面UI
|
//更新主界面UI
|
||||||
pollingTask.startPollingTaskOnIOThread(RxTag.MAIN_UI, 5, () -> {
|
pollingTask.startPollingTaskOnIOThread(RxTag.MAIN_UI, 5, () -> {
|
||||||
temperatureStr.postValue(String.valueOf(RoomSensor.getAverageIndoorTemperature()));
|
temperatureStr.postValue(String.valueOf(RoomSensor.getAverageIndoorTemperature()));
|
||||||
|
|||||||
@@ -236,14 +236,17 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
|
|
||||||
// //非集成控制器则需要设置传感器串口
|
// //非集成控制器则需要设置传感器串口
|
||||||
setList.add(new Set(getString(R.string.serial_port_sensor), HardwareSetting.getSensorSerialCom() + "\t:\t" + HardwareSetting.getSensorSerialComBaud(), view ->
|
setList.add(new Set(getString(R.string.serial_port_sensor), HardwareSetting.getSensorSerialCom() + "\t:\t" + HardwareSetting.getSensorSerialComBaud(), view ->
|
||||||
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.setSensorSerialCom(serial);
|
||||||
HardwareSetting.setSensorSerialComBaud(baud);
|
HardwareSetting.setSensorSerialComBaud(baud);
|
||||||
setController();
|
setController();
|
||||||
}).show()));
|
}).show()));
|
||||||
setList.add(new Set(getString(R.string.serial_port_controller), HardwareSetting.getControllerSerialCom() + "\t:\t" + HardwareSetting.getControllerSerialBaud(), view ->
|
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),
|
new SetDeviceBaseDialog(mContext, getString(R.string.serial_port_controller),
|
||||||
SpinnerList.BAUD, getString(R.string.baud), (serial, baud) -> {
|
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.setControllerSerialCom(serial);
|
||||||
HardwareSetting.setControllerSerialBaud(baud);
|
HardwareSetting.setControllerSerialBaud(baud);
|
||||||
setController();
|
setController();
|
||||||
@@ -268,7 +271,7 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
setRelay();
|
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(),
|
setList.add(new Set(getString(R.string.set_base), RoomController.relay.toString(), view -> 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.setModel(model);
|
||||||
RoomController.relay.setAddress(address);
|
RoomController.relay.setAddress(address);
|
||||||
setRelay();
|
setRelay();
|
||||||
@@ -308,7 +311,8 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
}));
|
}));
|
||||||
setList.add(new Set("基础配置", dehumidifier485.toString(), view ->
|
setList.add(new Set("基础配置", dehumidifier485.toString(), view ->
|
||||||
new SetDeviceBaseDialog(mContext, "485除湿机基础配置", dehumidifier485.getModels(),
|
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.setModel(model);
|
||||||
dehumidifier485.setAddress(address);
|
dehumidifier485.setAddress(address);
|
||||||
setDehumidifier485();
|
setDehumidifier485();
|
||||||
@@ -330,7 +334,8 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
}));
|
}));
|
||||||
setList.add(new Set(getString(R.string.set_base_infrared), deHumidityInfrared.toString(), view ->
|
setList.add(new Set(getString(R.string.set_base_infrared), deHumidityInfrared.toString(), view ->
|
||||||
new SetDeviceBaseDialog(mContext, getString(R.string.set_base_infrared), deHumidityInfrared.getModels(),
|
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.setModel(model);
|
||||||
deHumidityInfrared.setAddress(address);
|
deHumidityInfrared.setAddress(address);
|
||||||
setDeHumidityInfrared();
|
setDeHumidityInfrared();
|
||||||
@@ -394,7 +399,8 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
}));
|
}));
|
||||||
setList.add(new Set(getString(R.string.set_base_infrared), airConditionInfrared.toString(), view ->
|
setList.add(new Set(getString(R.string.set_base_infrared), airConditionInfrared.toString(), view ->
|
||||||
new SetDeviceBaseDialog(mContext, getString(R.string.set_base_infrared), airConditionInfrared.getModels(),
|
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.setModel(model);
|
||||||
airConditionInfrared.setAddress(address);
|
airConditionInfrared.setAddress(address);
|
||||||
setAirConditionDetail(airId, airConditionInfrared);
|
setAirConditionDetail(airId, airConditionInfrared);
|
||||||
@@ -464,8 +470,9 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
&& !RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) {//型号为BBIT-H-v1.0的继电器
|
&& !RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) {//型号为BBIT-H-v1.0的继电器
|
||||||
//需要配置地暖型号与地址
|
//需要配置地暖型号与地址
|
||||||
setList.add(new Set(getString(R.string.set_base), RoomController.floor.toString(), view ->
|
setList.add(new Set(getString(R.string.set_base), RoomController.floor.toString(), view ->
|
||||||
new SetDeviceBaseDialog(mContext, "地暖" + RoomSetting.getFloorName(), RoomController.floor.getModels(),
|
new SetDeviceBaseDialog(mContext, "地暖" + RoomSetting.getFloorName(), RoomController.floor.getModels(),
|
||||||
getString(R.string.mode), SpinnerList.ADDRESS, getString(R.string.address), (model, address) -> {
|
getString(R.string.mode), RoomController.floor.getModel(), SpinnerList.ADDRESS,
|
||||||
|
getString(R.string.address), RoomController.floor.getAddress(), (model, address) -> {
|
||||||
RoomController.floor.setModel(model);
|
RoomController.floor.setModel(model);
|
||||||
RoomController.floor.setAddress(address);
|
RoomController.floor.setAddress(address);
|
||||||
setFloor();
|
setFloor();
|
||||||
@@ -600,15 +607,11 @@ public class SetActivity extends BaseActivity<ActivitySetBinding, SetActivityPre
|
|||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifierInfrared.setPowerSupply(true)));
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifierInfrared.setPowerSupply(true)));
|
||||||
new DehumidityAddDialog(mContext, RoomController.dehumidifierInfrared).show();
|
new DehumidityAddDialog(mContext, RoomController.dehumidifierInfrared).show();
|
||||||
}));
|
}));
|
||||||
setList.add(new Set("PID测试及自整定", null, view -> {
|
|
||||||
startActivity(new Intent(this, ComposeActivity.class));
|
|
||||||
;
|
|
||||||
}));
|
|
||||||
setAdapter.setDataList(setList);
|
setAdapter.setDataList(setList);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logSettingChanged(String name, Object oldValue, Object newValue) {
|
private void logSettingChanged(String name, Object oldValue, Object newValue) {
|
||||||
UserLog.operate("本机屏幕", "修改设备设置:" + name);
|
UserLog.operate("本机屏幕", "设备设置变化:" + name + "=" + oldValue + " → " + newValue);
|
||||||
MyLog.app("设备设置修改:" + name + "=" + oldValue + "→" + newValue);
|
MyLog.app("设备设置修改:" + name + "=" + oldValue + "→" + newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,463 +0,0 @@
|
|||||||
package com.example.iot_controlhost.ui.compose
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.compose.animation.core.EaseInOutCubic
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.Button
|
|
||||||
import androidx.compose.material3.VerticalDivider
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.example.iot_controlhost.utils.MyUtil
|
|
||||||
import com.lt.compose_views.text_field.GoodTextField
|
|
||||||
import ir.ehsannarmani.compose_charts.LineChart
|
|
||||||
import ir.ehsannarmani.compose_charts.models.AnimationMode
|
|
||||||
import ir.ehsannarmani.compose_charts.models.DrawStyle
|
|
||||||
import ir.ehsannarmani.compose_charts.models.Line
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Preview(showBackground = true, widthDp = 1280, heightDp = 800)
|
|
||||||
@Composable
|
|
||||||
fun PIDScreenPV() {
|
|
||||||
PIDScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PIDScreen(
|
|
||||||
pidViewModel: PIDViewModel = viewModel()
|
|
||||||
) {
|
|
||||||
// 获取状态
|
|
||||||
var kp by remember { mutableStateOf(pidViewModel.Kp.toString()) }
|
|
||||||
var ki by remember { mutableStateOf(pidViewModel.Ki.toString()) }
|
|
||||||
var kd by remember { mutableStateOf(pidViewModel.Kd.toString()) }
|
|
||||||
var dtInput by remember { mutableStateOf(pidViewModel.dt.toString()) }
|
|
||||||
var tt by remember { mutableStateOf(pidViewModel.targetTemperature.toString()) }
|
|
||||||
var tp by remember { mutableStateOf(pidViewModel.tp.toString()) }
|
|
||||||
|
|
||||||
val currentTemp = pidViewModel.currentTemperature
|
|
||||||
val output = pidViewModel.output
|
|
||||||
val isTuning = pidViewModel.isTuning
|
|
||||||
val isControl = pidViewModel.isControl
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "BBIT-PID控制测试系统",
|
|
||||||
fontSize = 30.sp,
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
Title("状态图表")
|
|
||||||
LineChart(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(5f)
|
|
||||||
.padding(horizontal = 22.dp),
|
|
||||||
data =
|
|
||||||
listOf(
|
|
||||||
Line(
|
|
||||||
label = "当前温度",
|
|
||||||
values = pidViewModel.tempList,
|
|
||||||
color = SolidColor(Color(0xFF23af92)),
|
|
||||||
firstGradientFillColor = Color(0xFF2BC0A1).copy(alpha = .5f),
|
|
||||||
secondGradientFillColor = Color.Transparent,
|
|
||||||
strokeAnimationSpec = tween(2000, easing = EaseInOutCubic),
|
|
||||||
gradientAnimationDelay = 1000,
|
|
||||||
drawStyle = DrawStyle.Stroke(width = 2.dp),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
animationMode = AnimationMode.Together(delayBuilder = {
|
|
||||||
it * 500L
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(2f)
|
|
||||||
.padding(horizontal = 22.dp)
|
|
||||||
) {
|
|
||||||
LineChart(
|
|
||||||
labelHelperPadding = 0.dp,
|
|
||||||
modifier = Modifier.weight(1f), data = listOf(
|
|
||||||
Line(
|
|
||||||
label = "比例增益 Kp",
|
|
||||||
values = pidViewModel.kpList,
|
|
||||||
color = SolidColor(Color(0xFF23af92)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
LineChart(
|
|
||||||
labelHelperPadding = 0.dp,
|
|
||||||
modifier = Modifier.weight(1f), data = listOf(
|
|
||||||
Line(
|
|
||||||
label = "积分增益 Ki",
|
|
||||||
values = pidViewModel.kiList,
|
|
||||||
color = SolidColor(Color(0xFF23af92)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
LineChart(
|
|
||||||
labelHelperPadding = 0.dp,
|
|
||||||
modifier = Modifier.weight(1f), data = listOf(
|
|
||||||
Line(
|
|
||||||
label = "微分增益 Kd",
|
|
||||||
values = pidViewModel.kdList,
|
|
||||||
color = SolidColor(Color(0xFF23af92)),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Title("实时参数")
|
|
||||||
LazyVerticalGrid(modifier = Modifier, columns = GridCells.Fixed(3)) {
|
|
||||||
item {
|
|
||||||
InfoText("比例增益 Kp", pidViewModel.Kp)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("积分增益 Ki", pidViewModel.Ki)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("微分增益 Kd", pidViewModel.Kd)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("目标温度", pidViewModel.targetTemperature)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("当前温度", currentTemp)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText(
|
|
||||||
"温度类型",
|
|
||||||
if (pidViewModel.isVirtualTemp) "虚拟温度" else "真实温度"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("PID输出", output)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("采样时间间隔dt", pidViewModel.dt)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("积分项累加值", pidViewModel.integral)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("上个误差值", pidViewModel.prevError)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("上次温度变化斜率", pidViewModel.prevSlope)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("上次温度值", pidViewModel.lastTemp)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("临界比例增益ku", pidViewModel.ku)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("临界振荡周期tu", pidViewModel.tu)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("自整定步骤计数", pidViewModel.stepCounter)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("PID控制", if (isControl) "开启中" else "尚未控制")
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("自整定状态", if (isTuning) "正在自整定" else "尚未开始")
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("使用虚拟地暖", if (pidViewModel.isVirtualFloor) "是" else "否")
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
InfoText("温度采集时间间隔(s)", pidViewModel.tp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InfoText(
|
|
||||||
"最后峰值时间",
|
|
||||||
MyUtil.getFormatDateTime(Date(pidViewModel.lastPeakTime))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
VerticalDivider(modifier = Modifier.padding(horizontal = 10.dp))
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(7.dp)
|
|
||||||
) {
|
|
||||||
Row {
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
val logs by pidViewModel.logs.collectAsState()
|
|
||||||
// 显示当前温度和PID输出
|
|
||||||
Title("系统日志")
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(150.dp)
|
|
||||||
.border(1.dp, Color.LightGray)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
logs.forEach { logLine ->
|
|
||||||
Text(logLine, fontSize = 12.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Title("设备状态")
|
|
||||||
InfoText("空调1", pidViewModel.air1State)
|
|
||||||
InfoText("空调2", pidViewModel.air2State)
|
|
||||||
InfoText("地暖", pidViewModel.floorTState)
|
|
||||||
InfoText("虚拟地暖", pidViewModel.vFloorTState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Title("参数设置")
|
|
||||||
// 输入框:Kp, Ki, Kd, 目标温度
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
ParamInputField(
|
|
||||||
isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
"比例增益 Kp",
|
|
||||||
kp
|
|
||||||
) { value ->
|
|
||||||
kp = value
|
|
||||||
pidViewModel.Kp = value.toDoubleOrNull() ?: 0.0
|
|
||||||
}
|
|
||||||
ParamInputField(
|
|
||||||
isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
"积分增益 Ki",
|
|
||||||
ki
|
|
||||||
) { value ->
|
|
||||||
ki = value
|
|
||||||
pidViewModel.Ki = value.toDoubleOrNull() ?: 0.0
|
|
||||||
}
|
|
||||||
ParamInputField(
|
|
||||||
isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
"微分增益 Kd",
|
|
||||||
kd
|
|
||||||
) { value ->
|
|
||||||
kd = value
|
|
||||||
pidViewModel.Kd = value.toDoubleOrNull() ?: 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
ParamInputField(
|
|
||||||
isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
"采样时间间隔 dt 单位s",
|
|
||||||
dtInput
|
|
||||||
) { value ->
|
|
||||||
dtInput = value
|
|
||||||
pidViewModel.dt = value.toDoubleOrNull() ?: 0.0
|
|
||||||
}
|
|
||||||
ParamInputField(
|
|
||||||
false,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
"温度采集间隔 单位s",
|
|
||||||
tp
|
|
||||||
) { value ->
|
|
||||||
tp = value
|
|
||||||
pidViewModel.tp = value.toLongOrNull() ?: 0
|
|
||||||
}
|
|
||||||
ParamInputField(
|
|
||||||
false,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
"目标温度",
|
|
||||||
tt
|
|
||||||
) { value ->
|
|
||||||
tt = value
|
|
||||||
pidViewModel.targetTemperature = value.toDoubleOrNull() ?: 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Title("控制")
|
|
||||||
LazyColumn {
|
|
||||||
item {
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.switchVirtualFloor() }) {
|
|
||||||
Text("${if (pidViewModel.isVirtualFloor) "停用" else "启动"}虚拟地暖")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.targetTemperature += 0.5 }) {
|
|
||||||
Text("增加目标温度0.5℃")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.targetTemperature -= 0.5 }) {
|
|
||||||
Text("降低目标温度0.5℃")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.switchVirtualTemp() }) {
|
|
||||||
Text("${if (pidViewModel.isVirtualTemp) "停用" else "启动"}虚拟温度数据")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
enabled = pidViewModel.isVirtualTemp,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.addVirtualTemp() }) {
|
|
||||||
Text("增加虚拟当前温度0.5℃")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
enabled = pidViewModel.isVirtualTemp,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.reduceVirtualTemp() }) {
|
|
||||||
Text("降低虚拟当前温度0.5℃")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
// 按钮:启动自整定,停止自整定
|
|
||||||
Button(
|
|
||||||
enabled = !isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.startControl() }) {
|
|
||||||
Text("开始控制")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
enabled = isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.stopControl() }) {
|
|
||||||
Text("停止控制")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
// 按钮:启动自整定,停止自整定
|
|
||||||
Button(
|
|
||||||
enabled = !isTuning && isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.startAutoTune() }) {
|
|
||||||
Text("启动自整定")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
enabled = isTuning && isControl,
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.stopAutoTune() }) {
|
|
||||||
Text("停止自整定并重置参数")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
|
||||||
) {
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.saveParams() }) {
|
|
||||||
Text("保存当前PID参数")
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = { pidViewModel.loadParams() }) {
|
|
||||||
Text("读取PID参数")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun InfoText(title: String, content: Any) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.weight(1f),
|
|
||||||
fontSize = 16.sp
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = if (content is Double) {
|
|
||||||
String.format("%.2f", content)
|
|
||||||
} else content.toString(),
|
|
||||||
modifier = Modifier.padding(8.dp),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Title(text: String) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 20.sp,
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ParamInputField(
|
|
||||||
isControl: Boolean = true,
|
|
||||||
modifier: Modifier,
|
|
||||||
label: String,
|
|
||||||
value: String,
|
|
||||||
onValueChange: (String) -> Unit
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier.fillMaxWidth()) {
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
color = if (isControl) Color.Red else Color.Black,
|
|
||||||
modifier = Modifier.padding(start = 8.dp, top = 4.dp)
|
|
||||||
)
|
|
||||||
GoodTextField(
|
|
||||||
enabled = !isControl,
|
|
||||||
value = value,
|
|
||||||
onValueChange = { newText -> onValueChange(newText) },
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(
|
|
||||||
keyboardType = KeyboardType.Number
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(50.dp)
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
package com.example.iot_controlhost.ui.compose
|
|
||||||
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.example.iot_controlhost.base.BaseViewModel
|
|
||||||
import com.example.iot_controlhost.utils.MMKVUtil
|
|
||||||
import com.example.iot_controlhost.utils.MyUtil
|
|
||||||
import com.example.iot_controlhost.utils.global.RoomController
|
|
||||||
import com.example.iot_controlhost.utils.global.RoomSensor
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class PIDViewModel : BaseViewModel() {
|
|
||||||
|
|
||||||
private val _logs = MutableStateFlow<List<String>>(emptyList())
|
|
||||||
val logs = _logs.asStateFlow()
|
|
||||||
|
|
||||||
var Kp by mutableStateOf(5.0)
|
|
||||||
var Ki by mutableStateOf(0.1)
|
|
||||||
var Kd by mutableStateOf(1.0)
|
|
||||||
|
|
||||||
var air1State by mutableStateOf("")
|
|
||||||
var air2State by mutableStateOf("")
|
|
||||||
var floorTState by mutableStateOf("")
|
|
||||||
|
|
||||||
var targetTemperature by mutableStateOf(22.0)
|
|
||||||
|
|
||||||
var isVirtualTemp by mutableStateOf(false)
|
|
||||||
var virtualCurTemp = 16.0
|
|
||||||
|
|
||||||
var isVirtualFloor by mutableStateOf(false)
|
|
||||||
var vFloorTState by mutableStateOf("关闭")
|
|
||||||
|
|
||||||
|
|
||||||
// 采样时间间隔 单位:秒
|
|
||||||
var dt by mutableStateOf(10.0)
|
|
||||||
|
|
||||||
// 温度采集时间间隔
|
|
||||||
var tp by mutableStateOf(10L)
|
|
||||||
|
|
||||||
var currentTemperature by mutableStateOf(-1.0)
|
|
||||||
|
|
||||||
var output by mutableStateOf(0.0)
|
|
||||||
var isTuning by mutableStateOf(false)
|
|
||||||
var isControl by mutableStateOf(false)
|
|
||||||
|
|
||||||
// 积分项累加值
|
|
||||||
var integral by mutableStateOf(0.0)
|
|
||||||
|
|
||||||
// 上一个误差值
|
|
||||||
var prevError by mutableStateOf(0.0)
|
|
||||||
|
|
||||||
// 前一次温度变化斜率
|
|
||||||
var prevSlope by mutableStateOf(0.0)
|
|
||||||
|
|
||||||
// 前一次温度值(用于斜率计算)
|
|
||||||
var lastTemp by mutableStateOf(0.0)
|
|
||||||
|
|
||||||
// 自整定状态 默认空闲状态
|
|
||||||
var tuneState = TuneState.Idle
|
|
||||||
|
|
||||||
// 临界比例增益(Ziegler-Nichols参数)
|
|
||||||
var ku by mutableStateOf(0.0)
|
|
||||||
|
|
||||||
// 临界振荡周期(秒)
|
|
||||||
var tu by mutableStateOf(0.0)
|
|
||||||
|
|
||||||
// 自整定步骤计数器
|
|
||||||
var stepCounter by mutableStateOf(0)
|
|
||||||
|
|
||||||
// 最后峰值时间戳
|
|
||||||
var lastPeakTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
// 温度历史数据(用于振荡检测)
|
|
||||||
val tempHistory: ArrayDeque<Double> = ArrayDeque()
|
|
||||||
val tempList = mutableStateListOf<Double>()
|
|
||||||
|
|
||||||
val kpList = mutableStateListOf<Double>()
|
|
||||||
val kiList = mutableStateListOf<Double>()
|
|
||||||
val kdList = mutableStateListOf<Double>()
|
|
||||||
|
|
||||||
// 振荡周期记录
|
|
||||||
val periods: MutableList<Double> = mutableListOf()
|
|
||||||
|
|
||||||
init {
|
|
||||||
doInIoThreadNoDialog {
|
|
||||||
// 每10秒获取一次当前温度
|
|
||||||
polling(tp) {
|
|
||||||
air1State = RoomController.airConditionInfrared.state
|
|
||||||
air2State = RoomController.airConditionInfrared2.state
|
|
||||||
floorTState = RoomController.floor.state
|
|
||||||
currentTemperature = if (isVirtualTemp) {
|
|
||||||
virtualCurTemp
|
|
||||||
} else {
|
|
||||||
RoomSensor.getAverageIndoorTemperature()
|
|
||||||
}
|
|
||||||
if (isControl || isTuning) {
|
|
||||||
tempList.add(currentTemperature)
|
|
||||||
// tempList.add(Random(System.currentTimeMillis()).nextDouble(17.0,30.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tempLog = ""
|
|
||||||
|
|
||||||
private fun log(msg: String) {
|
|
||||||
if (msg == tempLog) return
|
|
||||||
_logs.value = listOf(MyUtil.getFormatDateTime(Date()) + msg) + _logs.value
|
|
||||||
tempLog = msg
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始使用PID控制
|
|
||||||
*/
|
|
||||||
fun startControl() {
|
|
||||||
doInIoThreadNoDialog {
|
|
||||||
tempList.clear()
|
|
||||||
log("开始控制")
|
|
||||||
isControl = true
|
|
||||||
while (isControl) {
|
|
||||||
delay((dt * 1000).toLong()) // 等待 dt 秒
|
|
||||||
if (isTuning) {
|
|
||||||
autoTuneStep()
|
|
||||||
kpList.add(Kp)
|
|
||||||
kiList.add(Ki)
|
|
||||||
kdList.add(Kd)
|
|
||||||
output = (Kp * (targetTemperature - currentTemperature)).coerceIn(0.0, 100.0)
|
|
||||||
} else {
|
|
||||||
output = computePID()
|
|
||||||
}
|
|
||||||
// 控制逻辑
|
|
||||||
controlLogic(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止PID控制
|
|
||||||
*/
|
|
||||||
fun stopControl() {
|
|
||||||
doInIoThread("正在停止所有设备") {
|
|
||||||
log("停止控制")
|
|
||||||
isControl = false
|
|
||||||
isTuning = false
|
|
||||||
RoomController.closeAllControllers()
|
|
||||||
vFloorTState = "关闭"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun computePID(): Double {
|
|
||||||
val error = targetTemperature - currentTemperature
|
|
||||||
val proportional = Kp * error
|
|
||||||
|
|
||||||
if (Math.abs(error) < 5.0) {
|
|
||||||
integral += error * dt
|
|
||||||
// integral = integral.coerceIn(-100.0, 100.0) // 防止积分风暴
|
|
||||||
integral = integral.coerceIn(0.0, 100.0 / (Ki.takeIf { it != 0.0 } ?: 1.0))
|
|
||||||
}
|
|
||||||
val derivative = (prevError - error) / dt
|
|
||||||
prevError = error
|
|
||||||
|
|
||||||
return (proportional + Ki * integral + Kd * derivative).coerceIn(0.0, 100.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始自整定
|
|
||||||
*/
|
|
||||||
fun startAutoTune() {
|
|
||||||
doInIoThreadNoDialog {
|
|
||||||
tempList.clear()
|
|
||||||
log("开始自整定 PID 控制器")
|
|
||||||
tuneState = TuneState.StepUp
|
|
||||||
Kp = 1.0
|
|
||||||
Ki = 0.0
|
|
||||||
Kd = 0.0
|
|
||||||
integral = 0.0
|
|
||||||
stepCounter = 0
|
|
||||||
periods.clear()
|
|
||||||
tempHistory.clear()
|
|
||||||
kpList.clear()
|
|
||||||
kiList.clear()
|
|
||||||
kdList.clear()
|
|
||||||
log("已重置所有参数")
|
|
||||||
|
|
||||||
isTuning = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止自整定
|
|
||||||
*/
|
|
||||||
fun stopAutoTune() {
|
|
||||||
doInIoThread("正在停止自整定") {
|
|
||||||
log("停止自整定")
|
|
||||||
isTuning = false
|
|
||||||
tuneState = TuneState.Idle
|
|
||||||
stepCounter = 0
|
|
||||||
integral = 0.0
|
|
||||||
prevError = 0.0
|
|
||||||
log("重置部分参数")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun controlLogic(output: Double) {
|
|
||||||
if (output > 50) {
|
|
||||||
log("PID:开启地暖")
|
|
||||||
RoomController.floor.setGear(5)
|
|
||||||
} else {
|
|
||||||
log("PID:关闭地暖")
|
|
||||||
RoomController.floor.setGear(0)
|
|
||||||
}
|
|
||||||
if (isVirtualFloor) {
|
|
||||||
vFloorTState = if (output > 50) "开启" else "关闭"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自整定步骤
|
|
||||||
*/
|
|
||||||
private fun autoTuneStep() {
|
|
||||||
when (tuneState) {
|
|
||||||
// 比例增益提升阶段
|
|
||||||
TuneState.StepUp -> {
|
|
||||||
stepCounter++
|
|
||||||
if (stepCounter % 5 == 0) {
|
|
||||||
Kp *= 1.2
|
|
||||||
log("尝试 Kp: ${Kp}")
|
|
||||||
}
|
|
||||||
tempHistory.addLast(currentTemperature)
|
|
||||||
if (tempHistory.size > 10)
|
|
||||||
tempHistory.removeFirst()
|
|
||||||
if (tempHistory.size == 10 && tempHistory.max() - tempHistory.min() > 0.5) {
|
|
||||||
log("检测到振荡,进入测量阶段")
|
|
||||||
ku = Kp
|
|
||||||
tuneState = TuneState.Measure
|
|
||||||
lastPeakTime = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
if (Kp > 50.0) {
|
|
||||||
log("自整定失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 振荡周期测量阶段
|
|
||||||
TuneState.Measure -> {
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
val temp = currentTemperature
|
|
||||||
val slope = temp - lastTemp
|
|
||||||
|
|
||||||
if ((temp > lastTemp && prevSlope < 0) || (temp < lastTemp && prevSlope > 0)) {
|
|
||||||
val period = (now - lastPeakTime) / 1000.0
|
|
||||||
lastPeakTime = now
|
|
||||||
periods.add(period)
|
|
||||||
log("振荡周期: $period 秒")
|
|
||||||
if (periods.size >= 3) {
|
|
||||||
tu = periods.average()
|
|
||||||
tuneState = TuneState.Complete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevSlope = slope
|
|
||||||
lastTemp = temp
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自整定完成状态
|
|
||||||
TuneState.Complete -> {
|
|
||||||
Kp = 0.6 * ku
|
|
||||||
Ki = 1.2 * ku / tu
|
|
||||||
Kd = 0.075 * ku * tu
|
|
||||||
log("自整定完成!Kp=${Kp}, Ki=${Ki}, Kd=${Kd}")
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveParams() {
|
|
||||||
doInIoThread {
|
|
||||||
// 保存参数到数据库或文件
|
|
||||||
MMKVUtil.put("pid_kp", Kp)
|
|
||||||
MMKVUtil.put("pid_ki", Ki)
|
|
||||||
MMKVUtil.put("pid_kd", Kd)
|
|
||||||
log("保存 PID 参数: Kp=$Kp, Ki=$Ki, Kd=$Kd")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadParams() {
|
|
||||||
doInIoThread {
|
|
||||||
// 从数据库或文件加载参数
|
|
||||||
Kp = MMKVUtil.get("pid_kp", 5.0)
|
|
||||||
Ki = MMKVUtil.get("pid_ki", 0.1)
|
|
||||||
Kd = MMKVUtil.get("pid_kd", 1.0)
|
|
||||||
log("加载 PID 参数: Kp=$Kp, Ki=$Ki, Kd=$Kd")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun switchVirtualTemp() {
|
|
||||||
isVirtualTemp = !isVirtualTemp
|
|
||||||
log("虚拟温度开关:$isVirtualTemp")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun switchVirtualFloor() {
|
|
||||||
isVirtualFloor = !isVirtualFloor
|
|
||||||
log("虚拟地暖开关:$isVirtualFloor")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addVirtualTemp() {
|
|
||||||
doInIoThreadNoDialog {
|
|
||||||
virtualCurTemp += 0.5
|
|
||||||
log("虚拟温度增加0.5°C,当前温度:$virtualCurTemp,目标温度:$targetTemperature")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reduceVirtualTemp() {
|
|
||||||
doInIoThreadNoDialog {
|
|
||||||
virtualCurTemp -= 0.5
|
|
||||||
log("虚拟温度减少0.5°C,当前温度:$virtualCurTemp,目标温度:$targetTemperature")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class TuneState {
|
|
||||||
Idle,// 空闲状态
|
|
||||||
StepUp,// 比例增益提升阶段
|
|
||||||
Measure,// 振荡周期测量阶段
|
|
||||||
Complete// 自整定完成状态
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import com.example.iot_controlhost.model.AITHMode;
|
|||||||
import com.example.iot_controlhost.utils.MMKVUtil;
|
import com.example.iot_controlhost.utils.MMKVUtil;
|
||||||
import com.example.iot_controlhost.utils.MyUtil;
|
import com.example.iot_controlhost.utils.MyUtil;
|
||||||
import com.example.iot_controlhost.utils.database.MyAIModeUtil;
|
import com.example.iot_controlhost.utils.database.MyAIModeUtil;
|
||||||
|
import com.example.iot_controlhost.utils.control.ventilator.VentilatorController;
|
||||||
import com.example.iot_controlhost.utils.global.AIModelSet;
|
import com.example.iot_controlhost.utils.global.AIModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.RxTag;
|
import com.example.iot_controlhost.utils.global.RxTag;
|
||||||
@@ -158,7 +159,9 @@ public class SetAutoParamDialog extends BaseDialog<DialogSetAutoBinding> {
|
|||||||
MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, max);
|
MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, max);
|
||||||
MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, min);
|
MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, min);
|
||||||
MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, enabled);
|
MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, enabled);
|
||||||
UserLog.operate("本机屏幕", "修改自动温度参数");
|
UserLog.operate("本机屏幕", "自动温度参数变化:启用=" + oldEnabled + " → " + enabled
|
||||||
|
+ ",目标=" + oldTarget + " → " + target + "℃,上限=" + oldMax + " → " + max
|
||||||
|
+ "℃,下限=" + oldMin + " → " + min + "℃");
|
||||||
MyLog.app("自动温度参数修改:启用=" + oldEnabled + "→" + enabled
|
MyLog.app("自动温度参数修改:启用=" + oldEnabled + "→" + enabled
|
||||||
+ ",目标=" + oldTarget + "→" + target + "℃,上限=" + oldMax + "→" + max
|
+ ",目标=" + oldTarget + "→" + target + "℃,上限=" + oldMax + "→" + max
|
||||||
+ "℃,下限=" + oldMin + "→" + min + "℃");
|
+ "℃,下限=" + oldMin + "→" + min + "℃");
|
||||||
@@ -185,7 +188,9 @@ public class SetAutoParamDialog extends BaseDialog<DialogSetAutoBinding> {
|
|||||||
MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, min);
|
MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, min);
|
||||||
MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, max);
|
MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, max);
|
||||||
MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, humidityEnabled);
|
MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, humidityEnabled);
|
||||||
UserLog.operate("本机屏幕", "修改自动湿度参数");
|
UserLog.operate("本机屏幕", "自动湿度参数变化:启用=" + humidityOldEnabled + " → " + humidityEnabled
|
||||||
|
+ ",目标=" + humidityOldTarget + " → " + target + "% ,上限=" + humidityOldMax
|
||||||
|
+ " → " + max + "% ,下限=" + humidityOldMin + " → " + min + "%");
|
||||||
MyLog.app("自动湿度参数修改:启用=" + humidityOldEnabled + "→" + humidityEnabled
|
MyLog.app("自动湿度参数修改:启用=" + humidityOldEnabled + "→" + humidityEnabled
|
||||||
+ ",目标=" + humidityOldTarget + "→" + target + "% ,上限=" + humidityOldMax + "→" + max
|
+ ",目标=" + humidityOldTarget + "→" + target + "% ,上限=" + humidityOldMax + "→" + max
|
||||||
+ "% ,下限=" + humidityOldMin + "→" + min + "%");
|
+ "% ,下限=" + humidityOldMin + "→" + min + "%");
|
||||||
@@ -213,10 +218,13 @@ public class SetAutoParamDialog extends BaseDialog<DialogSetAutoBinding> {
|
|||||||
MMKVUtil.put(AutoModelSet.CYCLE_STOP, stop);
|
MMKVUtil.put(AutoModelSet.CYCLE_STOP, stop);
|
||||||
MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, maxCo2);
|
MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, maxCo2);
|
||||||
MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, ventilatorEnabled);
|
MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, ventilatorEnabled);
|
||||||
UserLog.operate("本机屏幕", "修改自动换气参数");
|
UserLog.operate("本机屏幕", "自动换气参数变化:启用=" + ventilatorOldEnabled + " → "
|
||||||
|
+ ventilatorEnabled + ",开启=" + oldStart + " → " + start + "分钟,关闭="
|
||||||
|
+ oldStop + " → " + stop + "分钟,CO₂上限=" + oldMaxCo2 + " → " + maxCo2 + "ppm");
|
||||||
MyLog.app("自动换气参数修改:启用=" + ventilatorOldEnabled + "→" + ventilatorEnabled
|
MyLog.app("自动换气参数修改:启用=" + ventilatorOldEnabled + "→" + ventilatorEnabled
|
||||||
+ ",开启=" + oldStart + "→" + start + "分钟,关闭=" + oldStop + "→"
|
+ ",开启=" + oldStart + "→" + start + "分钟,关闭=" + oldStop + "→"
|
||||||
+ stop + "分钟,CO₂上限=" + oldMaxCo2 + "→" + maxCo2 + "ppm");
|
+ stop + "分钟,CO₂上限=" + oldMaxCo2 + "→" + maxCo2 + "ppm");
|
||||||
|
VentilatorController.getInstance().reloadSchedule("本机屏幕-自动换气参数");
|
||||||
dismiss();
|
dismiss();
|
||||||
needRefresh = true;
|
needRefresh = true;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
@@ -247,7 +255,8 @@ public class SetAutoParamDialog extends BaseDialog<DialogSetAutoBinding> {
|
|||||||
mode.setTargetTemp(temperature);
|
mode.setTargetTemp(temperature);
|
||||||
mode.setTargetHumi(humidity);
|
mode.setTargetHumi(humidity);
|
||||||
MyAIModeUtil.update(mode);
|
MyAIModeUtil.update(mode);
|
||||||
UserLog.operate("本机屏幕", "修改智能模式当前龄期温湿度参数");
|
UserLog.operate("本机屏幕", "智能模式当前龄期参数变化:温度=" + oldTemperature + " → "
|
||||||
|
+ temperature + "℃,湿度=" + oldHumidity + " → " + humidity + "%");
|
||||||
MyLog.app("智能模式当前龄期参数修改:温度=" + oldTemperature + "→" + temperature
|
MyLog.app("智能模式当前龄期参数修改:温度=" + oldTemperature + "→" + temperature
|
||||||
+ "℃,湿度=" + oldHumidity + "→" + humidity + "%");
|
+ "℃,湿度=" + oldHumidity + "→" + humidity + "%");
|
||||||
RxBusUtils.get().post(RxTag.AI_INFO, 0);
|
RxBusUtils.get().post(RxTag.AI_INFO, 0);
|
||||||
@@ -277,9 +286,11 @@ public class SetAutoParamDialog extends BaseDialog<DialogSetAutoBinding> {
|
|||||||
mode.setTargetVentOpen(start);
|
mode.setTargetVentOpen(start);
|
||||||
mode.setTargetVentClose(stop);
|
mode.setTargetVentClose(stop);
|
||||||
MyAIModeUtil.update(mode);
|
MyAIModeUtil.update(mode);
|
||||||
UserLog.operate("本机屏幕", "修改智能模式当前龄期换气参数");
|
UserLog.operate("本机屏幕", "智能模式当前龄期换气参数变化:开启=" + oldStart + " → "
|
||||||
|
+ start + "分钟,关闭=" + oldStop + " → " + stop + "分钟");
|
||||||
MyLog.app("智能模式当前龄期换气参数修改:开启=" + oldStart + "→" + start
|
MyLog.app("智能模式当前龄期换气参数修改:开启=" + oldStart + "→" + start
|
||||||
+ "分钟,关闭=" + oldStop + "→" + stop + "分钟");
|
+ "分钟,关闭=" + oldStop + "→" + stop + "分钟");
|
||||||
|
VentilatorController.getInstance().reloadSchedule("本机屏幕-智能换气参数");
|
||||||
RxBusUtils.get().post(RxTag.AI_INFO, 0);
|
RxBusUtils.get().post(RxTag.AI_INFO, 0);
|
||||||
dismiss();
|
dismiss();
|
||||||
needRefresh = true;
|
needRefresh = true;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.example.iot_controlhost.utils.MMKVUtil;
|
|||||||
import com.example.iot_controlhost.utils.MyQueue;
|
import com.example.iot_controlhost.utils.MyQueue;
|
||||||
import com.example.iot_controlhost.utils.MyUtil;
|
import com.example.iot_controlhost.utils.MyUtil;
|
||||||
import com.example.iot_controlhost.utils.control.temperature.TemperatureController;
|
import com.example.iot_controlhost.utils.control.temperature.TemperatureController;
|
||||||
|
import com.example.iot_controlhost.utils.control.ventilator.VentilatorController;
|
||||||
import com.example.iot_controlhost.utils.database.LogDBManager;
|
import com.example.iot_controlhost.utils.database.LogDBManager;
|
||||||
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.RoomSetting;
|
import com.example.iot_controlhost.utils.global.RoomSetting;
|
||||||
@@ -186,7 +187,9 @@ public class SetBaseDialog extends BaseDialog<DialogSetBinding> {
|
|||||||
MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, heatMode);
|
MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, heatMode);
|
||||||
MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, target - bias);
|
MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, target - bias);
|
||||||
MMKVUtil.put(AutoModelSet.MAX_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
|
MyLog.app("温度控制模式修改:" + oldMode + "→" + selection
|
||||||
+ ",自动调整目标上下限为" + (target - bias) + "~" + (target + bias) + "℃");
|
+ ",自动调整目标上下限为" + (target - bias) + "~" + (target + bias) + "℃");
|
||||||
updateTemperatureControl();
|
updateTemperatureControl();
|
||||||
@@ -226,6 +229,9 @@ public class SetBaseDialog extends BaseDialog<DialogSetBinding> {
|
|||||||
String oldValue = AutoModelSet.getVentilatorMode();
|
String oldValue = AutoModelSet.getVentilatorMode();
|
||||||
MMKVUtil.put(AutoModelSet.VENTILATOR_MODE, selection);
|
MMKVUtil.put(AutoModelSet.VENTILATOR_MODE, selection);
|
||||||
logSettingChanged("换气扇控制方式", oldValue, selection);
|
logSettingChanged("换气扇控制方式", oldValue, selection);
|
||||||
|
if (!oldValue.equals(selection)) {
|
||||||
|
VentilatorController.getInstance().reloadSchedule("本机屏幕-换气扇控制方式");
|
||||||
|
}
|
||||||
setAutoParam();
|
setAutoParam();
|
||||||
}).show()));
|
}).show()));
|
||||||
setList.add(new Set("新风控制", AutoModelSet.getAirExchangeMode(), view ->
|
setList.add(new Set("新风控制", AutoModelSet.getAirExchangeMode(), view ->
|
||||||
@@ -294,7 +300,7 @@ public class SetBaseDialog extends BaseDialog<DialogSetBinding> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void logSettingChanged(String name, Object oldValue, Object newValue) {
|
private void logSettingChanged(String name, Object oldValue, Object newValue) {
|
||||||
UserLog.operate("本机屏幕", "修改设置:" + name);
|
UserLog.operate("本机屏幕", "设置变化:" + name + "=" + oldValue + " → " + newValue);
|
||||||
MyLog.app("设置修改:" + name + "=" + oldValue + "→" + newValue);
|
MyLog.app("设置修改:" + name + "=" + oldValue + "→" + newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ public class SetDeviceBaseDialog extends BaseDialog<DialogDeviceCustomSetBinding
|
|||||||
* @param ac2Hint 下拉框2的提示
|
* @param ac2Hint 下拉框2的提示
|
||||||
* @param listener 点击确定事件
|
* @param listener 点击确定事件
|
||||||
*/
|
*/
|
||||||
public SetDeviceBaseDialog(@NonNull Context context, String title, String[] ac1List, String ac1Hint, String[] ac2List, String ac2Hint, OnCommonSetListener listener) {
|
public SetDeviceBaseDialog(@NonNull Context context, String title,
|
||||||
|
String[] ac1List, String ac1Hint, String oldValue1,
|
||||||
|
String[] ac2List, String ac2Hint, String oldValue2,
|
||||||
|
OnCommonSetListener listener) {
|
||||||
super(context,true);
|
super(context,true);
|
||||||
binding.tilSpinner1.setVisibility(View.VISIBLE);
|
binding.tilSpinner1.setVisibility(View.VISIBLE);
|
||||||
binding.tilSpinner2.setVisibility(View.VISIBLE);
|
binding.tilSpinner2.setVisibility(View.VISIBLE);
|
||||||
@@ -51,10 +54,13 @@ public class SetDeviceBaseDialog extends BaseDialog<DialogDeviceCustomSetBinding
|
|||||||
Toasty.info(context, ac2Hint).show();
|
Toasty.info(context, ac2Hint).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
listener.btnSet(binding.acInput1.getText().toString(), binding.acInput2.getText().toString());
|
String newValue1 = binding.acInput1.getText().toString();
|
||||||
UserLog.operate("本机屏幕", "修改设备配置:" + title);
|
String newValue2 = binding.acInput2.getText().toString();
|
||||||
MyLog.app(title + "修改:" + ac1Hint + "=" + binding.acInput1.getText()
|
listener.btnSet(newValue1, newValue2);
|
||||||
+ "," + ac2Hint + "=" + binding.acInput2.getText());
|
String change = title + "配置变化:" + ac1Hint + "=" + oldValue1 + " → " + newValue1
|
||||||
|
+ "," + ac2Hint + "=" + oldValue2 + " → " + newValue2;
|
||||||
|
UserLog.operate("本机屏幕", change);
|
||||||
|
MyLog.app(change);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class SetPWMDialog extends BaseDialog<DialogDeviceCustomSetBinding> {
|
|||||||
}
|
}
|
||||||
int oldPwm = RoomController.floor.getPwm(gear).getPwm();
|
int oldPwm = RoomController.floor.getPwm(gear).getPwm();
|
||||||
RoomController.floor.getPwm(gear).setPwm(pwm);
|
RoomController.floor.getPwm(gear).setPwm(pwm);
|
||||||
UserLog.operate("本机屏幕", "修改地暖PWM:" + gear + "档");
|
UserLog.operate("本机屏幕", "地暖PWM变化:" + gear + "档=" + oldPwm + " → " + pwm);
|
||||||
MyLog.app("地暖PWM修改:档位=" + gear + ",PWM=" + oldPwm + "→" + pwm);
|
MyLog.app("地暖PWM修改:档位=" + gear + ",PWM=" + oldPwm + "→" + pwm);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Toasty.error(mContext, "输入格式有误,请检查").show();
|
Toasty.error(mContext, "输入格式有误,请检查").show();
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ public class SetPortDialog extends BaseDialog<DialogDeviceCustomSetBinding> {
|
|||||||
}
|
}
|
||||||
//确定后关闭弹窗
|
//确定后关闭弹窗
|
||||||
RoomController.relay.setPowerSupply(false, port);
|
RoomController.relay.setPowerSupply(false, port);
|
||||||
UserLog.operate("本机屏幕", "修改继电器端口配置:端口" + port);
|
UserLog.operate("本机屏幕", "继电器端口配置变化:端口" + port + "=" + oldDeviceName
|
||||||
|
+ " → " + (deviceName.isEmpty() ? "未配置" : deviceName));
|
||||||
MyLog.app("继电器端口配置修改:端口=" + port + ",设备=" + oldDeviceName
|
MyLog.app("继电器端口配置修改:端口=" + port + ",设备=" + oldDeviceName
|
||||||
+ "→" + (deviceName.isEmpty() ? "未配置" : deviceName));
|
+ "→" + (deviceName.isEmpty() ? "未配置" : deviceName));
|
||||||
dismiss();
|
dismiss();
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ public class SetSensorDialog extends BaseDialog<DialogSetSensorBinding> {
|
|||||||
});
|
});
|
||||||
binding.btnYes.setOnClickListener(button -> {
|
binding.btnYes.setOnClickListener(button -> {
|
||||||
//设置事件
|
//设置事件
|
||||||
|
boolean oldEnable = sensor.isEnable();
|
||||||
|
String oldModel = sensor.getModel();
|
||||||
|
String oldAddress = sensor.getAddress();
|
||||||
boolean enable = binding.switcher.isChecked();
|
boolean enable = binding.switcher.isChecked();
|
||||||
if (enable) {
|
if (enable) {
|
||||||
String address = binding.acAddress.getText().toString();
|
String address = binding.acAddress.getText().toString();
|
||||||
@@ -89,9 +92,11 @@ public class SetSensorDialog extends BaseDialog<DialogSetSensorBinding> {
|
|||||||
sensor.setEnable(enable);
|
sensor.setEnable(enable);
|
||||||
//刷新页面
|
//刷新页面
|
||||||
listener.run(sensor);
|
listener.run(sensor);
|
||||||
UserLog.operate("本机屏幕", "修改传感器配置:" + sensor.getName());
|
String change = "传感器配置变化:名称=" + sensor.getName() + ",启用=" + oldEnable + " → "
|
||||||
MyLog.app("传感器配置修改:名称=" + sensor.getName() + ",启用=" + sensor.isEnable()
|
+ sensor.isEnable() + ",型号=" + oldModel + " → " + sensor.getModel()
|
||||||
+ ",型号=" + sensor.getModel() + ",地址=" + sensor.getAddress());
|
+ ",地址=" + oldAddress + " → " + sensor.getAddress();
|
||||||
|
UserLog.operate("本机屏幕", change);
|
||||||
|
MyLog.app(change);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class THBiasDialog extends BaseDialog<DialogBiasThBinding> {
|
|||||||
double newValue = Double.parseDouble(binding.tiSet1.getText().toString());
|
double newValue = Double.parseDouble(binding.tiSet1.getText().toString());
|
||||||
MMKVUtil.put(isTemp ? RoomSetting.BIAS_TEMP : RoomSetting.BIAS_HUMIDITY, newValue);
|
MMKVUtil.put(isTemp ? RoomSetting.BIAS_TEMP : RoomSetting.BIAS_HUMIDITY, newValue);
|
||||||
String typeName = isTemp ? "温度" : "湿度";
|
String typeName = isTemp ? "温度" : "湿度";
|
||||||
UserLog.operate("本机屏幕", "修改" + typeName + "校准偏差");
|
UserLog.operate("本机屏幕", typeName + "校准偏差变化:" + oldValue + " → " + newValue);
|
||||||
MyLog.app(typeName + "校准偏差修改:" + oldValue + "→" + newValue);
|
MyLog.app(typeName + "校准偏差修改:" + oldValue + "→" + newValue);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.WindowManager;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.example.iot_controlhost.R;
|
import com.example.iot_controlhost.R;
|
||||||
|
import com.example.iot_controlhost.ui.activity.MainActivity;
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||||
import com.xuexiang.rxutil2.rxjava.RxJavaUtils;
|
import com.xuexiang.rxutil2.rxjava.RxJavaUtils;
|
||||||
import com.xuexiang.rxutil2.rxjava.task.RxUITask;
|
import com.xuexiang.rxutil2.rxjava.task.RxUITask;
|
||||||
@@ -17,6 +18,7 @@ public class DialogUtil {
|
|||||||
private static Dialog progressDialog;
|
private static Dialog progressDialog;
|
||||||
private static TextView textViewInfo;
|
private static TextView textViewInfo;
|
||||||
private static LinearProgressIndicator progressIndicator;
|
private static LinearProgressIndicator progressIndicator;
|
||||||
|
private static Context progressContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示加载中弹窗,支持中途改变提示文字
|
* 显示加载中弹窗,支持中途改变提示文字
|
||||||
@@ -26,10 +28,10 @@ public class DialogUtil {
|
|||||||
*/
|
*/
|
||||||
public static void showLoadingDialog(Context context, String info) {
|
public static void showLoadingDialog(Context context, String info) {
|
||||||
if (progressDialog != null) {
|
if (progressDialog != null) {
|
||||||
progressDialog.dismiss(); // 如果之前有Dialog显示,则先关闭
|
hideLoadingDialog(); // 如果之前有Dialog显示,则先关闭并同步锁屏计数
|
||||||
progressDialog = null; // 释放旧Dialog
|
|
||||||
}
|
}
|
||||||
progressDialog = new Dialog(context, R.style.MyDialogTheme);
|
progressDialog = new Dialog(context, R.style.MyDialogTheme);
|
||||||
|
progressContext = context;
|
||||||
progressDialog.setContentView(R.layout.dialog_loading);
|
progressDialog.setContentView(R.layout.dialog_loading);
|
||||||
progressDialog.setCancelable(false);
|
progressDialog.setCancelable(false);
|
||||||
textViewInfo = progressDialog.findViewById(R.id.tv_loading);
|
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;
|
layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
progressDialog.getWindow().setAttributes(layoutParams);
|
progressDialog.getWindow().setAttributes(layoutParams);
|
||||||
progressDialog.show();
|
progressDialog.show();
|
||||||
|
notifyDialogShown(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,6 +72,9 @@ public class DialogUtil {
|
|||||||
public static void hideLoadingDialog() {
|
public static void hideLoadingDialog() {
|
||||||
if (progressDialog != null) {
|
if (progressDialog != null) {
|
||||||
progressDialog.dismiss();
|
progressDialog.dismiss();
|
||||||
|
notifyDialogDismissed(progressContext);
|
||||||
|
progressDialog = null;
|
||||||
|
progressContext = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +85,11 @@ public class DialogUtil {
|
|||||||
* @param info 提示信息
|
* @param info 提示信息
|
||||||
*/
|
*/
|
||||||
public static void showProgressIndicatorDialog(Context context, String info) {
|
public static void showProgressIndicatorDialog(Context context, String info) {
|
||||||
|
if (progressDialog != null) {
|
||||||
|
hideLoadingDialog();
|
||||||
|
}
|
||||||
progressDialog = new Dialog(context, R.style.MyDialogTheme);
|
progressDialog = new Dialog(context, R.style.MyDialogTheme);
|
||||||
|
progressContext = context;
|
||||||
progressDialog.setContentView(R.layout.dialog_process_indicator);
|
progressDialog.setContentView(R.layout.dialog_process_indicator);
|
||||||
progressDialog.setCancelable(false);
|
progressDialog.setCancelable(false);
|
||||||
textViewInfo = progressDialog.findViewById(R.id.tv_loading);
|
textViewInfo = progressDialog.findViewById(R.id.tv_loading);
|
||||||
@@ -97,6 +107,7 @@ public class DialogUtil {
|
|||||||
}
|
}
|
||||||
textViewInfo.setText(info);
|
textViewInfo.setText(info);
|
||||||
progressDialog.show();
|
progressDialog.show();
|
||||||
|
notifyDialogShown(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,7 +129,22 @@ public class DialogUtil {
|
|||||||
public static void hideProgressIndicatorDialog() {
|
public static void hideProgressIndicatorDialog() {
|
||||||
if (progressDialog != null) {
|
if (progressDialog != null) {
|
||||||
progressDialog.dismiss();
|
progressDialog.dismiss();
|
||||||
|
notifyDialogDismissed(progressContext);
|
||||||
|
progressDialog = null;
|
||||||
|
progressContext = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -276,6 +276,7 @@ public class MyUtil {
|
|||||||
*/
|
*/
|
||||||
public static void exitApp() {
|
public static void exitApp() {
|
||||||
UserLog.system("本机屏幕", "关闭软件");
|
UserLog.system("本机屏幕", "关闭软件");
|
||||||
|
MyLog.app("软件生命周期:收到本机关闭软件指令,开始停止自动控制和设备输出");
|
||||||
MyUtil.stopAppControl("正在关闭软件中", () -> AppUtils.exitApp());
|
MyUtil.stopAppControl("正在关闭软件中", () -> AppUtils.exitApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,6 +289,7 @@ public class MyUtil {
|
|||||||
|
|
||||||
public static void relaunchApp(String source) {
|
public static void relaunchApp(String source) {
|
||||||
UserLog.system(source, "重启软件");
|
UserLog.system(source, "重启软件");
|
||||||
|
MyLog.app("软件生命周期:收到重启软件指令,来源=" + source + ",开始停止自动控制和设备输出");
|
||||||
MyUtil.stopAppControl("正在重启软件中", () -> {
|
MyUtil.stopAppControl("正在重启软件中", () -> {
|
||||||
// AppUtils.relaunchApp(); // 这个方法重启不干净
|
// AppUtils.relaunchApp(); // 这个方法重启不干净
|
||||||
ProcessPhoenix.triggerRebirth(MyApp.getAppContext());
|
ProcessPhoenix.triggerRebirth(MyApp.getAppContext());
|
||||||
@@ -303,6 +305,7 @@ public class MyUtil {
|
|||||||
|
|
||||||
public static void shutDown(String source) {
|
public static void shutDown(String source) {
|
||||||
UserLog.system(source, "关闭设备");
|
UserLog.system(source, "关闭设备");
|
||||||
|
MyLog.app("主机生命周期:收到关闭设备指令,来源=" + source + ",开始停止自动控制和设备输出");
|
||||||
//先关闭所有设备
|
//先关闭所有设备
|
||||||
MyUtil.stopAppControl("正在关闭设备中", () -> MyAPIContext.getInstance().shutDown());
|
MyUtil.stopAppControl("正在关闭设备中", () -> MyAPIContext.getInstance().shutDown());
|
||||||
}
|
}
|
||||||
@@ -316,6 +319,7 @@ public class MyUtil {
|
|||||||
|
|
||||||
public static void reboot(String source) {
|
public static void reboot(String source) {
|
||||||
UserLog.system(source, "重启设备");
|
UserLog.system(source, "重启设备");
|
||||||
|
MyLog.app("主机生命周期:收到重启设备指令,来源=" + source + ",开始停止自动控制和设备输出");
|
||||||
MyUtil.stopAppControl("正在重启设备中", () -> MyAPIContext.getInstance().reBoot());
|
MyUtil.stopAppControl("正在重启设备中", () -> MyAPIContext.getInstance().reBoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,8 +374,14 @@ public class MyUtil {
|
|||||||
*/
|
*/
|
||||||
public static boolean autoControlOperateThird(Controller controller, boolean targetState, boolean needAutoLog) {
|
public static boolean autoControlOperateThird(Controller controller, boolean targetState, boolean needAutoLog) {
|
||||||
if (!controller.isAvailable()) {
|
if (!controller.isAvailable()) {
|
||||||
|
// 聚合设备(例如换气扇总控)会自行跳过未安装的子设备,避免每轮自动控制重复报错。
|
||||||
|
if (needAutoLog) {
|
||||||
|
MyLog.autoError("自动控制跳过:设备=" + controller.getName() + ",目标="
|
||||||
|
+ (targetState ? "开启" : "关闭") + ",原因=设备不可用");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
boolean previousState = controller.isPowerSupply();
|
||||||
int maxCount = 3;
|
int maxCount = 3;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
@@ -390,6 +400,16 @@ public class MyUtil {
|
|||||||
if (!success) {
|
if (!success) {
|
||||||
MyLog.autoError((needAutoLog ? "自动" : "系统联动") + "操作(连续" + maxCount + "次)失败:"
|
MyLog.autoError((needAutoLog ? "自动" : "系统联动") + "操作(连续" + maxCount + "次)失败:"
|
||||||
+ (targetState ? "开启" : "关闭") + controller.getName());
|
+ (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
|
//自动操作完设备需要更新UI
|
||||||
RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2);
|
RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2);
|
||||||
|
|||||||
@@ -5,36 +5,39 @@ import android.os.Looper;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.example.iot_controlhost.utils.log.MyLog;
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
|
import com.example.iot_controlhost.utils.log.UserLog;
|
||||||
import com.xuexiang.rxutil2.rxjava.impl.IRxUITask;
|
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.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class PollingTask {
|
public class PollingTask {
|
||||||
|
|
||||||
private static Map<String, PollingTask> instances = new HashMap<>();
|
private static final Map<String, PollingTask> instances = new ConcurrentHashMap<>();
|
||||||
|
private final String instanceId;
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
private Map<String, ScheduledFuture<?>> taskMap;
|
private final Map<String, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Long> lastUserErrorTime = new ConcurrentHashMap<>();
|
||||||
|
private volatile boolean stopped;
|
||||||
|
|
||||||
// 私有化构造函数,确保外部不能直接实例化
|
// 私有化构造函数,确保外部不能直接实例化
|
||||||
private PollingTask() {
|
private PollingTask(String instanceId) {
|
||||||
|
this.instanceId = instanceId;
|
||||||
createNewScheduler();
|
createNewScheduler();
|
||||||
}
|
}
|
||||||
// 创建新的 ScheduledExecutorService 实例
|
// 创建新的 ScheduledExecutorService 实例
|
||||||
private void createNewScheduler() {
|
private synchronized void createNewScheduler() {
|
||||||
if (scheduler != null && !scheduler.isShutdown()) {
|
if (scheduler != null && !scheduler.isShutdown()) {
|
||||||
scheduler.shutdown();
|
scheduler.shutdownNow();
|
||||||
}
|
}
|
||||||
scheduler = Executors.newScheduledThreadPool(3);
|
scheduler = Executors.newScheduledThreadPool(3);
|
||||||
taskMap = new HashMap<>();
|
taskMap.clear();
|
||||||
|
stopped = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +51,7 @@ public class PollingTask {
|
|||||||
// 获取基于ID的单例实例
|
// 获取基于ID的单例实例
|
||||||
public static synchronized PollingTask getInstance(String id) {
|
public static synchronized PollingTask getInstance(String id) {
|
||||||
if (!instances.containsKey(id)) {
|
if (!instances.containsKey(id)) {
|
||||||
instances.put(id, new PollingTask());
|
instances.put(id, new PollingTask(id));
|
||||||
}
|
}
|
||||||
return instances.get(id);
|
return instances.get(id);
|
||||||
}
|
}
|
||||||
@@ -59,13 +62,22 @@ public class PollingTask {
|
|||||||
* @param delaySeconds 延迟时间(以秒为单位)
|
* @param delaySeconds 延迟时间(以秒为单位)
|
||||||
* @param task 需要执行的任务
|
* @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的任务,先停止
|
stopPollingTask(taskId); // 如果已有相同ID的任务,先停止
|
||||||
|
if (stopped) {
|
||||||
ScheduledFuture<?> future = scheduler.schedule(() -> {
|
MyLog.auto("调度器已停止,忽略延迟任务:控制器=" + instanceId + ",任务=" + taskId);
|
||||||
task.run(); // 执行任务
|
return;
|
||||||
}, delaySeconds, TimeUnit.SECONDS);
|
}
|
||||||
taskMap.put(taskId, future); // 保存任务的ScheduledFuture
|
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 intervalSeconds 轮询间隔时间(以秒为单位)
|
||||||
* @param task 需要执行的任务
|
* @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的任务,先停止
|
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);
|
private Runnable safeRunnable(String taskId, Runnable task) {
|
||||||
taskMap.put(taskId, future); // 保存任务的ScheduledFuture
|
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()) {
|
for (String taskId : taskMap.keySet()) {
|
||||||
MyLog.test("关闭轮询任务" + taskId);
|
MyLog.auto("停止调度任务:控制器=" + instanceId + ",任务=" + taskId);
|
||||||
}
|
}
|
||||||
if (scheduler != null && !scheduler.isShutdown()) {
|
if (scheduler != null && !scheduler.isShutdown()) {
|
||||||
// scheduler.shutdown(); // 停止所有任务
|
// scheduler.shutdown(); // 停止所有任务
|
||||||
@@ -144,14 +211,14 @@ public class PollingTask {
|
|||||||
taskMap.clear(); // 清空任务映射
|
taskMap.clear(); // 清空任务映射
|
||||||
}
|
}
|
||||||
// 从实例映射中移除当前实例
|
// 从实例映射中移除当前实例
|
||||||
instances.values().remove(this);
|
instances.remove(instanceId, this);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 停止轮询任务
|
* 停止轮询任务
|
||||||
*
|
*
|
||||||
* @param taskId 任务ID
|
* @param taskId 任务ID
|
||||||
*/
|
*/
|
||||||
public void stopPollingTask(String taskId) {
|
public synchronized void stopPollingTask(String taskId) {
|
||||||
ScheduledFuture<?> future = taskMap.get(taskId);
|
ScheduledFuture<?> future = taskMap.get(taskId);
|
||||||
if (future != null) {
|
if (future != null) {
|
||||||
future.cancel(true); // 取消任务,中断正在执行的任务
|
future.cancel(true); // 取消任务,中断正在执行的任务
|
||||||
|
|||||||
@@ -14,19 +14,33 @@ public abstract class Control {
|
|||||||
/**
|
/**
|
||||||
* 是否在开始中
|
* 是否在开始中
|
||||||
*/
|
*/
|
||||||
protected boolean start = false;
|
protected volatile boolean start = false;
|
||||||
|
|
||||||
protected PollingTask pollingTask ;
|
protected PollingTask pollingTask ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始控制
|
* 开始控制
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public synchronized void start() {
|
||||||
if (start) {
|
if (start && pollingTask != null && pollingTask.hasActiveTasks()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (start) {
|
||||||
|
MyLog.autoError("检测到自动控制任务已无有效调度,尝试自恢复:" + getClass().getSimpleName());
|
||||||
|
}
|
||||||
pollingTask = PollingTask.getInstance(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){
|
if(pollingTask != null){
|
||||||
pollingTask.stopAllPollingTasks();
|
pollingTask.stopAllPollingTasks();
|
||||||
}
|
}
|
||||||
if (!start) {
|
if (!start) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopMethod();
|
try {
|
||||||
RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2);
|
stopMethod();
|
||||||
start = false;
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+57
-7
@@ -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.SpinnerList;
|
||||||
import com.example.iot_controlhost.utils.global.Variable;
|
import com.example.iot_controlhost.utils.global.Variable;
|
||||||
import com.example.iot_controlhost.utils.log.MyLog;
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
import com.xuexiang.rxutil2.rxjava.DisposablePool;
|
import com.example.iot_controlhost.utils.log.UserLog;
|
||||||
import com.xuexiang.rxutil2.rxjava.RxJavaUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Description 自动模式-控制逻辑-湿度
|
* @Description 自动模式-控制逻辑-湿度
|
||||||
@@ -29,6 +28,9 @@ public class HumidityController extends Control {
|
|||||||
*/
|
*/
|
||||||
private static boolean needOpenToTarget = false;
|
private static boolean needOpenToTarget = false;
|
||||||
private static boolean needDehumidifyToTarget = 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;
|
return false;
|
||||||
}
|
}
|
||||||
pollingTask.startPollingTaskOnIOThread(RxTag.AUTO_HUMIDITY, Variable.AUTO_CHECK_TIME, () -> {
|
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 min = 0;
|
||||||
double max = 0;
|
double max = 0;
|
||||||
if (RoomSetting.getMode() == 1) {
|
if (RoomSetting.getMode() == 1) {
|
||||||
@@ -85,6 +103,12 @@ public class HumidityController extends Control {
|
|||||||
needDehumidifyToTarget = false;
|
needDehumidifyToTarget = false;
|
||||||
needDehumidify = false;
|
needDehumidify = false;
|
||||||
}
|
}
|
||||||
|
// 真实湿度已接近100%时,即使负校准偏差导致显示值较低,也绝不继续加湿。
|
||||||
|
if (cur >= 99) {
|
||||||
|
needHumidify = false;
|
||||||
|
needOpenToTarget = false;
|
||||||
|
}
|
||||||
|
|
||||||
// === 互斥处理 ===
|
// === 互斥处理 ===
|
||||||
if (needHumidify && needDehumidify) {
|
if (needHumidify && needDehumidify) {
|
||||||
// 理论不会发生,保险处理
|
// 理论不会发生,保险处理
|
||||||
@@ -93,6 +117,18 @@ public class HumidityController extends Control {
|
|||||||
}
|
}
|
||||||
boolean humidifierState = needHumidify;
|
boolean humidifierState = needHumidify;
|
||||||
boolean dehumidifierState = needDehumidify;
|
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();
|
Controller autoDehumidifier = RoomController.getAutoDehumidifier();
|
||||||
int dehumidifierTargetHumidity = (int) Math.round(target);
|
int dehumidifierTargetHumidity = (int) Math.round(target);
|
||||||
boolean needRefreshDehumidifierSetting = autoDehumidifier instanceof Dehumidifier485
|
boolean needRefreshDehumidifierSetting = autoDehumidifier instanceof Dehumidifier485
|
||||||
@@ -103,16 +139,20 @@ public class HumidityController extends Control {
|
|||||||
|| autoDehumidifier.isPowerSupply() == dehumidifierState && !needRefreshDehumidifierSetting;
|
|| autoDehumidifier.isPowerSupply() == dehumidifierState && !needRefreshDehumidifierSetting;
|
||||||
|
|
||||||
if (humidifierKeep && dehumidifierKeep) {
|
if (humidifierKeep && dehumidifierKeep) {
|
||||||
MyLog.auto("湿度-控制维持当前状态");
|
// 状态维持时不重复输出,避免淹没真正的状态变化和异常日志。
|
||||||
} else {
|
} else {
|
||||||
// 加湿器控制
|
// 加湿器控制
|
||||||
if (RoomController.humidifier.isAvailable()) {
|
if (RoomController.humidifier.isAvailable()) {
|
||||||
MyLog.auto("自动:" + (humidifierState ? "开启" : "关闭") + "加湿器--");
|
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
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();
|
String airExchangeMode = AutoModelSet.getAirExchangeMode();
|
||||||
if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[2])) {
|
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 ? "成功" : "失败"));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -137,6 +177,9 @@ public class HumidityController extends Control {
|
|||||||
protected void stopMethod() {
|
protected void stopMethod() {
|
||||||
needOpenToTarget = false;
|
needOpenToTarget = false;
|
||||||
needDehumidifyToTarget = false;
|
needDehumidifyToTarget = false;
|
||||||
|
lastHumidifierTarget = null;
|
||||||
|
lastDehumidifierTarget = null;
|
||||||
|
invalidHumidityLogged = false;
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
||||||
MyUtil.autoControlOperateThird(RoomController.humidifier);
|
MyUtil.autoControlOperateThird(RoomController.humidifier);
|
||||||
Controller autoDehumidifier = RoomController.getAutoDehumidifier();
|
Controller autoDehumidifier = RoomController.getAutoDehumidifier();
|
||||||
@@ -151,4 +194,11 @@ public class HumidityController extends Control {
|
|||||||
MyLog.auto("停止自动控制湿度————————————————————————————————————————————————————");
|
MyLog.auto("停止自动控制湿度————————————————————————————————————————————————————");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatState(Boolean state) {
|
||||||
|
if (state == null) {
|
||||||
|
return "未初始化";
|
||||||
|
}
|
||||||
|
return state ? "开启" : "关闭";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -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.RxTag;
|
||||||
import com.example.iot_controlhost.utils.global.Variable;
|
import com.example.iot_controlhost.utils.global.Variable;
|
||||||
import com.example.iot_controlhost.utils.log.MyLog;
|
import com.example.iot_controlhost.utils.log.MyLog;
|
||||||
|
import com.example.iot_controlhost.utils.log.UserLog;
|
||||||
import com.xuexiang.rxutil2.rxbus.RxBusUtils;
|
import com.xuexiang.rxutil2.rxbus.RxBusUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,12 +154,14 @@ public abstract class ControlStrategy {
|
|||||||
//防止重复发送相同的提示日志
|
//防止重复发送相同的提示日志
|
||||||
tipsTempStr = tips.toString();
|
tipsTempStr = tips.toString();
|
||||||
MyLog.auto("自动温控:" + tips);
|
MyLog.auto("自动温控:" + tips);
|
||||||
|
UserLog.system("自动控制", "温度控制状态变化:" + tips.toString().trim());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mode != -1 && temp != -1 && RoomSetting.isAirConditionForceClose()) {
|
if (mode != -1 && temp != -1 && RoomSetting.isAirConditionForceClose()) {
|
||||||
tips.append(",在消毒期间无法开启空调");
|
tips.append(",在消毒期间无法开启空调");
|
||||||
}
|
}
|
||||||
MyLog.autoError("自动温控操作失败:" + tips);
|
MyLog.autoError("自动温控操作失败:" + tips);
|
||||||
|
UserLog.systemError("自动控制", "温度控制操作失败:" + tips.toString().trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+145
-30
@@ -3,6 +3,7 @@ package com.example.iot_controlhost.utils.control.ventilator;
|
|||||||
import com.example.iot_controlhost.model.thread.QueueIOTask;
|
import com.example.iot_controlhost.model.thread.QueueIOTask;
|
||||||
import com.example.iot_controlhost.utils.MyQueue;
|
import com.example.iot_controlhost.utils.MyQueue;
|
||||||
import com.example.iot_controlhost.utils.MyUtil;
|
import com.example.iot_controlhost.utils.MyUtil;
|
||||||
|
import com.example.iot_controlhost.utils.PollingTask;
|
||||||
import com.example.iot_controlhost.utils.control.Control;
|
import com.example.iot_controlhost.utils.control.Control;
|
||||||
import com.example.iot_controlhost.utils.global.AIModelSet;
|
import com.example.iot_controlhost.utils.global.AIModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
||||||
@@ -26,6 +27,10 @@ public class VentilatorController extends Control {
|
|||||||
* 关闭持续时间
|
* 关闭持续时间
|
||||||
*/
|
*/
|
||||||
int targetClose;
|
int targetClose;
|
||||||
|
/**
|
||||||
|
* 调度代次。参数重载或停止后,旧任务即使已经进入执行阶段也不能继续串接新任务。
|
||||||
|
*/
|
||||||
|
private volatile long scheduleGeneration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单例模式
|
* 单例模式
|
||||||
@@ -47,8 +52,14 @@ public class VentilatorController extends Control {
|
|||||||
protected boolean startMethod() {
|
protected boolean startMethod() {
|
||||||
//每次开启时获取最新参数
|
//每次开启时获取最新参数
|
||||||
refreshRule();
|
refreshRule();
|
||||||
MyLog.auto("自动:首先关闭换气扇");
|
if (!RoomController.fan.isAvailable()) {
|
||||||
stopFan(0);
|
MyLog.autoError("换气自动控制无法启动:换气扇不可用");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MyLog.auto("换气自动控制初始化:先关闭换气扇,开启时长=" + targetOpen
|
||||||
|
+ "分钟,关闭时长=" + targetClose + "分钟");
|
||||||
|
long generation = ++scheduleGeneration;
|
||||||
|
stopFan(0, generation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,26 +73,63 @@ public class VentilatorController extends Control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startFan(int minutes) {
|
private void startFan(int minutes, long generation) {
|
||||||
MyLog.auto("自动:" + minutes + "分钟后开启换气扇");
|
PollingTask activePollingTask = pollingTask;
|
||||||
pollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_START_FUN, minutes * 60, () -> {
|
if (generation != scheduleGeneration || activePollingTask == null) {
|
||||||
refreshRule();
|
MyLog.auto("换气旧任务不再创建开启调度:任务代次=" + generation
|
||||||
if (targetOpen == 0 && targetClose == 0) {
|
+ ",当前代次=" + scheduleGeneration);
|
||||||
//如果开始停止时间均为0,则3分钟后再次检测配置项
|
return;
|
||||||
MyLog.auto("开始停止时间均为0,3分钟后再次检测配置项");
|
}
|
||||||
startFan(3);
|
MyLog.auto("换气调度:计划" + minutes + "分钟后开启换气扇");
|
||||||
return;
|
activePollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_START_FUN, minutes * 60, () -> {
|
||||||
}
|
try {
|
||||||
// 开启换气扇
|
if (generation != scheduleGeneration) {
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
MyLog.auto("换气旧任务已失效,取消开启阶段:任务代次=" + generation
|
||||||
String airExchangeMode = AutoModelSet.getAirExchangeMode();
|
+ ",当前代次=" + scheduleGeneration);
|
||||||
RoomController.fan.setPowerSupply(true);
|
return;
|
||||||
if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[1])) {
|
|
||||||
MyUtil.autoControlOperateThird(RoomController.airExchange, true);
|
|
||||||
}
|
}
|
||||||
}));
|
refreshRule();
|
||||||
MyLog.auto("自动:" + targetOpen + "分钟后关闭换气扇");
|
if (targetOpen == 0 && targetClose == 0) {
|
||||||
stopFan(targetOpen);
|
//如果开始停止时间均为0,则3分钟后再次检测配置项
|
||||||
|
MyLog.auto("换气控制暂停:开启和关闭时长均为0,3分钟后重新读取配置");
|
||||||
|
startFan(3, generation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 开启换气扇
|
||||||
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
||||||
|
if (generation != scheduleGeneration) {
|
||||||
|
MyLog.auto("换气旧任务已失效,取消已入队的开启操作:任务代次=" + generation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String airExchangeMode = AutoModelSet.getAirExchangeMode();
|
||||||
|
boolean fanResult;
|
||||||
|
if (RoomController.fan.hasAvailableFanForCurrentMode()) {
|
||||||
|
fanResult = MyUtil.autoControlOperateThird(RoomController.fan, true);
|
||||||
|
} else {
|
||||||
|
fanResult = false;
|
||||||
|
MyLog.autoError("换气控制无法开启:模式=" + AutoModelSet.getVentilatorMode()
|
||||||
|
+ ",进气扇可用=" + RoomController.intakeFan.isAvailable()
|
||||||
|
+ ",端口=" + RoomController.intakeFan.getPorts()
|
||||||
|
+ ";排气扇可用=" + RoomController.exhaustFan.isAvailable()
|
||||||
|
+ ",端口=" + RoomController.exhaustFan.getPorts());
|
||||||
|
}
|
||||||
|
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, generation);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
MyLog.autoError("换气扇开启阶段异常:异常=" + throwable.getClass().getSimpleName()
|
||||||
|
+ ",原因=" + throwable.getMessage() + ",1分钟后尝试恢复调度");
|
||||||
|
if (generation == scheduleGeneration) {
|
||||||
|
startFan(1, generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,22 +138,89 @@ public class VentilatorController extends Control {
|
|||||||
*
|
*
|
||||||
* @param minutes 该时间后检测下一步动作
|
* @param minutes 该时间后检测下一步动作
|
||||||
*/
|
*/
|
||||||
private void stopFan(int minutes) {
|
private void stopFan(int minutes, long generation) {
|
||||||
|
PollingTask activePollingTask = pollingTask;
|
||||||
|
if (generation != scheduleGeneration || activePollingTask == null) {
|
||||||
|
MyLog.auto("换气旧任务不再创建关闭调度:任务代次=" + generation
|
||||||
|
+ ",当前代次=" + scheduleGeneration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 开启换气扇,持续targetOpen分钟
|
// 开启换气扇,持续targetOpen分钟
|
||||||
pollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_STOP_FUN, minutes * 60, () -> {
|
activePollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_STOP_FUN, minutes * 60, () -> {
|
||||||
// 关闭换气扇
|
try {
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
if (generation != scheduleGeneration) {
|
||||||
if (RoomController.airExchange.isAvailable() && AutoModelSet.getAirExchangeMode().equals(SpinnerList.AIR_EXCHANGE_MODES[1])) {
|
MyLog.auto("换气旧任务已失效,取消关闭阶段:任务代次=" + generation
|
||||||
MyUtil.autoControlOperateThird(RoomController.airExchange, false);
|
+ ",当前代次=" + scheduleGeneration);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
RoomController.fan.setPowerSupply(false);
|
refreshRule();
|
||||||
}));
|
// 关闭换气扇
|
||||||
startFan(targetClose);
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
||||||
|
if (generation != scheduleGeneration) {
|
||||||
|
MyLog.auto("换气旧任务已失效,取消已入队的关闭操作:任务代次=" + generation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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, generation);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
MyLog.autoError("换气扇关闭阶段异常:异常=" + throwable.getClass().getSimpleName()
|
||||||
|
+ ",原因=" + throwable.getMessage() + ",1分钟后尝试恢复调度");
|
||||||
|
if (generation == scheduleGeneration) {
|
||||||
|
startFan(1, generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启停时长或分路模式变化后,废弃旧计时并从关闭阶段按新规则重新建立周期。
|
||||||
|
*/
|
||||||
|
public synchronized void reloadSchedule(String source) {
|
||||||
|
int previousOpen = targetOpen;
|
||||||
|
int previousClose = targetClose;
|
||||||
|
boolean wasRunning = isRunning();
|
||||||
|
refreshRule();
|
||||||
|
|
||||||
|
if (RoomSetting.getMode() == 1 && !AutoModelSet.isAutoVentilator()) {
|
||||||
|
MyLog.auto("换气调度重载:来源=" + source + ",自动换气已关闭,停止现有任务");
|
||||||
|
stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!wasRunning) {
|
||||||
|
if (RoomSetting.getMode() == 1 && AutoModelSet.isAutoVentilator()) {
|
||||||
|
MyLog.auto("换气调度重载:来源=" + source + ",控制器未运行,按最新参数启动;开启="
|
||||||
|
+ targetOpen + "分钟,关闭=" + targetClose + "分钟");
|
||||||
|
start();
|
||||||
|
} else {
|
||||||
|
MyLog.auto("换气参数已更新但控制器当前未运行:来源=" + source + ",开启="
|
||||||
|
+ targetOpen + "分钟,关闭=" + targetClose + "分钟");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long invalidGeneration = ++scheduleGeneration;
|
||||||
|
if (pollingTask != null) {
|
||||||
|
pollingTask.stopAllPollingTasks();
|
||||||
|
}
|
||||||
|
pollingTask = null;
|
||||||
|
start = false;
|
||||||
|
MyLog.auto("换气调度重载:来源=" + source + ",开启=" + previousOpen + "→" + targetOpen
|
||||||
|
+ "分钟,关闭=" + previousClose + "→" + targetClose + "分钟,模式="
|
||||||
|
+ AutoModelSet.getVentilatorMode() + ",失效标记=" + invalidGeneration + ",旧任务已取消");
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void stopMethod() {
|
protected void stopMethod() {
|
||||||
|
scheduleGeneration++;
|
||||||
MyLog.auto("停止自动控制换气————————————————————————————————————————————————————");
|
MyLog.auto("停止自动控制换气————————————————————————————————————————————————————");
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> {
|
||||||
MyUtil.autoControlOperateThird(RoomController.fan);
|
MyUtil.autoControlOperateThird(RoomController.fan);
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ import com.example.iot_controlhost.utils.log.MyLog;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import android_serialport_api.SerialPortUtil;
|
import android_serialport_api.SerialPortUtil;
|
||||||
|
|
||||||
@@ -80,6 +82,16 @@ public class RoomController {
|
|||||||
* 485除湿机物理电源
|
* 485除湿机物理电源
|
||||||
*/
|
*/
|
||||||
public static Dehumidifier485Relay dehumidifier485Relay = new Dehumidifier485Relay(DEVICE_NAME[17]);
|
public static Dehumidifier485Relay dehumidifier485Relay = new Dehumidifier485Relay(DEVICE_NAME[17]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 红外除湿机运行功率阈值。
|
||||||
|
* 待机时约2W,风机启动后约60-70W,压缩机启动后约300W。
|
||||||
|
*/
|
||||||
|
private static final double DEHUMIDIFIER_RUNNING_POWER = 50.0;
|
||||||
|
private static final int DEHUMIDIFIER_MISMATCH_CONFIRM_TIMES = 2;
|
||||||
|
private static int dehumidifierMismatchTimes;
|
||||||
|
private static Boolean lastDehumidifierMismatchTarget;
|
||||||
|
private static final Set<String> dehumidifierPollingLogKeys = new HashSet<>();
|
||||||
/**
|
/**
|
||||||
* 加湿器
|
* 加湿器
|
||||||
*/
|
*/
|
||||||
@@ -295,31 +307,29 @@ public class RoomController {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 检查除湿机功率是否与除湿机物理电源状态匹配,时刻监测当前除湿机功率状态,使之与软件开关匹配
|
// 485除湿机保持原有5分钟检测逻辑。
|
||||||
// 防止出现软件界面中开启除湿器但实际上除湿器未开启(功率不足100W)的情况
|
if (RoomController.dehumidifier485Relay.isAvailable()) {
|
||||||
if (RoomController.dehumidifier.isAvailable() || RoomController.dehumidifier485Relay.isAvailable()) {
|
|
||||||
PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_DEHUMIDIFIER_STATUS, 60 * 5, () -> {
|
PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_DEHUMIDIFIER_STATUS, 60 * 5, () -> {
|
||||||
if (RoomController.dehumidifier.isAvailable() && RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() < 100) {
|
if (RoomController.dehumidifier485.isPowerSupply()
|
||||||
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.setPowerSupply(false)));
|
|
||||||
}
|
|
||||||
if (RoomController.dehumidifier485Relay.isAvailable()
|
|
||||||
&& RoomController.dehumidifier485.isPowerSupply()
|
|
||||||
&& RoomController.dehumidifier485Relay.getPower() < 100) {
|
&& RoomController.dehumidifier485Relay.getPower() < 100) {
|
||||||
MyLog.controllerError("485除湿机电源已开启但功率不足100W,现尝试再次开启485除湿机物理电源");
|
MyLog.controllerError("485除湿机电源已开启但功率不足100W,现尝试再次开启485除湿机物理电源");
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier485Relay.setPowerSupply(true)));
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(
|
||||||
} else if (RoomController.dehumidifier485Relay.isAvailable()
|
() -> RoomController.dehumidifier485Relay.setPowerSupply(true)));
|
||||||
&& !RoomController.dehumidifier485.isPowerSupply()
|
} else if (!RoomController.dehumidifier485.isPowerSupply()
|
||||||
&& RoomController.dehumidifier485Relay.getPower() > 100) {
|
&& RoomController.dehumidifier485Relay.getPower() > 100) {
|
||||||
MyLog.controllerError("485除湿机电源已关闭但功率大于100W,现尝试再次关闭485除湿机物理电源");
|
MyLog.controllerError("485除湿机电源已关闭但功率大于100W,现尝试再次关闭485除湿机物理电源");
|
||||||
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier485Relay.setPowerSupply(false)));
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(
|
||||||
|
() -> RoomController.dehumidifier485Relay.setPowerSupply(false)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 检查除湿机功率是否与软件状态匹配。红外开关为同一条翻转指令,必须确认状态后才能补发。
|
||||||
|
if (RoomController.dehumidifier.isAvailable()) {
|
||||||
|
PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_INFRARED_DEHUMIDIFIER_STATUS, 60, () ->
|
||||||
|
MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(
|
||||||
|
new QueueIOTask(RoomController::checkInfraredDehumidifierStatus)));
|
||||||
|
}
|
||||||
//初始化新线程,时刻监测当前空调红外状态,以使得物理开关与之匹配
|
//初始化新线程,时刻监测当前空调红外状态,以使得物理开关与之匹配
|
||||||
PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_CONTROLLER_STATE_CHECK_AIR, RoomSetting.getTimeOfAirStopRelay(), () -> {
|
PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_CONTROLLER_STATE_CHECK_AIR, RoomSetting.getTimeOfAirStopRelay(), () -> {
|
||||||
// MyLog.test("空调后台线程:现检查是否需要关闭空调物理电源");
|
// MyLog.test("空调后台线程:现检查是否需要关闭空调物理电源");
|
||||||
@@ -380,6 +390,116 @@ public class RoomController {
|
|||||||
public static boolean doNotNeedDelayCloseDehumidifier = true;
|
public static boolean doNotNeedDelayCloseDehumidifier = true;
|
||||||
public static boolean doNotNeedDelayCloseDehumidifier485 = true;
|
public static boolean doNotNeedDelayCloseDehumidifier485 = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分路功率或总功率校验红外除湿机真实状态。
|
||||||
|
*/
|
||||||
|
private static void checkInfraredDehumidifierStatus() {
|
||||||
|
if (!RoomController.dehumidifier.isAvailable()) {
|
||||||
|
resetDehumidifierMismatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean useBranchPower = RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]);
|
||||||
|
double power;
|
||||||
|
String powerSource;
|
||||||
|
if (useBranchPower) {
|
||||||
|
if (!RoomController.dehumidifier.collectSensorData()) {
|
||||||
|
resetDehumidifierMismatchCount();
|
||||||
|
logDehumidifierPollingOnce("branch-collect-failed", "红外除湿机状态检测:BBIT继电器分路功率采集失败,本轮不判断状态", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
power = RoomController.dehumidifier.getPower();
|
||||||
|
powerSource = "BBIT继电器分路功率";
|
||||||
|
} else {
|
||||||
|
if (!RoomSensor.consumptionSensor.isAvailable()) {
|
||||||
|
resetDehumidifierMismatchCount();
|
||||||
|
logDehumidifierPollingOnce("total-unavailable", "红外除湿机状态检测:无BBIT继电器分路功率,且总能耗传感器不可用,本轮不判断状态", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasOtherPoweredDeviceForDehumidifierCheck()) {
|
||||||
|
resetDehumidifierMismatchCount();
|
||||||
|
logDehumidifierPollingOnce("other-device-running", "红外除湿机状态检测:其他设备正在运行,无法从总功率中分离除湿机功率,本轮不判断状态", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!RoomSensor.consumptionSensor.collectSensorData(
|
||||||
|
RoomSensor.consumptionSensor.getModel(), RoomSensor.consumptionSensor.getAddress())) {
|
||||||
|
resetDehumidifierMismatchCount();
|
||||||
|
logDehumidifierPollingOnce("total-collect-failed", "红外除湿机状态检测:总能耗功率采集失败,本轮不判断状态", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
power = RoomSensor.consumptionSensor.getPower();
|
||||||
|
powerSource = "总能耗功率";
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean targetState = RoomController.dehumidifier.isPowerSupply();
|
||||||
|
boolean actualState = power > DEHUMIDIFIER_RUNNING_POWER;
|
||||||
|
if (targetState == actualState) {
|
||||||
|
if (lastDehumidifierMismatchTarget != null) {
|
||||||
|
MyLog.controller("红外除湿机状态恢复一致:目标=" + formatPowerState(targetState)
|
||||||
|
+ "," + powerSource + "=" + power + "W");
|
||||||
|
}
|
||||||
|
resetDehumidifierMismatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDehumidifierMismatchTarget == null || lastDehumidifierMismatchTarget != targetState) {
|
||||||
|
dehumidifierMismatchTimes = 0;
|
||||||
|
lastDehumidifierMismatchTarget = targetState;
|
||||||
|
dehumidifierPollingLogKeys.clear();
|
||||||
|
}
|
||||||
|
dehumidifierMismatchTimes++;
|
||||||
|
logDehumidifierPollingOnce("mismatch-" + targetState,
|
||||||
|
"红外除湿机状态不一致:目标=" + formatPowerState(targetState)
|
||||||
|
+ ",检测=" + formatPowerState(actualState) + "," + powerSource + "=" + power
|
||||||
|
+ "W,将连续确认" + DEHUMIDIFIER_MISMATCH_CONFIRM_TIMES + "次后纠正", true);
|
||||||
|
if (dehumidifierMismatchTimes < DEHUMIDIFIER_MISMATCH_CONFIRM_TIMES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = RoomController.dehumidifier.setPowerSupply(targetState);
|
||||||
|
logDehumidifierPollingOnce("correct-" + targetState + "-" + result,
|
||||||
|
"红外除湿机状态纠正:重新发送" + formatPowerState(targetState) + "指令,结果="
|
||||||
|
+ (result ? "成功" : "失败") + ",纠正前" + powerSource + "=" + power + "W", !result);
|
||||||
|
dehumidifierMismatchTimes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用总能耗功率时,只有除湿机之外的设备均未开启,功率才可归属于除湿机。
|
||||||
|
*/
|
||||||
|
private static boolean hasOtherPoweredDeviceForDehumidifierCheck() {
|
||||||
|
for (Relay relayDevice : RoomController.getAllAvailableRelays()) {
|
||||||
|
if (relayDevice != RoomController.dehumidifier && relayDevice.isPowerSupply()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RoomController.dehumidifier485.isAvailable() && RoomController.dehumidifier485.isPowerSupply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetDehumidifierMismatchCount() {
|
||||||
|
dehumidifierMismatchTimes = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetDehumidifierMismatch() {
|
||||||
|
dehumidifierMismatchTimes = 0;
|
||||||
|
lastDehumidifierMismatchTarget = null;
|
||||||
|
dehumidifierPollingLogKeys.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void logDehumidifierPollingOnce(String key, String message, boolean error) {
|
||||||
|
if (!dehumidifierPollingLogKeys.add(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
MyLog.controllerError(message);
|
||||||
|
} else {
|
||||||
|
MyLog.controller(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatPowerState(boolean state) {
|
||||||
|
return state ? "开启" : "关闭";
|
||||||
|
}
|
||||||
|
|
||||||
public static void closeAllControllers() {
|
public static void closeAllControllers() {
|
||||||
List<Relay> relays = RoomController.getAllAvailableRelays();
|
List<Relay> relays = RoomController.getAllAvailableRelays();
|
||||||
MyInfraredUtils.getInstances().setPowerSupply(false);
|
MyInfraredUtils.getInstances().setPowerSupply(false);
|
||||||
|
|||||||
@@ -199,36 +199,55 @@ public class RoomSensor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取室内平均湿度
|
* 获取未叠加校准偏差的室内平均真实湿度,供自动控制和安全保护使用。
|
||||||
*/
|
*/
|
||||||
public static double getAverageIndoorHumidity() {
|
public static double getBeforeCalibratedIndoorHumidity() {
|
||||||
double sumOfHumidities = 0.0;
|
double sumOfHumidities = 0.0;
|
||||||
int numberOfEnabledSensors = 0;
|
int numberOfEnabledSensors = 0;
|
||||||
|
|
||||||
for (THSensor sensor : dynamicTHSensorList) {
|
for (THSensor sensor : dynamicTHSensorList) {
|
||||||
if (sensor.humidity == 0 || sensor.humidity == 100) {
|
if (sensor.humidity <= 0 || sensor.humidity > 100) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sumOfHumidities += sensor.humidity;
|
sumOfHumidities += sensor.humidity;
|
||||||
numberOfEnabledSensors++;
|
numberOfEnabledSensors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GasSensor sensor : dynamicCO2SensorList) {
|
for (GasSensor sensor : dynamicCO2SensorList) {
|
||||||
if (sensor.getHumidity() == 0 || sensor.getHumidity() == 100) {
|
if (sensor.getHumidity() <= 0 || sensor.getHumidity() > 100) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sumOfHumidities += sensor.getHumidity();
|
sumOfHumidities += sensor.getHumidity();
|
||||||
numberOfEnabledSensors++;
|
numberOfEnabledSensors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (GasSensor sensor : dynamicAmmoniaSensorList) {
|
for (GasSensor sensor : dynamicAmmoniaSensorList) {
|
||||||
if (sensor.getHumidity() == 0 || sensor.getHumidity() == 100) {
|
if (sensor.getHumidity() <= 0 || sensor.getHumidity() > 100) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sumOfHumidities += sensor.getHumidity();
|
sumOfHumidities += sensor.getHumidity();
|
||||||
numberOfEnabledSensors++;
|
numberOfEnabledSensors++;
|
||||||
}
|
}
|
||||||
if (numberOfEnabledSensors == 0 || sumOfHumidities == 0) {
|
|
||||||
|
if (numberOfEnabledSensors == 0) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
return Math.round((sumOfHumidities / numberOfEnabledSensors + RoomSetting.getBiasHumidity()) * 10.0) / 10.0;
|
|
||||||
|
return Math.round(sumOfHumidities / numberOfEnabledSensors * 10.0) / 10.0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取叠加校准偏差后的室内平均湿度,供界面显示和上报使用。
|
||||||
|
*/
|
||||||
|
public static double getAverageIndoorHumidity() {
|
||||||
|
double averageHumidity = getBeforeCalibratedIndoorHumidity();
|
||||||
|
|
||||||
|
if (averageHumidity == 0.0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return Math.min(
|
||||||
|
Math.round((averageHumidity + RoomSetting.getBiasHumidity()) * 10.0) / 10.0,
|
||||||
|
99.9
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public class RxTag {
|
|||||||
* 持续检测除湿机工作状态
|
* 持续检测除湿机工作状态
|
||||||
*/
|
*/
|
||||||
public static final String TAG_DEHUMIDIFIER_STATUS = "TAG_DEHUMIDIFIER_STATUS";
|
public static final String TAG_DEHUMIDIFIER_STATUS = "TAG_DEHUMIDIFIER_STATUS";
|
||||||
|
public static final String TAG_INFRARED_DEHUMIDIFIER_STATUS = "TAG_INFRARED_DEHUMIDIFIER_STATUS";
|
||||||
/**
|
/**
|
||||||
* 持续判断是否注册
|
* 持续判断是否注册
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -66,8 +66,21 @@ public class MyLog extends Timber.Tree {
|
|||||||
public static final String TAG_FRP = "远程协助";
|
public static final String TAG_FRP = "远程协助";
|
||||||
|
|
||||||
public static List<String> getUserQueryTag() {
|
public static List<String> getUserQueryTag() {
|
||||||
|
return getSystemLogTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统诊断日志的全部落库标签,包含普通和错误级别。
|
||||||
|
*/
|
||||||
|
public static List<String> getSystemLogTags() {
|
||||||
List<String> tags = new ArrayList<>();
|
List<String> tags = new ArrayList<>();
|
||||||
Collections.addAll(tags, TAG_APP, TAG_REMOTE, TAG_CONTROLLER, TAG_WARNING,TAG_SENSOR, TAG_NETWORK, TAG_AUTO);
|
List<String> 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;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,19 @@ import timber.log.Timber;
|
|||||||
* 日志用户查看用
|
* 日志用户查看用
|
||||||
*/
|
*/
|
||||||
public class UserLog extends Timber.Tree {
|
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_OPERATE = "操作日志";
|
||||||
|
public static final String FILTER_USER_SENSOR = "用户传感器日志";
|
||||||
public static final String FILTER_SENSOR = "传感器日志";
|
public static final String FILTER_SENSOR = "传感器日志";
|
||||||
|
public static final String FILTER_CONTROLLER = "控制器日志";
|
||||||
public static final String FILTER_TASK = "作业日志";
|
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<String> getUserQueryFilters() {
|
public static List<String> 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<String> getStorageTagsForFilter(String filter) {
|
public static List<String> 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)) {
|
if (FILTER_OPERATE.equals(filter) || TAG_USER_OPERATE.equals(filter)) {
|
||||||
return tagsWithError(TAG_USER_OPERATE);
|
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);
|
return tagsWithError(TAG_USER_SENSOR);
|
||||||
}
|
}
|
||||||
if (FILTER_TASK.equals(filter) || TAG_USER_TASK.equals(filter)) {
|
if (FILTER_TASK.equals(filter) || TAG_USER_TASK.equals(filter)) {
|
||||||
@@ -70,6 +95,9 @@ public class UserLog extends Timber.Tree {
|
|||||||
for (String userTag : getUserLogTags()) {
|
for (String userTag : getUserLogTags()) {
|
||||||
tags.addAll(tagsWithError(userTag));
|
tags.addAll(tagsWithError(userTag));
|
||||||
}
|
}
|
||||||
|
if (FILTER_ALL.equals(filter) || filter == null) {
|
||||||
|
tags.addAll(MyLog.getSystemLogTags());
|
||||||
|
}
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,19 +106,7 @@ public class UserLog extends Timber.Tree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getCategoryName(String tag) {
|
public static String getCategoryName(String tag) {
|
||||||
if (tag == null) {
|
return tag == null ? "未知" : tag;
|
||||||
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) {
|
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);
|
Timber.tag(TAG_USER_SYSTEM + MyLog.TAG_ERROR).e(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void systemError(String source, String msg) {
|
||||||
|
systemError(formatSource(source, msg));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 崩溃场景使用非阻塞入口,防止数据库繁忙时阻断进程重启。
|
* 崩溃场景使用非阻塞入口,防止数据库繁忙时阻断进程重启。
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.example.iot_controlhost.utils.MyQueue;
|
|||||||
import com.example.iot_controlhost.utils.MyUtil;
|
import com.example.iot_controlhost.utils.MyUtil;
|
||||||
import com.example.iot_controlhost.utils.control.RemoteControl;
|
import com.example.iot_controlhost.utils.control.RemoteControl;
|
||||||
import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils;
|
import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils;
|
||||||
|
import com.example.iot_controlhost.utils.control.ventilator.VentilatorController;
|
||||||
import com.example.iot_controlhost.utils.global.AIModelSet;
|
import com.example.iot_controlhost.utils.global.AIModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
import com.example.iot_controlhost.utils.global.AutoModelSet;
|
||||||
import com.example.iot_controlhost.utils.global.HardwareSetting;
|
import com.example.iot_controlhost.utils.global.HardwareSetting;
|
||||||
@@ -35,6 +36,8 @@ import org.json.JSONObject;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MQTT 主题
|
* MQTT 主题
|
||||||
@@ -47,6 +50,8 @@ public class TopicClass {
|
|||||||
private static String areaDataTemp = "";
|
private static String areaDataTemp = "";
|
||||||
private static String sensorDataTemp = "";
|
private static String sensorDataTemp = "";
|
||||||
private static String sensorInfoDataTemp = "";
|
private static String sensorInfoDataTemp = "";
|
||||||
|
private static final int MAX_PENDING_LOGS = 200;
|
||||||
|
private static final Queue<PendingLog> PENDING_LOGS = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
//————————————————————————————————————————————————————————————————————————————————获取区域集合————————————————————————————————————————————————————————————————
|
//————————————————————————————————————————————————————————————————————————————————获取区域集合————————————————————————————————————————————————————————————————
|
||||||
|
|
||||||
@@ -309,16 +314,56 @@ public class TopicClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void uploadLog(int tag, int priority, String message, Date datetime) {
|
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();
|
JSONObject json = new JSONObject();
|
||||||
try {
|
try {
|
||||||
json.put("areacode", RoomSetting.getAreaCode());
|
json.put("areacode", RoomSetting.getAreaCode());
|
||||||
json.put("tage", tag);
|
json.put("tage", pendingLog.tag);
|
||||||
json.put("msglevel", priority);
|
json.put("msglevel", pendingLog.priority);
|
||||||
json.put("msg", message);
|
json.put("msg", pendingLog.message);
|
||||||
json.put("datetime", MyUtil.getDateTime(datetime));
|
json.put("datetime", MyUtil.getDateTime(pendingLog.datetime));
|
||||||
MQTTService.publish(DeviceRunLogsPost(), json.toString());
|
MQTTService.publish(DeviceRunLogsPost(), json.toString());
|
||||||
} catch (JSONException e) {
|
} 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 +609,18 @@ public class TopicClass {
|
|||||||
//唯一区域码 之前通过唯一区域码获得索引,现在就一个房间,所以不需要
|
//唯一区域码 之前通过唯一区域码获得索引,现在就一个房间,所以不需要
|
||||||
String code = jsons.getString("code");
|
String code = jsons.getString("code");
|
||||||
if (RoomSetting.getAreaCode().equals(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(AIModelSet.AI_SCHEME_NAME, jsons.getString("autocontrolprogram"));
|
||||||
MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, jsons.getDouble("tempmaxwarning"));
|
MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, jsons.getDouble("tempmaxwarning"));
|
||||||
@@ -600,12 +657,32 @@ public class TopicClass {
|
|||||||
MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, jsons.getInt("autovent") != 0);
|
MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, jsons.getInt("autovent") != 0);
|
||||||
MMKVUtil.put(AutoModelSet.CYCLE_START, jsons.getInt("autoventopenvalue"));
|
MMKVUtil.put(AutoModelSet.CYCLE_START, jsons.getInt("autoventopenvalue"));
|
||||||
MMKVUtil.put(AutoModelSet.CYCLE_STOP, jsons.getInt("autoventclosevalue"));
|
MMKVUtil.put(AutoModelSet.CYCLE_STOP, jsons.getInt("autoventclosevalue"));
|
||||||
|
boolean ventilatorRuleChanged = oldAutoVentilator != AutoModelSet.isAutoVentilator()
|
||||||
|
|| oldVentilatorOpen != AutoModelSet.getCycleStart()
|
||||||
|
|| oldVentilatorClose != AutoModelSet.getCycleStop();
|
||||||
|
|
||||||
//2025年5月7日:取消以下逻辑
|
//2025年5月7日:取消以下逻辑
|
||||||
// //切换为自动模式
|
// //切换为自动模式
|
||||||
// RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3);
|
// 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) {
|
if (RoomSetting.getMode() == 1) {
|
||||||
|
if (ventilatorRuleChanged) {
|
||||||
|
// 控制逻辑直接响应远程参数,不依赖自动模式页面及其Presenter是否存活。
|
||||||
|
VentilatorController.getInstance().reloadSchedule("远程控制");
|
||||||
|
}
|
||||||
// 如果是自动模式,则刷新自动模式参数
|
// 如果是自动模式,则刷新自动模式参数
|
||||||
RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3);
|
RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
composeVersion=1.8.0
|
|
||||||
|
|||||||
Reference in New Issue
Block a user