commit d8dc699870c99969839ce5286a78eafcfd8434c6 Author: BBIT-Kai <2911862937@qq.com> Date: Mon May 25 15:01:43 2026 +0800 初始化项目,当前版本:157/3.5.0.157 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..66191e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle/ +.idea/ +app/build/ +.svn/ +build/ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..1661771 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +build/ +release/ \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..91889e4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,199 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" +} + +android { + compileSdk 35 + defaultConfig { + applicationId "com.example.iot_controlhost" + minSdkVersion 25 + targetSdkVersion 30 + versionCode 157 + versionName "3.5.0.157" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + ndk { + moduleName "mcu" + abiFilters "armeabi" + abiFilters "armeabi-v7a" + abiFilters "arm64-v8a" + } + resourceConfigurations += ['zh-rCN'] + } + packagingOptions { + jniLibs { + pickFirsts += ['lib/arm64-v8a/libc++_shared.so', 'lib/armeabi-v7a/libc++_shared.so'] + } + } + + signingConfigs { + release { + keyAlias 'store' + keyPassword '123456' + storeFile file('../key/store.jks') + storePassword '123456' + } + debug { + keyAlias 'store' + keyPassword '123456' + storeFile file('../key/store.jks') + storePassword '123456' + } + } + + //引用.so文件 + sourceSets { + main { + jniLibs.srcDirs 'libs' + } + } + + //修改生成的apk名字,格式为 app名_版本号_打包时间.apk + applicationVariants.all { variant -> + def type = variant.buildType.name + def time = new Date().format("yyyy-MM-dd_HH-mm") + if (type == 'release') { + variant.outputs.all { output -> + outputFileName = "BBIT智慧共育室_${versionCode}_${versionName}_${time}.apk" + } + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + buildFeatures { + viewBinding true + dataBinding true + compose true + } + kotlinOptions { + jvmTarget = '17' + } + namespace 'com.example.iot_controlhost' + lint { + abortOnError false + checkReleaseBuilds false + } + composeOptions { + kotlinCompilerExtensionVersion rootProject.composeVersion + } +} +apply plugin: 'com.android.application' +apply plugin: 'org.greenrobot.greendao' + +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 'com.google.android.material:material:1.8.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation files('libs\\ysAPI.jar') + implementation 'com.google.firebase:firebase-crashlytics-buildtools:2.9.9' + implementation 'androidx.core:core-ktx:1.10.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation files('libs/json-lib-2.4-jdk15.jar') + implementation files('libs/zckjAPI-2.1.jar') + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.android.support:support-annotations:28.0.0' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + //mqtt + implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5' + implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' + //条形码 + implementation 'com.google.zxing:core:3.5.0' + //屏幕适配 + implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1' + //dataBinding + implementation 'androidx.databinding:databinding-runtime:8.0.0' + //RxJava + implementation 'io.reactivex.rxjava2:rxjava:2.2.20' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0' + implementation 'com.github.xuexiangjys:RxUtil2:1.2.1' + //util + implementation 'com.blankj:utilcodex:1.31.1' + //MMKV + implementation 'com.tencent:mmkv:1.2.13' + //网络请求库 + implementation 'org.xutils:xutils:3.9.0' + //流式布局 + implementation 'com.google.android.flexbox:flexbox:3.0.0' + //日期下拉框 + implementation 'com.contrarywind:Android-PickerView:4.1.9' + //greendao· + implementation 'org.greenrobot:greendao:3.3.0' + //日志库 + implementation 'com.jakewharton.timber:timber:5.0.1' + //权限库 + implementation 'com.github.getActivity:XXPermissions:18.5' + //图表库 + implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0' + //GSON + implementation 'com.google.code.gson:gson:2.9.0' + //下拉刷新 加载更多 + implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3' + implementation 'com.scwang.smart:refresh-header-classics:2.0.3' + //Toast + implementation 'com.github.GrenderG:Toasty:1.5.2' + //时间 + implementation 'com.xhinliang:LunarCalendar:4.0.7' + //日出日落时间计算 + implementation 'com.luckycatlabs:SunriseSunsetCalculator:1.2' + implementation 'me.samlss:broccoli:1.0.0' + // 重启 + implementation 'com.jakewharton:process-phoenix:3.0.0' + + +// def lifecycle_version = "2.7.0" +// def arch_version = "2.2.0" +// +// // ViewModel +// implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" +// // LiveData +// implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" +// // Lifecycles only (without ViewModel or LiveData) +// implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" +// +// // Saved state module for ViewModel +// implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" +// +// // Annotation processor +// annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" +// // alternately - if using Java8, use the following instead of lifecycle-compiler +// implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" +// +// // optional - helpers for implementing LifecycleOwner in a Service +// implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version" +// +// // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process +// implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" +// +// // optional - ReactiveStreams support for LiveData +// implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" +// +// // optional - Test helpers for LiveData +// testImplementation "androidx.arch.core:core-testing:$arch_version" +// +// // optional - Test helpers for Lifecycle runtime +// testImplementation "androidx.lifecycle:lifecycle-runtime-testing:$lifecycle_version" +} \ No newline at end of file diff --git a/app/jni/Android.mk b/app/jni/Android.mk new file mode 100644 index 0000000..f4ce1c3 --- /dev/null +++ b/app/jni/Android.mk @@ -0,0 +1,26 @@ +# +# Copyright 2009 Cedric Priscal +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +TARGET_PLATFORM := android-3 +LOCAL_MODULE := serial_port +LOCAL_SRC_FILES := SerialPort.c +LOCAL_LDLIBS := -llog + +include $(BUILD_SHARED_LIBRARY) diff --git a/app/jni/Application.mk b/app/jni/Application.mk new file mode 100644 index 0000000..7866257 --- /dev/null +++ b/app/jni/Application.mk @@ -0,0 +1 @@ +APP_ABI := armeabi armeabi-v7a x86 diff --git a/app/jni/SerialPort.c b/app/jni/SerialPort.c new file mode 100644 index 0000000..eaf4e2d --- /dev/null +++ b/app/jni/SerialPort.c @@ -0,0 +1,163 @@ +/* + * Copyright 2009-2011 Cedric Priscal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "SerialPort.h" + +#include "android/log.h" +static const char *TAG="serial_port"; +#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) +#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) +#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) + +static speed_t getBaudrate(jint baudrate) +{ + switch(baudrate) { + case 0: return B0; + case 50: return B50; + case 75: return B75; + case 110: return B110; + case 134: return B134; + case 150: return B150; + case 200: return B200; + case 300: return B300; + case 600: return B600; + case 1200: return B1200; + case 1800: return B1800; + case 2400: return B2400; + case 4800: return B4800; + case 9600: return B9600; + case 19200: return B19200; + case 38400: return B38400; + case 57600: return B57600; + case 115200: return B115200; + case 230400: return B230400; + case 460800: return B460800; + case 500000: return B500000; + case 576000: return B576000; + case 921600: return B921600; + case 1000000: return B1000000; + case 1152000: return B1152000; + case 1500000: return B1500000; + case 2000000: return B2000000; + case 2500000: return B2500000; + case 3000000: return B3000000; + case 3500000: return B3500000; + case 4000000: return B4000000; + default: return -1; + } +} + +/* + * Class: android_serialport_SerialPort + * Method: open + * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open + (JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags) +{ + int fd; + speed_t speed; + jobject mFileDescriptor; + + /* Check arguments */ + { + speed = getBaudrate(baudrate); + if (speed == -1) { + LOGE("Invalid baudrate"); + return NULL; + } + } + + /* Opening device */ + { + jboolean iscopy; + const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy); + LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags); + fd = open(path_utf, O_RDWR | flags); + LOGD("open() fd = %d", fd); + (*env)->ReleaseStringUTFChars(env, path, path_utf); + if (fd == -1) + { + /* Throw an exception */ + LOGE("Cannot open port"); + return NULL; + } + } + + /* Configure device */ + { + struct termios cfg; + LOGD("Configuring serial port"); + if (tcgetattr(fd, &cfg)) + { + LOGE("tcgetattr() failed"); + close(fd); + return NULL; + } + + cfmakeraw(&cfg); + cfsetispeed(&cfg, speed); + cfsetospeed(&cfg, speed); + + if (tcsetattr(fd, TCSANOW, &cfg)) + { + LOGE("tcsetattr() failed"); + close(fd); + return NULL; + } + } + + /* Create a corresponding file descriptor */ + { + jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor"); + jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "", "()V"); + jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I"); + mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor); + (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd); + } + + return mFileDescriptor; +} + +/* + * Class: cedric_serial_SerialPort + * Method: close + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close + (JNIEnv *env, jobject thiz) +{ + jclass SerialPortClass = (*env)->GetObjectClass(env, thiz); + jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor"); + + jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;"); + jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I"); + + jobject mFd = (*env)->GetObjectField(env, thiz, mFdID); + jint descriptor = (*env)->GetIntField(env, mFd, descriptorID); + + LOGD("close(fd = %d)", descriptor); + close(descriptor); +} + diff --git a/app/jni/SerialPort.h b/app/jni/SerialPort.h new file mode 100644 index 0000000..61f1fb2 --- /dev/null +++ b/app/jni/SerialPort.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class android_serialport_api_SerialPort */ + +#ifndef _Included_android_serialport_api_SerialPort +#define _Included_android_serialport_api_SerialPort +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: android_serialport_api_SerialPort + * Method: open + * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor; + */ +JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open + (JNIEnv *, jclass, jstring, jint, jint); + +/* + * Class: android_serialport_api_SerialPort + * Method: close + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/jni/gen_SerialPort_h.sh b/app/jni/gen_SerialPort_h.sh new file mode 100644 index 0000000..1998186 --- /dev/null +++ b/app/jni/gen_SerialPort_h.sh @@ -0,0 +1,3 @@ +#!/bin/sh +javah -o SerialPort.h -jni -classpath ../src android_serialport_api.SerialPort + diff --git a/app/libs/arm64-v8a/libc++.so b/app/libs/arm64-v8a/libc++.so new file mode 100644 index 0000000..e56ca7c Binary files /dev/null and b/app/libs/arm64-v8a/libc++.so differ diff --git a/app/libs/arm64-v8a/libcutils.so b/app/libs/arm64-v8a/libcutils.so new file mode 100644 index 0000000..a1eb532 Binary files /dev/null and b/app/libs/arm64-v8a/libcutils.so differ diff --git a/app/libs/arm64-v8a/libmcu7502.so b/app/libs/arm64-v8a/libmcu7502.so new file mode 100644 index 0000000..3a607b0 Binary files /dev/null and b/app/libs/arm64-v8a/libmcu7502.so differ diff --git a/app/libs/arm64-v8a/libserial_port.so b/app/libs/arm64-v8a/libserial_port.so new file mode 100644 index 0000000..b6953a3 Binary files /dev/null and b/app/libs/arm64-v8a/libserial_port.so differ diff --git a/app/libs/armeabi-v7a/libc++.so b/app/libs/armeabi-v7a/libc++.so new file mode 100644 index 0000000..e56ca7c Binary files /dev/null and b/app/libs/armeabi-v7a/libc++.so differ diff --git a/app/libs/armeabi-v7a/libcutils.so b/app/libs/armeabi-v7a/libcutils.so new file mode 100644 index 0000000..a1eb532 Binary files /dev/null and b/app/libs/armeabi-v7a/libcutils.so differ diff --git a/app/libs/armeabi-v7a/libmcu7502.so b/app/libs/armeabi-v7a/libmcu7502.so new file mode 100644 index 0000000..911f5e4 Binary files /dev/null and b/app/libs/armeabi-v7a/libmcu7502.so differ diff --git a/app/libs/armeabi-v7a/libserial_port.so b/app/libs/armeabi-v7a/libserial_port.so new file mode 100644 index 0000000..3c15c77 Binary files /dev/null and b/app/libs/armeabi-v7a/libserial_port.so differ diff --git a/app/libs/armeabi/libserial_port.so b/app/libs/armeabi/libserial_port.so new file mode 100644 index 0000000..fd63606 Binary files /dev/null and b/app/libs/armeabi/libserial_port.so differ diff --git a/app/libs/json-lib-2.4-jdk15.jar b/app/libs/json-lib-2.4-jdk15.jar new file mode 100644 index 0000000..68d4f3b Binary files /dev/null and b/app/libs/json-lib-2.4-jdk15.jar differ diff --git a/app/libs/libs.rar b/app/libs/libs.rar new file mode 100644 index 0000000..43a99a2 Binary files /dev/null and b/app/libs/libs.rar differ diff --git a/app/libs/x86/libserial_port.so b/app/libs/x86/libserial_port.so new file mode 100644 index 0000000..4f1f03e Binary files /dev/null and b/app/libs/x86/libserial_port.so differ diff --git a/app/libs/ysAPI.jar b/app/libs/ysAPI.jar new file mode 100644 index 0000000..b1dbe23 Binary files /dev/null and b/app/libs/ysAPI.jar differ diff --git a/app/libs/zckjAPI-2.1.jar b/app/libs/zckjAPI-2.1.jar new file mode 100644 index 0000000..a4f067b Binary files /dev/null and b/app/libs/zckjAPI-2.1.jar differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..802783f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/android_serialport_api/SerialPort.java b/app/src/main/java/android_serialport_api/SerialPort.java new file mode 100644 index 0000000..56dcb15 --- /dev/null +++ b/app/src/main/java/android_serialport_api/SerialPort.java @@ -0,0 +1,73 @@ +/* + * Copyright 2009 Cedric Priscal + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android_serialport_api; + +import android.util.Log; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class SerialPort { + private FileDescriptor mFd; + private FileInputStream mFileInputStream; + private FileOutputStream mFileOutputStream; + public SerialPort(File device, int baudrate, int parity, int dataBits, int stopBit) throws SecurityException, IOException { + if (!device.canRead() || !device.canWrite()) { + try { + Process su; + su = Runtime.getRuntime().exec("/system/bin/su"); + String cmd = "chmod 666 " + device.getAbsolutePath() + "\n" + "exit\n"; + su.getOutputStream().write(cmd.getBytes()); + if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) { + throw new SecurityException(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + mFd = open(device.getAbsolutePath(), baudrate, parity, dataBits, stopBit); + if (mFd == null) { + throw new IOException(); + } + mFileInputStream = new FileInputStream(mFd); + mFileOutputStream = new FileOutputStream(mFd); + } + + public FileInputStream getInputStream() { + return mFileInputStream; + } + + public OutputStream getOutputStream() { + return mFileOutputStream; + } + + private native static FileDescriptor open(String path, int baudrate, int parity, int dataBits, int stopBit); + + public native void close(); + + static { + try { + System.loadLibrary("serial_port"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/android_serialport_api/SerialPortUtil.java b/app/src/main/java/android_serialport_api/SerialPortUtil.java new file mode 100644 index 0000000..8e528a4 --- /dev/null +++ b/app/src/main/java/android_serialport_api/SerialPortUtil.java @@ -0,0 +1,276 @@ +package android_serialport_api; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.utils.DataUtils; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class SerialPortUtil { + /** + * 发送的数据内容 + */ + public static String T = "T:"; + /** + * 接收的数据内容 + */ + public static String R = "R:"; + /** + * 串口通信类别-控制器 + */ + public final static String TYPE_CONTROLLER = "TYPE_CONTROLLER"; + /** + * 串口通信类别-传感器 + */ + public final static String TYPE_SENSOR = "TYPE_SENSOR"; + /** + * 使用SerialPortUtil的设备 + */ + private String type; + private static SerialPortUtil sensorSP; + private static SerialPortUtil controllerSP; + private SerialPort serialPort; + private InputStream inputStream; + private OutputStream outputStream; + private boolean isStart; + + private SerialPortUtil(String type) { + this.type = type; + isStart = false; + } + + public static SerialPortUtil getInstance(String type) { + if (Device.isIntegratedController() || TYPE_CONTROLLER.equals(type)) { + if (controllerSP == null) { + synchronized (SerialPortUtil.class) { + if (controllerSP == null) { + controllerSP = new SerialPortUtil(type); + } + } + } + return controllerSP; + } else { + // 传感器 + if (sensorSP == null) { + synchronized (SerialPortUtil.class) { + if (sensorSP == null) { + sensorSP = new SerialPortUtil(type); + } + } + } + return sensorSP; + } + } + + /** + * 开启串口 + * 使用设置好的端口号、波特率 + */ + public boolean openSerialPort() { + String portName; + String baud; + if (TYPE_SENSOR.equals(type)) { + portName = HardwareSetting.getSensorSerialCom(); + baud = HardwareSetting.getSensorSerialComBaud(); + } else { + portName = HardwareSetting.getControllerSerialCom(); + baud = HardwareSetting.getControllerSerialBaud(); + } + return openSerialPort(portName, baud); + } + + /** + * 开启串口 + * 自定义端口号、波特率 + */ + public boolean openSerialPort(String serialName, String baud) { + if (StringUtils.isEmpty(serialName)) { + isStart = false; + if (TYPE_SENSOR.equals(type)) { + MyLog.sensorError("传感器由于没有正确配置串口号导致串口打开失败"); + } else { + MyLog.controllerError("控制器由于没有正确配置串口号导致串口打开失败"); + } + return isStart; + } + try { + serialPort = new SerialPort(new File(serialName), Integer.parseInt(baud), 0, 8, 1); + inputStream = serialPort.getInputStream(); + outputStream = serialPort.getOutputStream(); + isStart = true; + if (TYPE_SENSOR.equals(type)) { + MyLog.sensor("传感器串口" + serialName + "打开成功"); + } else { + MyLog.controller("控制器串口" + serialName + "打开成功"); + } + } catch (IOException e) { + e.printStackTrace(); + isStart = false; + if (TYPE_SENSOR.equals(type)) { + MyLog.sensorError("传感器串口<" + serialName + ">打开失败"); + } else { + MyLog.controllerError("控制器串口<" + serialName + ">打开失败"); + } + } + return isStart; + } + + /** + * 关闭串口 + */ + public boolean close() { + try { + if (inputStream != null) { + inputStream.close(); + } + if (outputStream != null) { + outputStream.close(); + } + if (serialPort != null) { + serialPort.close(); + } + isStart = false; + return true; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + /** + * 发送数据并等待接收响应 + * + * @param device 设备类型 用于确定输出日志的类型 + * @param data 要发送的数据 + * @return 接收到的数据或空字符串(如果超时) + */ + public String sendAndReceiveSerialData(Device device, String data) { + boolean isSensor = false; + if (!isStart || inputStream == null) { + MyLog.appError("串口未打开但尝试发送数据"); + return null; + } + if (device instanceof Sensor) { + //传感器在使用 + isSensor = true; + } + String readString = null; + try { + byte[] sendData = DataUtils.HexToByteArr(data); + outputStream.write(sendData); + outputStream.flush(); + T = "T:" + data; + if (isSensor) { + MyLog.sensor(T); + } else { + MyLog.controller(T); + } + long startTime = System.currentTimeMillis(); + //最多等待1s + while (System.currentTimeMillis() - startTime < 1000) { + int available = inputStream.available(); + if (available > 0) { + byte[] readData = new byte[available]; + int size = inputStream.read(readData); + if (size > 0) { + byte[] readData2 = new byte[size]; + for (int i = 0; i < size; i++) { + readData2[i] = readData[i]; + } + readString = DataUtils.ByteArrToHex(readData2); + if (readString.startsWith(data.substring(0, 2))) { + //串口接收正确数据 + //持续接收,直到接收到完整的数据,使用CRC校验readString后4位 + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < 1000 && !passCRC(readString)) { + available = inputStream.available(); + if (available > 0) { + readData = new byte[available]; + size = inputStream.read(readData); + if (size > 0) { + readData2 = new byte[size]; + for (int i = 0; i < size; i++) { + readData2[i] = readData[i]; + } + String newReadString = DataUtils.ByteArrToHex(readData2); + readString = readString + newReadString; + //继续接收,拼接新数据: + if (isSensor) { + MyLog.sensor("R,join:" + newReadString); + } else { + MyLog.controller("R,join:" + newReadString); + } + } + } + } + //串口接收正确完整数据: + R = "R:" + readString; + if (isSensor) { + MyLog.sensor(R); + } else { + MyLog.controller(R); + } + device.setFailTimes(0); + return readString; + } else { + //串口接收错误数据 + R = "R,error:" + readString; + if (isSensor) { + MyLog.sensorError(R); + } else { + MyLog.controllerError(R); + } + } + } + } + Thread.sleep(100); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + if (StringUtils.isEmpty(readString)) { + R = "R:串口超时未接收数据"; + if (isSensor) { + MyLog.sensorError(R); + } else { + MyLog.controllerError(R); + } + } + //操作失败 + device.addFailTimes(); + return null; + } + + public boolean isStart() { + return isStart; + } + + public void setStart(boolean start) { + isStart = start; + } + + /** + * CRC校验 readString后4位是否正确 + * + * @param readString 读取到的数据 + * @return 校验结果 + */ + private boolean passCRC(String readString) { + try { + String crc = readString.substring(readString.length() - 4); + String data = readString.substring(0, readString.length() - 4); + String realCRC = Device.getCRC(data); + return crc.equals(realCRC); + } catch (Exception e) { + return false; + } + } +} + + diff --git a/app/src/main/java/com/example/iot_controlhost/MyApp.java b/app/src/main/java/com/example/iot_controlhost/MyApp.java new file mode 100644 index 0000000..1e5ed6a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/MyApp.java @@ -0,0 +1,155 @@ +package com.example.iot_controlhost; + +import android.app.Application; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.provider.Settings; +import android.view.Gravity; + +import com.example.iot_controlhost.model.DaoMaster; +import com.example.iot_controlhost.model.DaoSession; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.CrashHandlerUtil; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.mainboard.MyAPIContext; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.old.OldSet; +import com.example.iot_controlhost.utils.timer.ThemeTaskUtil; +import com.example.iot_controlhost.utils.timer.TimerTaskUtil; +import com.scwang.smart.refresh.footer.ClassicsFooter; +import com.scwang.smart.refresh.header.ClassicsHeader; +import com.scwang.smart.refresh.layout.SmartRefreshLayout; + +import org.xutils.x; + +import java.io.File; + +import es.dmoral.toasty.Toasty; +import timber.log.Timber; + +public class MyApp extends Application { + private static DaoSession mSession; + private static MyApp instance; + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + // 初始化网络请求地址 + Url.initUrl(); + // 初始化MMKV + MMKVUtil.init(); + // 初始化数据库 + initDb(); + // 初始化崩溃捕捉 + CrashHandlerUtil.getInstance().init(getApplicationContext()); + // 初始化日志库 + Timber.plant(new MyLog()); + Timber.plant(new UserLog()); + if (OldSet.isFirstTime()) { + OldSet.importOldSet(getApplicationContext()); + } + MMKVUtil.put(HardwareSetting.HOST_ID, Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)); + // 初始化亮暗主题 + initTheme(); + // 初始化网络请求库 + x.Ext.init(this); + // 初始化Toast + Toasty.Config + .getInstance() + .tintIcon(true) + .setTextSize(30) + .allowQueue(false) + .setGravity(Gravity.CENTER) + .supportDarkTheme(true) + .apply(); + // 初始化主板Api + MyAPIContext.getInstance().init(); + MyAPIContext.getInstance().feet(); + // 初始化调试任务 + initDebug(false); + // 初始化每日定时任务 + TimerTaskUtil.start(); + } + + + /** + * 在调试时使用的代码 + */ + private void initDebug(boolean isDebug) { + if (isDebug) { + //在新主机上测试用代码: + MMKVUtil.put(RoomSetting.AREA_CODE, "5f645faa"); + MMKVUtil.put(HardwareSetting.HOST_ID, "2af30b4c49d4b26b"); + MMKVUtil.put(HardwareSetting.IS_REGISTERED, false); + } + MyLog.test("(区域码)AREA_CODE: " + RoomSetting.getAreaCode()); + MyLog.test("(设备码)HOST_ID: " + HardwareSetting.getHostId()); + } + + static { + //设置全局的Header构建器 + SmartRefreshLayout.setDefaultRefreshHeaderCreator((context, layout) -> { +// layout.setPrimaryColorsId(R.color.md_theme_light_primary, R.color.md_theme_light_secondary, R.color.md_theme_light_tertiary);//全局设置主题颜色 + return new ClassicsHeader(context);//.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header + }); + //设置全局的Footer构建器 + SmartRefreshLayout.setDefaultRefreshFooterCreator((context, layout) -> new ClassicsFooter(context).setDrawableSize(20)); + } + + /** + * 连接数据库并创建会话 + */ + public void initDb() { + deleteDatabase(this, "lot.db"); + // 获取需要连接的数据库 + DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "lot240827.db"); + SQLiteDatabase db = devOpenHelper.getWritableDatabase(); + // 创建数据库连接 + DaoMaster daoMaster = new DaoMaster(db); + // 创建数据库会话 + mSession = daoMaster.newSession(); + } + + public static Context getAppContext() { + return instance.getApplicationContext(); + } + + // 删除数据库的方法 + public void deleteDatabase(Context context, String dbName) { + // 获取数据库文件的路径 + File dbFile = context.getDatabasePath(dbName); + + // 删除数据库文件 + if (dbFile.exists()) { + dbFile.delete(); + } + } + /** + * 初始化亮暗主题 + */ + private void initTheme() { + boolean isDark; + if (RoomSetting.getAutoLightNight()) { + isDark = ThemeTaskUtil.resetTask(); + } else { + isDark = RoomSetting.isDarkTheme(); + } + MyUtil.switchDarkMode(isDark); + } + + /** + * 供外接使用 + */ + public static DaoSession getDaoSession() { + return mSession; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java new file mode 100644 index 0000000..7bc4256 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/AutoControllerAdapter.java @@ -0,0 +1,130 @@ +package com.example.iot_controlhost.adapter; + +import android.content.Context; +import android.graphics.Color; +import android.transition.Fade; +import android.transition.TransitionManager; +import android.view.View; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemControllerAutoBinding; +import com.example.iot_controlhost.model.Result; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.UserLog; + +import java.util.List; + +import es.dmoral.toasty.Toasty; + +/** + * 手动控制继电器下的设备 + * 用于主界面显示 + * + * @Author:DuanKaiji + */ +public class AutoControllerAdapter extends BaseRecyclerAdapter { + Context mContext; + + public AutoControllerAdapter(Context context, List dataList, Class bindingClass) { + super(dataList, bindingClass); + mContext = context; + } + + + @Override + protected void bindData(ItemControllerAutoBinding binding, Relay device) { + TransitionManager.beginDelayedTransition(binding.layoutRoot, new Fade()); + if ("未配置".equals(device.getName())) { + binding.cardNo.setVisibility(View.VISIBLE); + binding.cardYes.setVisibility(View.GONE); + return; + } else { + binding.cardNo.setVisibility(View.GONE); + binding.cardYes.setVisibility(View.VISIBLE); + } + if (device.getName().equals(RoomController.fan.getName())) { + binding.tvInfo.setText(GasSensor.getQualityAir()); + binding.tvInfo.setTextColor(GasSensor.getQualityAirColor()); + } else { + binding.tvInfo.setText(null); + } + changeState(binding, device); + binding.tvController.setText(device.getName()); + if (AutoModelSet.isAutoHumidity() && RoomController.DEVICE_NAME[2].equals(device.getName())//加湿器 + || AutoModelSet.isAutoHumidity() && RoomController.DEVICE_NAME[16].equals(device.getName()) //除湿器 + || AutoModelSet.isAutoVentilator() && RoomController.DEVICE_NAME[12].equals(device.getName()) //换气扇 + || AutoModelSet.isAutoEven() && RoomController.DEVICE_NAME[7].equals(device.getName()) //匀风扇 + || AutoModelSet.isAutoDisinfect() && RoomController.DEVICE_NAME[8].equals(device.getName()) //消毒灯 + || !AutoModelSet.getAirExchangeMode().equals(SpinnerList.AIR_EXCHANGE_MODES[0]) && RoomController.DEVICE_NAME[13].equals(device.getName()) //新风 + ) { + binding.cardController.setOnClickListener(v -> Toasty.info(mContext, "该设备由自动控制系统接管中").show()); + } else { + binding.cardController.setOnClickListener(view -> { + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_operate)); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Result doInQueueThread() { + Result result = new Result(); + result.setOperateResult(device.setPowerSupply(!device.isPowerSupply())); + result.setState(device.isPowerSupply()); + return result; + } + + @Override + public void doInUIThread(Result result) { + if (result.isOperateResult()) { + changeState(binding, device); + Toasty.success(mContext, mContext.getString(R.string.operate_success)).show(); + UserLog.operate("手动:" + (result.isState() ? "开启" : "关闭") + device.getName()); + } else { + Toasty.error(mContext, mContext.getString(R.string.operate_fail)).show(); + UserLog.operateError("手动操作失败:" + (result.isState() ? "开启" : "关闭") + device.getName()); + } + DialogUtil.hideLoadingDialog(); + } + }); + }); + } + } + + private void changeState(ItemControllerAutoBinding binding, Relay device) { + if (device.isPowerSupply()) { + binding.tvState.setText("运行中"); + binding.ivController.setImageResource(device.getIconOpen()); + binding.ivPower.setBackground(mContext.getDrawable(R.drawable.bg_controller_open)); + binding.ivPower.setImageDrawable(mContext.getDrawable(R.mipmap.switch_on)); + binding.cardController.setBackgroundColor(Color.parseColor("#00BDF4")); + binding.ivController.setImageDrawable(mContext.getDrawable(device.getIconOpen())); + binding.tvState.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvController.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvPower.setTextColor(Color.parseColor("#FFFFFF")); + } else { + // 地暖在超过最大温度限制后会自动断电,此时状态显示为“高温保护”而非“未启动” + if(device.getName().equals(RoomSetting.getFloorName()) && RoomSensor.getFloorTemperature() > AutoModelSet.getAutoFlorMaxTemp()) { + binding.tvState.setText("高温保护中"); + } else { + binding.tvState.setText("未启动"); + } + binding.ivController.setImageResource(device.getIconClose()); + binding.cardController.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33DCE3E3" : "#F2F0F1")); + binding.ivPower.setBackground(mContext.getDrawable(R.drawable.bg_controller_close)); + binding.ivPower.setImageDrawable(mContext.getDrawable(R.mipmap.switch_off)); + binding.ivController.setImageDrawable(mContext.getDrawable(device.getIconClose())); + binding.tvState.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvController.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvPower.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + } + binding.tvPower.setVisibility(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) ? View.VISIBLE : View.GONE); + binding.tvPower.setText("实时功率:" + device.getPower() + "w"); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java new file mode 100644 index 0000000..4e83cd5 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/LogAdapter.java @@ -0,0 +1,33 @@ +package com.example.iot_controlhost.adapter; + +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemLogBinding; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.utils.global.RoomSetting; + +import java.util.List; + +/** + * 日志适配器 + * 用于查找日志弹窗 + * + * @Author:DuanKaiji + */ +public class LogAdapter extends BaseRecyclerAdapter { + + public LogAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + } + + @Override + protected void bindData(ItemLogBinding binding, Log data) { + int targetColorId = binding.tvMessage.getContext().getColor( + data.getLevel() == android.util.Log.ERROR ? R.color.red : RoomSetting.isDarkTheme() ? R.color.white : R.color.black); + binding.tvMessage.setTextColor(targetColorId); + binding.tvDate.setTextColor(targetColorId); + binding.tvMessage.setText(data.getMessage()); + binding.tvDate.setText(TimeUtils.date2String(data.getDatetime())); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java new file mode 100644 index 0000000..f6a2b45 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/ManualControllerAdapter.java @@ -0,0 +1,110 @@ +package com.example.iot_controlhost.adapter; + +import android.content.Context; +import android.graphics.Color; +import android.view.View; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemControllerBinding; +import com.example.iot_controlhost.model.Result; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.UserLog; + +import java.util.List; + +import es.dmoral.toasty.Toasty; + +/** + * 手动控制继电器下的设备 + * 用于主界面显示 + * + * @Author:DuanKaiji + */ +public class ManualControllerAdapter extends BaseRecyclerAdapter { + Context mContext; + + public ManualControllerAdapter(Context context, List dataList, Class bindingClass) { + super(dataList, bindingClass); + this.mContext = context; + } + + @Override + protected void bindData(ItemControllerBinding binding, Relay device) { + if (device.getName().equals(RoomController.fan.getName())) { + binding.tvInfo.setText(GasSensor.getQualityAir()); + binding.tvInfo.setTextColor(GasSensor.getQualityAirColor()); + } else { + binding.tvInfo.setText(null); + } + changeState(binding, device); + binding.tvController.setText(device.getName()); + binding.cardController.setOnClickListener(view -> { + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_operate)); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Result doInQueueThread() throws Exception { + Result result = new Result(); + result.setOperateResult(device.setPowerSupply(!device.isPowerSupply())); + result.setState(device.isPowerSupply()); + return result; + } + + @Override + public void doInUIThread(Result result) { + if (result.isOperateResult()) { + changeState(binding, device); + Toasty.success(mContext, mContext.getString(R.string.operate_success)).show(); + UserLog.operate("手动:" + (result.isState() ? "开启" : "关闭") + device.getName()); + } else { + Toasty.error(mContext, mContext.getString(R.string.operate_fail)).show(); + UserLog.operateError("手动操作失败:" + (result.isState() ? "开启" : "关闭") + device.getName()); + } + DialogUtil.hideLoadingDialog(); + } + }); + }); + } + + private void changeState(ItemControllerBinding binding, Relay device) { + if ("未配置".equals(device.getName())) { + binding.cardNo.setVisibility(View.VISIBLE); + binding.cardYes.setVisibility(View.GONE); + return; + } else { + binding.cardNo.setVisibility(View.GONE); + binding.cardYes.setVisibility(View.VISIBLE); + } + if (device.isPowerSupply()) { + binding.tvState.setText("运行中"); + binding.ivController.setImageResource(device.getIconOpen()); + binding.ivPower.setBackground(mContext.getDrawable(R.drawable.bg_controller_open)); + binding.ivPower.setImageDrawable(mContext.getDrawable(R.mipmap.switch_on)); + binding.cardController.setBackgroundColor(Color.parseColor("#00BDF4")); + binding.ivController.setImageDrawable(mContext.getDrawable(device.getIconOpen())); + binding.tvState.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvController.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvPower.setTextColor(Color.parseColor("#FFFFFF")); + } else { + binding.tvState.setText("未启动"); + binding.ivController.setImageResource(device.getIconClose()); + binding.cardController.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33DCE3E3" : "#F2F0F1")); + binding.ivPower.setBackground(mContext.getDrawable(R.drawable.bg_controller_close)); + binding.ivPower.setImageDrawable(mContext.getDrawable(R.mipmap.switch_off)); + binding.ivController.setImageDrawable(mContext.getDrawable(device.getIconClose())); + binding.tvState.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvController.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvPower.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + } + binding.tvPower.setVisibility(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) ? View.VISIBLE : View.GONE); + binding.tvPower.setText("实时功率:" + device.getPower() + "w"); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/MessageAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/MessageAdapter.java new file mode 100644 index 0000000..cd96cd8 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/MessageAdapter.java @@ -0,0 +1,46 @@ +package com.example.iot_controlhost.adapter; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.DialogMessageBinding; +import com.example.iot_controlhost.databinding.ItemMsgTitleBinding; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.utils.database.MessageReadUtil; +import com.example.iot_controlhost.utils.global.RoomSetting; + +import java.util.List; + +/** + * 日志适配器 + * 用于查找日志弹窗 + * + * @Author:DuanKaiji + */ +public class MessageAdapter extends BaseRecyclerAdapter { + + DialogMessageBinding dialogBinding; + + public MessageAdapter(List dataList, Class bindingClass, DialogMessageBinding binding) { + super(dataList, bindingClass); + this.dialogBinding = binding; + } + + @Override + protected void bindData(ItemMsgTitleBinding binding, Log data) { + int targetColorId = binding.tvTitle.getContext().getColor( + data.getLevel() == android.util.Log.ERROR ? R.color.red : RoomSetting.isDarkTheme() ? R.color.white : R.color.black); + binding.tvTitle.setTextColor(targetColorId); + // 如果未读 字体加粗 + binding.tvTitle.setTypeface(null, MessageReadUtil.INSTANCE.isRead(data.getId().toString()) ? android.graphics.Typeface.NORMAL : android.graphics.Typeface.BOLD); + binding.linearLayout6.setOnClickListener(v ->{ + // 点击时,显示日志内容 + dialogBinding.tvMsgTitle.setText(data.getTag()); + dialogBinding.tvMsgTime.setText(data.getDatetime().toString()); + dialogBinding.tvMessage.setText(data.getMessage()); + + // 标记为已读 + MessageReadUtil.INSTANCE.readMessage(data.getId().toString());// todo + binding.tvTitle.setTypeface(null, android.graphics.Typeface.NORMAL); + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/ModelsAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/ModelsAdapter.java new file mode 100644 index 0000000..6d99227 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/ModelsAdapter.java @@ -0,0 +1,53 @@ +package com.example.iot_controlhost.adapter; + +import android.util.SparseArray; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.example.iot_controlhost.base.BaseMode; +import com.example.iot_controlhost.ui.fragment.AutoFragment; +import com.example.iot_controlhost.ui.fragment.IntelligenceFragment; +import com.example.iot_controlhost.ui.fragment.ManualFragment; + +/** + * @Description: 主界面三大模式切换-适配器 + * @Author:DuanKaiji + */ +public class ModelsAdapter extends FragmentStateAdapter { + private static final SparseArray fragments = new SparseArray<>(); + + public ModelsAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + BaseMode fragment = null; + switch (position) { + case 0: + fragment = new ManualFragment(); + break; + case 1: + fragment = new AutoFragment(); + break; + case 2: + fragment = new IntelligenceFragment(); + break; + } + fragments.put(position, fragment); + return (Fragment) fragment; + } + + public static BaseMode getModeByPosition(int position) { + return fragments.get(position); + } + + @Override + public int getItemCount() { + return 3; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/SensorInfoAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/SensorInfoAdapter.java new file mode 100644 index 0000000..0c00549 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/SensorInfoAdapter.java @@ -0,0 +1,22 @@ +package com.example.iot_controlhost.adapter; + +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemSensorInfoBinding; +import com.example.iot_controlhost.model.sensor.SensorInfo; +import java.util.List; + +/** + * 传感器信息适配器 + */ +public class SensorInfoAdapter extends BaseRecyclerAdapter { + + public SensorInfoAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + } + + @Override + protected void bindData(ItemSensorInfoBinding binding, SensorInfo data) { + binding.tvSensorName.setText(data.getName()); + binding.tvSensorInfo.setText(data.getInfo()); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/SetAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/SetAdapter.java new file mode 100644 index 0000000..18aa291 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/SetAdapter.java @@ -0,0 +1,48 @@ +package com.example.iot_controlhost.adapter; + + +import android.view.View; +import android.widget.AdapterView; + +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemSetBinding; +import com.example.iot_controlhost.model.Set; + +import java.util.List; + +/** + * @Author:DuanKaiji + * @Date:2023-06-08/09:38 + * @Declaration:设置界面-单项 + */ +public class SetAdapter extends BaseRecyclerAdapter { + + public SetAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + } + + @Override + protected void bindData(ItemSetBinding binding, Set data) { + if(data.getOnClickListener()==null){ + binding.ivRight.setVisibility(View.INVISIBLE); + }else{ + binding.ivRight.setVisibility(View.VISIBLE); + } + //设置-普通显示主副内容点击事件 + binding.tvSetTitle.setText(data.getTitle()); + binding.tvSetDetail.setText(data.getDetail()); + binding.clSet.setOnClickListener(data.getOnClickListener()); + //设置-开关事件 + // 先设置为null,否则会触发事件 + binding.switcher.setOnCheckedChangeListener(null); + if (data.getOnCheckedChangeListener() != null) { + //开关事件 + binding.switcher.setChecked(data.isChecked()); + binding.switcher.setOnCheckedChangeListener(data.getOnCheckedChangeListener()); + binding.switcher.setVisibility(View.VISIBLE); + }else{ + binding.switcher.setVisibility(View.GONE); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/StageAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/StageAdapter.java new file mode 100644 index 0000000..577aaa6 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/StageAdapter.java @@ -0,0 +1,85 @@ +package com.example.iot_controlhost.adapter; + +import android.widget.TextView; + +import com.blankj.utilcode.util.GsonUtils; +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemBtnBinding; +import com.example.iot_controlhost.model.net.ControlMode; +import com.example.iot_controlhost.model.net.StageList; +import com.example.iot_controlhost.model.net.WorkStage; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.ui.dialog.WorkNewDialog; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.database.TempTHVDBManager; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.List; + +/** + * @Author:DuanKaiji + */ +public class StageAdapter extends BaseRecyclerAdapter { + + TextView tvStageCurrent; + TextView tvStageContent; + + public StageAdapter(List dataList, Class bindingClass, TextView tvStageCurrent, TextView tvStageContent) { + super(dataList, bindingClass); + this.tvStageCurrent = tvStageCurrent; + this.tvStageContent = tvStageContent; + } + + @Override + protected void bindData(ItemBtnBinding binding, StageList.Data data) { + binding.tv.setText(data.getName()); + binding.tv.setOnClickListener(v -> { + tvStageCurrent.setText(data.getName()); + refreshWorkContent(data); + }); + } + + private void refreshWorkContent(StageList.Data content) { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getStageDetail); + params.addParameter("feedingStageConfigId", content.getId()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(WorkStage model) { + String info = WorkNewDialog.getInfo(model, false); + TempTHVDBManager.INSTANCE.setStage(content.getName(), info); + tvStageContent.setText(info); + if (RoomSetting.getMode() != 1) { + // 如果不是自动模式,则切换为自动模式 + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3); + }else{ + // 刷新自动模式下的参数 + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + } + } + + @Override + public void error(Throwable e) { + tvStageContent.setText("获取失败"); + UserLog.taskError("作业列表获取失败" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/UvTaskAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/UvTaskAdapter.java new file mode 100644 index 0000000..190452a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/UvTaskAdapter.java @@ -0,0 +1,58 @@ +package com.example.iot_controlhost.adapter; + +import android.content.Context; +import android.view.View; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemUvTaskBinding; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; + +import java.util.List; + +/** + * @Author:DuanKaiji + * @Date:2023年11月27日 14:01:37 + * @Description:消毒任务适配器 + */ +public class UvTaskAdapter extends BaseRecyclerAdapter { + Context mContext; + + public UvTaskAdapter(List dataList, Class bindingClass, View tvTipNone) { + super(dataList, bindingClass, tvTipNone); + } + + public void setContext(Context mContext) { + this.mContext = mContext; + } + + @Override + protected void bindData(ItemUvTaskBinding binding, MyTask data) { + binding.tvStartTime.setText(data.getStartTimeHour() + ":" + data.getStartTimeMinute()); + binding.tvEndTime.setText(data.getEndTimeHour() + ":" + data.getEndTimeMinute()); + binding.btnDelete.setOnClickListener(view -> new ConfirmDialog(mContext, mContext.getString(R.string.warning), mContext.getString(R.string.ask_delete_plan), () -> { + //删除消毒任务 + UVTaskUtil.removeUvTask(data); + dataList.remove(data); + notifyDataSetChanged(); + }).show()); + } + + @Override + protected void bindData(ItemUvTaskBinding binding, MyTask data, int position) { + super.bindData(binding, data, position); + binding.tvId.setText(String.valueOf(position + 1)); + } + + @Override + public int getItemCount() { + if (dataList == null) { + return 0; + } else { + return dataList.size(); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/WeatherAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/WeatherAdapter.java new file mode 100644 index 0000000..2d5841e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/WeatherAdapter.java @@ -0,0 +1,29 @@ +package com.example.iot_controlhost.adapter; + +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemWeatherBinding; +import com.example.iot_controlhost.model.net.Weather; + +import java.util.List; + +/** + * 天气适配器 + * 用于查看天气 + * + * @Author:DuanKaiji + */ +public class WeatherAdapter extends BaseRecyclerAdapter { + + public WeatherAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + } + + @Override + protected void bindData(ItemWeatherBinding binding, Weather.DataDTO.CastsDTO data) { + binding.tvDate.setText(data.getDate()); + String day = data.getDayweather() + " " + data.getDaytemp() + "℃\n" + data.getDaypower() + " " + data.getDaywind() + " "; + binding.tvDay.setText(day); + String night = data.getNightweather() + " " + data.getNighttemp() + "℃\n" + data.getNightpower() + " " + data.getNightwind() + " "; + binding.tvNight.setText(night); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/WorkAdapter.java b/app/src/main/java/com/example/iot_controlhost/adapter/WorkAdapter.java new file mode 100644 index 0000000..1dfde0e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/WorkAdapter.java @@ -0,0 +1,79 @@ +package com.example.iot_controlhost.adapter; + +import android.widget.TextView; + +import com.example.iot_controlhost.base.BaseRecyclerAdapter; +import com.example.iot_controlhost.databinding.ItemBtnBinding; +import com.example.iot_controlhost.model.net.WorkList; +import com.example.iot_controlhost.model.net.WorkStage; +import com.example.iot_controlhost.ui.dialog.WorkNewDialog; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import org.w3c.dom.Text; +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.List; + + +/** + * @Author:DuanKaiji + */ +public class WorkAdapter extends BaseRecyclerAdapter { + + TextView tvWorkCurrent; + TextView tvWorkContent; + WorkAdapterInterface wai; + + public WorkAdapter(List dataList, Class bindingClass, TextView tvWorkCurrent, TextView tvWorkContent, WorkAdapterInterface wai) { + super(dataList, bindingClass); + this.tvWorkCurrent = tvWorkCurrent; + this.tvWorkContent = tvWorkContent; + this.wai = wai; + } + + @Override + protected void bindData(ItemBtnBinding binding, WorkList.Data data) { + binding.tv.setText(data.getName()); + binding.tv.setOnClickListener(v -> { + tvWorkCurrent.setText(data.getName()); + refreshWorkContent(data.getId()); + wai.startWork(data.getName()); + }); + } + + private void refreshWorkContent(String content) { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getWorkDetail); + params.addParameter("jobContentConfigId", content); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(WorkStage model) { + tvWorkContent.setText(WorkNewDialog.getInfo(model,true)); + if (RoomSetting.getMode() == 1) { + // 如果是自动模式,则刷新自动模式参数 + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + } + } + + @Override + public void error(Throwable e) { + tvWorkContent.setText("获取失败"); + UserLog.taskError("作业列表获取失败" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/adapter/WorkAdapterInterface.java b/app/src/main/java/com/example/iot_controlhost/adapter/WorkAdapterInterface.java new file mode 100644 index 0000000..2c58ca3 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/adapter/WorkAdapterInterface.java @@ -0,0 +1,5 @@ +package com.example.iot_controlhost.adapter; + +public interface WorkAdapterInterface { + void startWork(String content); +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseActivity.java b/app/src/main/java/com/example/iot_controlhost/base/BaseActivity.java new file mode 100644 index 0000000..acffcf2 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseActivity.java @@ -0,0 +1,232 @@ +package com.example.iot_controlhost.base; + +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.ViewDataBinding; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import es.dmoral.toasty.Toasty; +import me.jessyan.autosize.internal.CustomAdapt; + +/** + * Activity基类 + */ +public abstract class BaseActivity extends AppCompatActivity implements CustomAdapt, View.OnClickListener { + + public Presenter presenter; + public Binding binding; + public Context mContext; + /** + * 防抖 + */ + long lastClickTime; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + initTransition(); + super.onCreate(savedInstanceState); + mContext = this; + Type type = this.getClass().getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Class bindingClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + Class controllerClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; + try { + Method inflateMethod = bindingClass.getMethod("inflate", LayoutInflater.class); + binding = (Binding) inflateMethod.invoke(null, getLayoutInflater()); + presenter = new ViewModelProvider(this).get(controllerClass); +// presenter = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(controllerClass); + binding.setLifecycleOwner(this); + registerPresenter(presenter); + } catch (Exception e) { + MyLog.appError("BaseActivity初始化失败:" + e.getMessage()); + e.printStackTrace(); + } + setContentView(binding.getRoot()); + initView(); + initListener(); + setFullScreen(true); + } + } + + protected void registerPresenter(BasePresenter presenter) { + presenter.getIsLoadingLiveData().observe(this, new Observer() { + @Override + public void onChanged(String msg) { + if (StringUtils.isEmpty(msg)) { + DialogUtil.hideLoadingDialog(); + } else { + DialogUtil.showLoadingDialog(mContext, msg); + } + } + }); + presenter.getErrorTips().observe(this, new Observer() { + @Override + public void onChanged(String msg) { + errorTips(msg); + } + }); + presenter.getSuccessTips().observe(this, new Observer() { + @Override + public void onChanged(String msg) { + successTips(msg); + } + }); + } + + /** + * 开启加载中弹窗 + * 在IO线程中调用的 + */ + public void showLoadingDialogInIO(String msg) { + DialogUtil.showLoadingDialogInIO(mContext, msg); + } + + /** + * 开启加载中弹窗 + * 在IO线程中调用的 + */ + public void showLoadingDialog(String msg) { + DialogUtil.showLoadingDialog(mContext, msg); + } + + /** + * 开启加载中弹窗 + * 在IO线程中调用的 + */ + public void hideLoadingDialog() { + DialogUtil.hideLoadingDialog(); + } + + public void setFullScreen(boolean isFullScreen) { + if (isFullScreen) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // Android 11 及以上 (API 30+) + getWindow().setDecorFitsSystemWindows(false); + getWindow().getInsetsController().hide(WindowInsets.Type.navigationBars()); + } else { + // Android 7.1 - 10 (API 25 - 29) + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // 隐藏导航栏 + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | // 沉浸模式 + View.SYSTEM_UI_FLAG_FULLSCREEN // 全屏 + ); + } + } else { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } + } + + /** + * 初始化转场动画 + */ + protected void initTransition() { + } + + /** + * 初始化界面 + */ + public abstract void initView(); + + /** + * 初始化监听器,可选 + */ + public void initListener() { + } + + @Override + public final void onClick(View view) { + //防抖拦截 + long now = System.currentTimeMillis(); + if (now - lastClickTime > Variable.CLICK_INTERVAL) { + myClick(view); + } + lastClickTime = now; + } + + /** + * 点击事件 + * 会被防抖拦截,需要的子类可以按需重写,可选 + */ + public void myClick(View view) { + } + + /** + * 屏幕自适应 + */ + @Override + public boolean isBaseOnWidth() { + return true; + } + + @Override + public float getSizeInDp() { + return 0; + } + + /** + * 通用成功提示 + */ + public void successTips() { + Toasty.success(mContext, getString(R.string.operate_success)).show(); + } + + public void successTips(String msg) { + if (StringUtils.isEmpty(msg)) { + successTips(); + } else { + Toasty.success(mContext, msg).show(); + } + + } + + /** + * 通用错误提示 + */ + public void errorTips() { + Toasty.error(mContext, getString(R.string.operate_fail)).show(); + } + + public void errorTips(String msg) { + if (StringUtils.isEmpty(msg)) { + errorTips(); + } else { + Toasty.error(mContext, msg).show(); + } + } + + protected void fillDataInText(TextView textView, T data) { + if (data == null) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Double && (Double) data == 0) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Integer && (Integer) data == 0) { + textView.setText(getText(R.string.none)); + } else { + textView.setText(data.toString()); + } + } + +} + diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java b/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java new file mode 100644 index 0000000..c3ddc8a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseDialog.java @@ -0,0 +1,103 @@ +package com.example.iot_controlhost.base; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.databinding.ViewDataBinding; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 弹窗基类 + * @Author DuanKaiji + * @CreateTime 2024年02月29日 14:49 + */ +public abstract class BaseDialog extends Dialog { + protected Binding binding; + protected Context mContext; + + public BaseDialog(@NonNull Context context) { + this(context, false); + } + + /** + * 构造函数 + * + * @param context 上下文 + * @param needInput 是否需要输入,即软键盘弹出 + */ + public BaseDialog(@NonNull Context context, boolean needInput) { + super(context, R.style.MyDialogTheme); + mContext = context; + Type type = this.getClass().getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Class bindingClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + try { + Method inflateMethod = bindingClass.getMethod("inflate", LayoutInflater.class); + binding = (Binding) inflateMethod.invoke(null, getLayoutInflater()); + setContentView(binding.getRoot()); + findViewById(R.id.iv_dialog_close).setOnClickListener(view -> dismiss()); + getWindow().setGravity(Gravity.CENTER); + if (!needInput) { + getWindow().setAttributes(getAttributes(this)); + } + } catch (Exception e) { + MyLog.appError("BaseDialog初始化失败:" + e.getMessage()); + e.printStackTrace(); + } + } + } + + + protected void successTips(String msg) { + Toasty.success(mContext, msg).show(); + } + + protected void errorTips(String msg) { + Toasty.error(mContext, msg).show(); + } + + protected void infoTips(String msg) { + Toasty.info(mContext, msg).show(); + } + + + public interface CustomString { + /** + * @param selString 选中的项 + */ + void listener(String selString); + } + + /** + * 获得弹窗属性设置 + * 全屏弹窗,但无法使用输入法 + */ + public static WindowManager.LayoutParams getAttributes(Dialog dialog) { + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.copyFrom(dialog.getWindow().getAttributes()); + layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.CENTER; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; + return layoutParams; + } + + protected String getString(int resId) { + return mContext.getString(resId); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseFragment.java b/app/src/main/java/com/example/iot_controlhost/base/BaseFragment.java new file mode 100644 index 0000000..b9201c5 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseFragment.java @@ -0,0 +1,193 @@ +package com.example.iot_controlhost.base; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.ViewDataBinding; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.global.Variable; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import es.dmoral.toasty.Toasty; +import me.jessyan.autosize.internal.CustomAdapt; + +/** + * Fragment基类 + * + * @Author:DuanKaiji + */ +public abstract class BaseFragment extends Fragment implements CustomAdapt, View.OnClickListener { + + protected Binding binding; + protected Presenter presenter; + protected Context mContext; + /** + * 防抖 + */ + long lastClickTime; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Type type = getClass().getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Class bindingClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + Class presenterClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; + try { + Method inflateMethod = bindingClass.getMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class); + binding = (Binding) inflateMethod.invoke(null, inflater, container, false); + presenter = new ViewModelProvider(this).get(presenterClass); +// presenter = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication())).get(presenterClass); + binding.setLifecycleOwner(getViewLifecycleOwner()); + } catch (Exception e) { + e.printStackTrace(); + } + registerPresenter(presenter); + } + return binding.getRoot(); + } + + protected void registerPresenter(BasePresenter presenter) { + presenter.getIsLoadingLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String msg) { + if (StringUtils.isEmpty(msg)) { + DialogUtil.hideLoadingDialog(); + } else { + DialogUtil.showLoadingDialog(mContext, msg); + } + } + }); + presenter.getErrorTips().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String msg) { + error(msg); + } + }); + presenter.getSuccessTips().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String msg) { + success(msg); + } + }); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mContext = getActivity(); + init(); + initListener(); + } + + /** + * 初始化界面 + */ + public abstract void init(); + + /** + * 初始化监听器 + */ + public void initListener() { + } + + @Override + public final void onClick(View view) { + //防抖拦截 + long now = System.currentTimeMillis(); + if (now - lastClickTime > Variable.CLICK_INTERVAL) { + myClick(view); + } + lastClickTime = now; + } + + /** + * 点击事件 + * 会被防抖拦截 + * + * @param view + */ + public void myClick(View view) { + } + + @Override + public boolean isBaseOnWidth() { + return true; + } + + @Override + public float getSizeInDp() { + return 0; + } + + protected void fillDataInText(TextView textView, T data) { + if (data == null) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Double && (Double) data == 0) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Integer && (Integer) data == 0) { + textView.setText(getText(R.string.none)); + } else { + textView.setText(data.toString()); + } + } + + /** + * 通用成功提示 + */ + public void success() { + Toasty.success(mContext, getString(R.string.operate_success)).show(); + } + + public void success(String msg) { + if (StringUtils.isEmpty(msg)) { + success(); + } else { + Toasty.success(mContext, msg).show(); + } + } + + /** + * 通用错误提示 + */ + public void error() { + Toasty.error(mContext, getString(R.string.operate_fail)).show(); + } + + public void error(String msg) { + if (StringUtils.isEmpty(msg)) { + error(); + } else { + Toasty.error(mContext, msg).show(); + } + } + + /** + * 开启加载中弹窗 + */ + public void showLoadingDialog(String msg) { + DialogUtil.showLoadingDialog(mContext, msg); + } + + /** + * 关闭加载中弹窗 + */ + public void hideLoadingDialog() { + DialogUtil.hideLoadingDialog(); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseMode.java b/app/src/main/java/com/example/iot_controlhost/base/BaseMode.java new file mode 100644 index 0000000..00f9074 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseMode.java @@ -0,0 +1,19 @@ +package com.example.iot_controlhost.base; + +/** + * @Description 不同模式的必须方法 + * @Author DuanKaiji + * @CreateTime 2023年11月01日 15:27:18 + */ +public interface BaseMode { + + /** + * 初始化该模式的显示(在UI线程) + */ + void initMode(); + + /** + * 退出时使用(在IO线程) + */ + void stopMode(); +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/BasePresenter.java b/app/src/main/java/com/example/iot_controlhost/base/BasePresenter.java new file mode 100644 index 0000000..c887dab --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BasePresenter.java @@ -0,0 +1,77 @@ +package com.example.iot_controlhost.base; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.MutableLiveData; + +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description ViewModel 逻辑层 + * @Author DuanKaiji + * @CreateTime 2024年01月17日 09:32 + */ +public abstract class BasePresenter extends AndroidViewModel { + + private MutableLiveData isLoadingLiveData = new MutableLiveData<>(); + private MutableLiveData errorTips = new MutableLiveData<>(); + private MutableLiveData successTips = new MutableLiveData<>(); + protected PollingTask pollingTask; + public BasePresenter(@NonNull Application application) { + super(application); + pollingTask = PollingTask.getInstance(getClass().getSimpleName()); // 使用类名作为ID + } + + public MutableLiveData getIsLoadingLiveData() { + return isLoadingLiveData; + } + + public MutableLiveData getErrorTips() { + return errorTips; + } + + public MutableLiveData getSuccessTips() { + return successTips; + } + + public void showLoadingDialog(String content) { + isLoadingLiveData.setValue(content); + } + + public void showLoadingDialogInIO(String content) { + isLoadingLiveData.postValue(content); + } + + public void hideLoadingDialog() { + isLoadingLiveData.setValue(null); + } + + public void errorTips() { + errorTips(null); + } + + public void errorTips(String msg) { + errorTips.setValue(msg); + } + + public void successTips() { + successTips(null); + } + + public void successTips(String msg) { + successTips.setValue(msg); + } + + public String getString(int id) { + return getApplication().getString(id); + } + + @Override + protected void onCleared() { + super.onCleared(); + pollingTask.stopAllPollingTasks(); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseQueue.java b/app/src/main/java/com/example/iot_controlhost/base/BaseQueue.java new file mode 100644 index 0000000..b173572 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseQueue.java @@ -0,0 +1,32 @@ +package com.example.iot_controlhost.base; + +/** + * @Description 基本队列类 + * @Author DuanKaiji + * @CreateTime 2023年11月16日 14:02:48 + */ +public abstract class BaseQueue implements Runnable { + + /** + * 任务优先级 默认3 + * 数字越低,优先级越高 + */ + private int priority; + + public BaseQueue() { + this.priority = 3; + } + + public BaseQueue(int priority) { + this.priority = priority; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseRecyclerAdapter.java b/app/src/main/java/com/example/iot_controlhost/base/BaseRecyclerAdapter.java new file mode 100644 index 0000000..44ec611 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseRecyclerAdapter.java @@ -0,0 +1,134 @@ +package com.example.iot_controlhost.base; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * RecyclerAdapter基类 + * + * @param 数据-类型 + * @param 布局-类型 + */ +public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter.BaseViewHolder> { + + View tvTipNone; + + protected List dataList; + private Class bindingClass; + + /** + * 构造方法,必须重写此方法 + * + * @param dataList 数据list + * @param bindingClass 布局类,例如ActivityMainBinding.class + */ + public BaseRecyclerAdapter(List dataList, Class bindingClass) { + this.dataList = dataList; + this.bindingClass = bindingClass; + } + + public BaseRecyclerAdapter(List dataList, Class bindingClass, View tvTipNone) { + this.dataList = dataList; + this.bindingClass = bindingClass; + this.tvTipNone = tvTipNone; + if (dataList != null && dataList.size() > 0) { + tvTipNone.setVisibility(View.GONE); + } else { + tvTipNone.setVisibility(View.VISIBLE); + } + } + + @NonNull + @Override + public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + try { + Method inflateMethod = bindingClass.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class); + B binding = (B) inflateMethod.invoke(null, LayoutInflater.from(parent.getContext()), parent, false); + return new BaseViewHolder(binding); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to inflate binding class: " + bindingClass.getSimpleName()); + } + } + + @Override + public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) { + if (holder.binding != null) { + T data = dataList.get(position); + bindData(holder.binding, data, position); + } + } + + @Override + public int getItemCount() { + return dataList == null ? 0 : dataList.size(); + } + + /** + * 快捷更新RecycleView数据 + * + * @param dataList 数据 + */ + public void setDataList(List dataList) { + RxJavaUtils.doInUIThread(new RxUITask(null) { + @Override + public void doInUIThread(Object o) { + if (tvTipNone != null) { + if (dataList != null && dataList.size() > 0) { + tvTipNone.setVisibility(View.GONE); + } else { + tvTipNone.setVisibility(View.VISIBLE); + } + } + BaseRecyclerAdapter.this.dataList = dataList; + notifyDataSetChanged(); + } + }); + } + + public void addDataList(List dataList) { + this.dataList.addAll(dataList); + //只刷新增加新数据 + notifyItemRangeInserted(this.dataList.size() - dataList.size(), dataList.size()); + } + + /** + * 手动实现具体数据与布局的绑定 + * + * @param binding 布局 + * @param data 数据 + */ + protected abstract void bindData(B binding, T data); + + /** + * 有需要位置信息的,可以重写该方法 + * + * @param binding 布局 + * @param data 数据 + * @param position 位置 + */ + protected void bindData(B binding, T data, int position) { + bindData(binding, data); + } + + public class BaseViewHolder extends RecyclerView.ViewHolder { + public B binding; + + public BaseViewHolder(B binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/base/BaseViewModel.kt b/app/src/main/java/com/example/iot_controlhost/base/BaseViewModel.kt new file mode 100644 index 0000000..d6aa005 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/BaseViewModel.kt @@ -0,0 +1,116 @@ +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 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 doInIoThread( + loadingTips: String = "正在加载中", + showDialog: Boolean = true, + onError: (Throwable) -> Unit = { }, + doInIO: suspend () -> T, + ) { + doInIoThreadThenUI(loadingTips, showDialog, onError, doInIO) { } + } + + fun 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) + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/base/IRxIOTask.java b/app/src/main/java/com/example/iot_controlhost/base/IRxIOTask.java new file mode 100644 index 0000000..0557b21 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/IRxIOTask.java @@ -0,0 +1,11 @@ +package com.example.iot_controlhost.base; + +public interface IRxIOTask { + + /** + * 在队列线程中执行 + * + * @return T 任务执行的出参 + */ + T doInQueueThread() throws Exception; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/base/SetAddress.java b/app/src/main/java/com/example/iot_controlhost/base/SetAddress.java new file mode 100644 index 0000000..e0f0441 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/base/SetAddress.java @@ -0,0 +1,49 @@ +package com.example.iot_controlhost.base; + +/** + * @Description 调试设备用 + * @Author DuanKaiji + * @CreateTime 2024年03月08日 10:11 + */ +public interface SetAddress { + + /** + * 设置设备 + * 地址A + * 为地址B、波特率C + * + * @param model 型号 + * @param oldAddress 旧地址 (设置所有设备,则此项为“FF”) + * @param newAddress 新地址 + * @return 是否设置成功 + */ + boolean setAddressToNewAddress(String model, String oldAddress,String oldBaud, String newAddress); + /** + * 设置设备 + * 地址A + * 为地址B、波特率C + * + * @param model 型号 + * @param oldAddress 旧地址 (设置所有设备,则此项为“FF”) + * @param newBaud 新波特率 + * @return 是否设置成功 + */ + boolean setBaudToNewBaud(String model, String oldAddress, String newBaud); + + /** + * 测试当前设备是否能返回信息 + * + * @param deviceModel 测试的设备型号 + * @param deviceAddress 测试的设备地址 + * @return 测试指令发送成功与否 + */ + boolean test(String deviceModel, String deviceAddress); + + /** + * 查看是否能连接 + * + * @return 测试后的信息 + */ + String getTestState(); + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/AITHMode.java b/app/src/main/java/com/example/iot_controlhost/model/AITHMode.java new file mode 100644 index 0000000..e22ed10 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/AITHMode.java @@ -0,0 +1,124 @@ +package com.example.iot_controlhost.model; + +import com.google.gson.annotations.SerializedName; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; + +/** + * 智能模式温湿度模板 + * 数据库 + */ +@Entity +public class AITHMode { + @Id(autoincrement = true) + private Long id; + @SerializedName("Name") + private String name; + @SerializedName("Sort") + private int sort; + @SerializedName("ExpectedDays") + private double expectedDays; + @SerializedName("ExpectedHours") + private double expectedHours; + @SerializedName("TargetTemp") + private double targetTemp; + @SerializedName("TargetHumi") + private double targetHumi; + @SerializedName("TargetVentOpen") + private int targetVentOpen; + @SerializedName("TargetVentClose") + private int targetVentClose; + + @Generated(hash = 653503392) + public AITHMode(Long id, String name, int sort, double expectedDays, + double expectedHours, double targetTemp, double targetHumi, + int targetVentOpen, int targetVentClose) { + this.id = id; + this.name = name; + this.sort = sort; + this.expectedDays = expectedDays; + this.expectedHours = expectedHours; + this.targetTemp = targetTemp; + this.targetHumi = targetHumi; + this.targetVentOpen = targetVentOpen; + this.targetVentClose = targetVentClose; + } + + @Generated(hash = 617072932) + public AITHMode() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSort() { + return sort; + } + + public void setSort(int sort) { + this.sort = sort; + } + + public double getExpectedDays() { + return expectedDays; + } + + public void setExpectedDays(double expectedDays) { + this.expectedDays = expectedDays; + } + + public double getExpectedHours() { + return expectedHours; + } + + public void setExpectedHours(double expectedHours) { + this.expectedHours = expectedHours; + } + + public double getTargetTemp() { + return targetTemp; + } + + public void setTargetTemp(double targetTemp) { + this.targetTemp = targetTemp; + } + + public double getTargetHumi() { + return targetHumi; + } + + public void setTargetHumi(double targetHumi) { + this.targetHumi = targetHumi; + } + + public int getTargetVentOpen() { + return targetVentOpen; + } + + public void setTargetVentOpen(int targetVentOpen) { + this.targetVentOpen = targetVentOpen; + } + + public int getTargetVentClose() { + return targetVentClose; + } + + public void setTargetVentClose(int targetVentClose) { + this.targetVentClose = targetVentClose; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/Device.java b/app/src/main/java/com/example/iot_controlhost/model/Device.java new file mode 100644 index 0000000..41b8ffd --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/Device.java @@ -0,0 +1,226 @@ +package com.example.iot_controlhost.model; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.controller.Controller; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.nio.ByteBuffer; + +import android_serialport_api.SerialPortUtil; + +/** + * 各种硬件设备 + */ +public abstract class Device { + + public static final String MY_CONTROLLER_MODEL = "控制器类型"; + /** + * 控制器型号 + */ + public static String[] CONTROLLER_MODEL = {"装配式控制器"/*, "集成式控制器"*/}; + /** + * 设备名称 + */ + protected String name; + /** + * 设备型号 + */ + public static final String MODEL = "型号"; + /** + * 设备地址 + */ + public static final String ADDRESS = "地址"; + /** + * 设备是否启用 + */ + public static final String ENABLE = "启用"; + /** + * 操作失败次数 + */ + private int failTimes; + + public Device(String name) { + this.name = name; + //设置默认型号 + if (StringUtils.isEmpty(getModel())) { + setModel(getModels()[0]); + } + } + + /** + * 返回设备基础信息 + */ + @Override + public String toString() { + return !isEnable() ? "未启用" + : (("型号:" + getModel()) + "\t地址:" + (isIntegratedController() ? HardwareSetting.getCommonAddress() : getAddress())); + } + + /** + * 是否可用 + * isAvailable()区别于isEnable(),后者为人工设置可用于否,而前者为配置是否得当,可用于否 + */ + public boolean isAvailable() { + if (!isIntegratedController() && StringUtils.isEmpty(getAddress()) //装配式控制器 判断地址为空 + || isIntegratedController() && StringUtils.isEmpty(HardwareSetting.getCommonAddress())) {//集成式控制器 判断地址为空 + //地址为空也视为不可用 + return false; + } + return isEnable(); + } + + /** + * 获得型号 + */ + public abstract String[] getModels(); + + public int addFailTimes() { + int curTimes = getFailTimes(); + MMKVUtil.put(name + "failTimes", ++curTimes); + if (this instanceof Controller) { + MyLog.controllerError("控制器:" + name + "已连续操作失败:" + curTimes + "次"); + } else if (this instanceof Sensor) { + MyLog.sensorError("传感器:" + name + "已连续读取失败:" + curTimes + "次"); + } + return curTimes; + } + + public void clearSetting() { + MMKVUtil.remove(name + MODEL); + MMKVUtil.remove(name + ADDRESS); + MMKVUtil.remove(name + ENABLE); + MMKVUtil.remove(name + "failTimes"); + } + + /** + * 设置该设备错误次数 + * 只有在操作成功后或发送警报后才会置零 + */ + public void setFailTimes(int failTimes) { + MMKVUtil.put(name + "failTimes", failTimes); + } + + public boolean isEnable() { + return MMKVUtil.get(name + ENABLE, false); + } + + public void setEnable(boolean enable) { + MyLog.test("设置" + name + "设备是否启用:" + enable); + MMKVUtil.put(name + ENABLE, enable); + } + + public int getFailTimes() { + return MMKVUtil.get(name + "failTimes", 0); + } + + public static String getControllerModel() { + return MMKVUtil.get(MY_CONTROLLER_MODEL, CONTROLLER_MODEL[0]); + } + + /** + * 是否是集成式控制器 + * + * @return true:集成式控制器 false:装配式控制器 2023年11月14日 10:12:48 + */ + public static boolean isIntegratedController() { + return !getControllerModel().equals(CONTROLLER_MODEL[0]); + } + + public static void setControllerModel(String model) { + MMKVUtil.put(MY_CONTROLLER_MODEL, model); + } + + public void setAddress(String address) { + MMKVUtil.put(name + ADDRESS, address); + } + + public String getAddress() { + return MMKVUtil.get(name + ADDRESS, SpinnerList.ADDRESS[0]); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModel() { + return MMKVUtil.get(name + MODEL, getModels()[0]); + } + + public void setModel(String model) { + MMKVUtil.put(name + MODEL, model); + } + + public static String getCRC(String hex) { + try { + byte[] bytes; + //十六进制字符串转byte[] + if (hex == null) { + bytes = new byte[]{}; + } else { + // 奇数位补0 + if (hex.length() % 2 != 0) { + hex = "0" + hex; + } + + int length = hex.length(); + ByteBuffer buffer = ByteBuffer.allocate(length / 2); + for (int i = 0; i < length; i++) { + String hexStr = hex.charAt(i) + ""; + i++; + hexStr += hex.charAt(i); + byte b = (byte) Integer.parseInt(hexStr, 16); + buffer.put(b); + } + bytes = buffer.array(); + } + int CRC = 0x0000ffff; + int POLYNOMIAL = 0x0000a001; + int i, j; + for (i = 0; i < bytes.length; i++) { + CRC ^= ((int) bytes[i] & 0x000000ff); + for (j = 0; j < 8; j++) { + if ((CRC & 0x00000001) != 0) { + CRC >>= 1; + CRC ^= POLYNOMIAL; + } else { + CRC >>= 1; + } + } + } + String crc = Integer.toHexString(CRC); + if (crc.length() == 2) { + crc = "00" + crc; + } else if (crc.length() == 3) { + crc = "0" + crc; + } + crc = crc.substring(2, 4) + crc.substring(0, 2); + return crc.toUpperCase(); + } catch (Exception e) { + return "0000"; + } + } + + /** + * 按照指定长度截取字符串成一个数组 + */ + public String[] substring(String str, int length) { + if(str == null ){ + return new String[0]; + } + String[] result = new String[str.length() / length]; + int beg = 0; + for (int i = 0; i < result.length; i++) { + result[i] = str.substring(beg, beg + length); + beg = beg + length; + } + return result; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/DeviceErrorInfo.java b/app/src/main/java/com/example/iot_controlhost/model/DeviceErrorInfo.java new file mode 100644 index 0000000..d714767 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/DeviceErrorInfo.java @@ -0,0 +1,186 @@ +package com.example.iot_controlhost.model; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.PublicNetRequest; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +public class DeviceErrorInfo { + + /** + * 一级-出错次数-则弹窗+微信 + */ + private static final int errorCheckTimes1 = 3; + /** + * 二级-出错次数-则弹窗+微信+语音 + */ + public static final int errorCheckTimes2 = 6; + /** + * 设备 + */ + private String name; + /** + * 值-上次 + */ + private double value; + /** + * 开关状态-上次 + */ + private boolean open; + /** + * 操作类型-上次 + */ + private int operateType; + /** + * 出错次数 + */ + private int errorTimes; + /** + * 错误信息(如果有) + */ + private String errorMessage; + /** + * 错误信息(不需要时间) + */ + private String errorMessageNoTime; + /** + * 警告图标样式 + */ + private int resId; + + public DeviceErrorInfo(String name, double value) { + this.name = name; + this.value = value; + this.open = false; + this.operateType = 1; + errorTimes = 0; + this.resId = R.mipmap.warning_other; + } + + public DeviceErrorInfo(String name, double value, int resId) { + this(name, value); + this.resId = resId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getValue() { + return value; + } + + public void setValue(double value) { + this.value = value; + } + + public boolean isOpen() { + return open; + } + + public void setOpen(boolean open) { + this.open = open; + } + + public int getErrorTimes() { + return errorTimes; + } + + public int getResId() { + return resId; + } + + public void setResId(int resId) { + this.resId = resId; + } + + public void setErrorTimes(int errorTimes) { + this.errorTimes = errorTimes; + } + + /** + * 检查是否需要实施报警措施 + * + */ + public void checkNotice() { + checkNotice(3); + } + + /** + * 检查是否需要实施报警措施 + * @param noticeType 1数据采集故障 2设备操作故障 3设备疑似故障 + */ + public void checkNotice(int noticeType) { + if (StringUtils.isEmpty(getErrorMessage())) { + //已正常 + clearErrorTimes(); + RxBusUtils.get().post(RxTag.MAIN_WARNING, this); + } else { + errorTimes++; + MyLog.warningError(getName() + getErrorTimes() + "次-" + getErrorMessage()); + // 只有在一级和二级次数允许时 才预警 + if (getErrorTimes() == errorCheckTimes1 || getErrorTimes() == errorCheckTimes2) { + // 弹窗 + sentDialog(); + // 公众号提示 + int noticeTypeCode = 0; + if (getErrorTimes() == errorCheckTimes2) { + // 语音通知 + noticeTypeCode = noticeType; + //重新计数,但不删除错误信息,即界面仍会显示错误信息, + this.errorTimes = 0; + } + PublicNetRequest.AnomalyNotice(getName(), noticeTypeCode, 3, getErrorMessageNoTime()); + } else { + MyLog.warning(getName() + "错误次数:" + getErrorTimes() + "次,暂不符合预警触发条件"); + } + } + } + + /** + * 显示弹窗 + */ + private void sentDialog() { + UserLog.operateError(getErrorMessage()); + RxBusUtils.get().post(RxTag.MAIN_WARNING, this); + } + + public void clearErrorTimes() { + setErrorMessage(null); + this.errorTimes = 0; + } + + public int getOperateType() { + return operateType; + } + + public void setOperateType(int operateType) { + this.operateType = operateType; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = StringUtils.isEmpty(errorMessage) ? errorMessage : MyUtil.getDateTime() + "\n" + errorMessage; + setErrorMessageNoTime(errorMessage); + } + + public String getErrorMessageNoTime() { + return errorMessageNoTime; + } + + public void setErrorMessageNoTime(String errorMessageNoTime) { + this.errorMessageNoTime = errorMessageNoTime; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/EnergyRecord.java b/app/src/main/java/com/example/iot_controlhost/model/EnergyRecord.java new file mode 100644 index 0000000..3148912 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/EnergyRecord.java @@ -0,0 +1,57 @@ +package com.example.iot_controlhost.model; + +import androidx.annotation.NonNull; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; + +import java.util.Date; + +@Entity +public class EnergyRecord { + /** + * 唯一Id + */ + @Id(autoincrement = true) + private Long id; + /** + * 能耗值 + */ + @NonNull + Double energy; + /** + * 记录时间 + */ + @NonNull + Date datetime; + @Generated(hash = 1248913969) + public EnergyRecord(Long id, @NonNull Double energy, @NonNull Date datetime) { + this.id = id; + this.energy = energy; + this.datetime = datetime; + } + @Generated(hash = 496893221) + public EnergyRecord() { + } + public Long getId() { + return this.id; + } + public void setId(Long id) { + this.id = id; + } + public Double getEnergy() { + return this.energy; + } + public void setEnergy(Double energy) { + this.energy = energy; + } + public Date getDatetime() { + return this.datetime; + } + public void setDatetime(Date datetime) { + this.datetime = datetime; + } + + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/Log.java b/app/src/main/java/com/example/iot_controlhost/model/Log.java new file mode 100644 index 0000000..587f76c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/Log.java @@ -0,0 +1,99 @@ +package com.example.iot_controlhost.model; + +import androidx.annotation.NonNull; +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; + +import java.util.Date; + +@Entity +public class Log { + + /** + * 日志唯一Id + */ + @Id(autoincrement = true) + private Long id; + /** + * 标签/类型 + */ + @NonNull + String tag; + /** + * 级别 + */ + @NonNull + int level; + /** + * 产生时间 + */ + @NonNull + Date datetime; + /** + * 日志内容 + */ + @NonNull + String message; + + @Generated(hash = 1364647056) + public Log() { + } + + public Log(@NonNull String tag, int level, @NonNull Date datetime, @NonNull String message) { + this.tag = tag; + this.level = level; + this.datetime = datetime; + this.message = message; + } + + @Generated(hash = 1896824052) + public Log(Long id, @NonNull String tag, int level, @NonNull Date datetime, @NonNull String message) { + this.id = id; + this.tag = tag; + this.level = level; + this.datetime = datetime; + this.message = message; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTag() { + return this.tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public int getLevel() { + return this.level; + } + + public void setLevel(int level) { + this.level = level; + } + + public Date getDatetime() { + return this.datetime; + } + + public void setDatetime(Date datetime) { + this.datetime = datetime; + } + + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/MyTask.java b/app/src/main/java/com/example/iot_controlhost/model/MyTask.java new file mode 100644 index 0000000..572b796 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/MyTask.java @@ -0,0 +1,123 @@ +package com.example.iot_controlhost.model; + +import java.util.Objects; +import java.util.Random; + +/** + * 定时消毒任务 + */ +public class MyTask implements Comparable { + /** + * 任务Id 正为开启,负为关闭 + */ + private int id; + /** + * 执行设备 + */ + private int deviceType; + /** + * 开始时间 小时 + */ + private int startTimeHour; + /** + * 开始时间 分钟 + */ + private int startTimeMinute; + /** + * 结束时间 小时 + */ + private int endTimeHour; + /** + * 结束时间 分钟 + */ + private int endTimeMinute; + + public MyTask() { + } + + public MyTask(int startTimeHour, int startTimeMinute, int endTimeHour, int endTimeMinute) { + this.id = new Random(System.currentTimeMillis()).nextInt(); + this.startTimeHour = startTimeHour; + this.startTimeMinute = startTimeMinute; + this.endTimeHour = endTimeHour; + this.endTimeMinute = endTimeMinute; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MyTask other = (MyTask) o; + return id == other.getId(); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getDeviceType() { + return deviceType; + } + + public void setDeviceType(int deviceType) { + this.deviceType = deviceType; + } + + public int getStartTimeHour() { + return startTimeHour; + } + + public void setStartTimeHour(int startTimeHour) { + this.startTimeHour = startTimeHour; + } + + public int getStartTimeMinute() { + return startTimeMinute; + } + + public void setStartTimeMinute(int startTimeMinute) { + this.startTimeMinute = startTimeMinute; + } + + public int getEndTimeHour() { + return endTimeHour; + } + + public void setEndTimeHour(int endTimeHour) { + this.endTimeHour = endTimeHour; + } + + public int getEndTimeMinute() { + return endTimeMinute; + } + + public void setEndTimeMinute(int endTimeMinute) { + this.endTimeMinute = endTimeMinute; + } + + + @Override + public int compareTo(MyTask myTask) { + if (startTimeHour == myTask.startTimeHour && startTimeMinute == myTask.startTimeMinute) { + return 0; + } else if (startTimeHour > myTask.startTimeHour || (startTimeHour == myTask.startTimeHour && startTimeMinute > myTask.startTimeMinute)) { + return 1; + } else { + return -1; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/ParameterizedTypeImpl.java b/app/src/main/java/com/example/iot_controlhost/model/ParameterizedTypeImpl.java new file mode 100644 index 0000000..23b7dc9 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/ParameterizedTypeImpl.java @@ -0,0 +1,28 @@ +package com.example.iot_controlhost.model; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; + +public class ParameterizedTypeImpl implements ParameterizedType { + Class clazz; + + public ParameterizedTypeImpl(Class clz) { + clazz = clz; + } + + @Override + public Type[] getActualTypeArguments() { + return new Type[]{clazz}; + } + + @Override + public Type getRawType() { + return List.class; + } + + @Override + public Type getOwnerType() { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/Result.java b/app/src/main/java/com/example/iot_controlhost/model/Result.java new file mode 100644 index 0000000..c1b0a23 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/Result.java @@ -0,0 +1,31 @@ +package com.example.iot_controlhost.model; + +/** + * 开关按钮的操作结果 + */ +public class Result { + /** + * 操作成功与否 + */ + private boolean operateResult; + /** + * 操作后的状态 + */ + private boolean state; + + public boolean isOperateResult() { + return operateResult; + } + + public void setOperateResult(boolean operateResult) { + this.operateResult = operateResult; + } + + public boolean isState() { + return state; + } + + public void setState(boolean state) { + this.state = state; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/Set.java b/app/src/main/java/com/example/iot_controlhost/model/Set.java new file mode 100644 index 0000000..e653421 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/Set.java @@ -0,0 +1,106 @@ +package com.example.iot_controlhost.model; + +import android.view.View; +import android.widget.CompoundButton; + +/** + * @Author:DuanKaiji + * @Date:2023年9月20日 13:39:27 + * @Declaration:设置单项内容-配置界面用 + */ +public class Set { + /** + * 设置项 + */ + private String title; + /** + * 设置内容 + */ + private String detail; + /** + * 点击事件 + */ + private View.OnClickListener onClickListener; + /** + * 默认开关 + */ + private boolean isChecked; + /** + * 开关事件 + * 为空时不显示开关 + */ + private CompoundButton.OnCheckedChangeListener onCheckedChangeListener; + + /** + * 设置标题和事件 + */ + public Set(String title, View.OnClickListener onClickListener) { + this.title = title; + this.onClickListener = onClickListener; + } + /** + * 设置标题和事件 + */ + public Set(String title, String detail) { + this.title = title; + this.detail = detail; + } + + /** + * 设置标题、细节和事件 + */ + public Set(String title, String detail, View.OnClickListener onClickListener) { + this.title = title; + this.detail = detail; + this.onClickListener = onClickListener; + } + + /** + * 设置标题、开关默认状态和开关事件 + */ + public Set(String title, boolean isChecked, CompoundButton.OnCheckedChangeListener onCheckedChangeListener) { + this.title = title; + this.isChecked = isChecked; + this.onCheckedChangeListener = onCheckedChangeListener; + } + + public boolean isChecked() { + return isChecked; + } + + public void setChecked(boolean checked) { + isChecked = checked; + } + + public CompoundButton.OnCheckedChangeListener getOnCheckedChangeListener() { + return onCheckedChangeListener; + } + + public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener onCheckedChangeListener) { + this.onCheckedChangeListener = onCheckedChangeListener; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public View.OnClickListener getOnClickListener() { + return onClickListener; + } + + public void setOnClickListener(View.OnClickListener onClickListener) { + this.onClickListener = onClickListener; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/Silkworm.java b/app/src/main/java/com/example/iot_controlhost/model/Silkworm.java new file mode 100644 index 0000000..cd2461f --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/Silkworm.java @@ -0,0 +1,101 @@ +package com.example.iot_controlhost.model; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Generated; + +import java.util.Date; + +/** + * 蚕期 + * 数据库 + */ +@Entity +public class Silkworm { + @Id(autoincrement = true) + private Long id; + /** + * 蚕季名 + */ + private String name; + + /** + * 开始日期 + */ + private Date StartDate; + /** + * 结束日期 + */ + private Date EndDate; + /** + * 数量 + */ + private int number; + /** + * 品种 + */ + private String variety; + + @Generated(hash = 539743088) + public Silkworm(Long id, String name, Date StartDate, Date EndDate, int number, + String variety) { + this.id = id; + this.name = name; + this.StartDate = StartDate; + this.EndDate = EndDate; + this.number = number; + this.variety = variety; + } + + @Generated(hash = 222488988) + public Silkworm() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getStartDate() { + return StartDate; + } + + public void setStartDate(Date startDate) { + StartDate = startDate; + } + + public Date getEndDate() { + return EndDate; + } + + public void setEndDate(Date endDate) { + EndDate = endDate; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public String getVariety() { + return variety; + } + + public void setVariety(String variety) { + this.variety = variety; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/SilkwormNew.java b/app/src/main/java/com/example/iot_controlhost/model/SilkwormNew.java new file mode 100644 index 0000000..13c594c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/SilkwormNew.java @@ -0,0 +1,99 @@ +package com.example.iot_controlhost.model; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Generated; +import org.greenrobot.greendao.annotation.Id; + +import java.util.Date; + +/** + * 蚕期 + */ +public class SilkwormNew { + /** + * 共育编码 + * 用于服务器开始共育结束共育的识别,作用类似于开始/结束作业 + */ + private String id; + /** + * 蚕季名 + */ + private String name; + + /** + * 开始日期 + */ + private Date StartDate; + /** + * 结束日期 + */ + private Date EndDate; + /** + * 数量 + */ + private double number; + /** + * 品种 + */ + private String variety; + + public SilkwormNew() { + } + + public SilkwormNew(String id, String name, Date startDate, Date endDate, double number, String variety) { + this.id = id; + this.name = name; + StartDate = startDate; + EndDate = endDate; + this.number = number; + this.variety = variety; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getStartDate() { + return StartDate; + } + + public void setStartDate(Date startDate) { + StartDate = startDate; + } + + public Date getEndDate() { + return EndDate; + } + + public void setEndDate(Date endDate) { + EndDate = endDate; + } + + public double getNumber() { + return number; + } + + public void setNumber(double number) { + this.number = number; + } + + public String getVariety() { + return variety; + } + + public void setVariety(String variety) { + this.variety = variety; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/SingleLiveEvent.java b/app/src/main/java/com/example/iot_controlhost/model/SingleLiveEvent.java new file mode 100644 index 0000000..86d5f7a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/SingleLiveEvent.java @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.iot_controlhost.model; + +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and Snackbar messages. + *

+ * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + *

+ * Note that only one observer is going to be notified of changes. + */ +public class SingleLiveEvent extends MutableLiveData { + + private final AtomicBoolean mPending = new AtomicBoolean(false); + + @MainThread + public void observe(LifecycleOwner owner, final Observer observer) { + + // Observe the internal MutableLiveData + super.observe(owner, new Observer() { + @Override + public void onChanged(@Nullable T t) { + if (mPending.compareAndSet(true, false)) { + observer.onChanged(t); + } + } + }); + } + + @MainThread + public void setValue(@Nullable T t) { + mPending.set(true); + super.setValue(t); + } + + /** + * Used for cases where T is Void, to make calls cleaner. + */ + @MainThread + public void call() { + setValue(null); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/AirConditionInfrared.java b/app/src/main/java/com/example/iot_controlhost/model/controller/AirConditionInfrared.java new file mode 100644 index 0000000..f6afeb4 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/AirConditionInfrared.java @@ -0,0 +1,427 @@ +package com.example.iot_controlhost.model.controller; + +import com.example.iot_controlhost.base.SetAddress; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * 空调红外控制器 + * 此类的port为空调的,而非红外的 + */ +public class AirConditionInfrared extends Controller implements SetAddress { + + public static int HEAT = 0; + public static int AUTO = 1; + public static int COLD = 2; + public static int DEHUMIDIFY = 3; + + /** + * 空调温度 + */ + private final String TEMPERATURE = name + "temperature"; + /** + * 空调状态 + * 0制热 1自动 2制冷 + */ + private final String MODE = name + "mode"; + + public AirConditionInfrared(String name) { + super(name); + } + + /** + * 使用红外遥控器调节 + * + * @param state 开/关 + * @return 调节电源成功与否 + */ + @Override + public boolean setPowerSupply(boolean state) { + if (state && RoomSetting.isAirConditionForceClose()) { + //想开空调,但是在强制关空调期间 + MyLog.controllerError("空调红外:强制关空调期间,无法开启空调"); + return false; + } + if (!isAvailable()) { + MyLog.controllerError("空调红外:未配置但尝试开关"); + this.powerSupply = false; + return false; + } + boolean result; + if (state) { + //开空调:开空调物理开关->开遥控开关 + if (!RoomController.airCondition.isPowerSupply()) { + MyLog.controller("空调电源当前为关闭状态,现开启空调物理开关"); + if (RoomController.airCondition.setPowerSupply(true)) { + MyLog.controller("空调电源打开成功,等待" + Variable.WAIT_START_INFRARED / 1000 + "s后使用红外遥控器打开空调"); + try { + Thread.sleep(Variable.WAIT_START_INFRARED); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + MyLog.controllerError("空调电源打开失败"); + return false; + } + } + //开机:先开机指令01,再调节指令setAirCondition + MyLog.controller("空调红外:开机"); + String str = getAddress() + "0600010001"; + result = executeInstructions(str + getCRC(str), "开电源"); + if (result) { + //开机成功 + this.powerSupply = true; + } else { + //开机失败 + this.powerSupply = false; + } + } else { + //关机 + MyLog.controller("空调红外:关电源"); + String str = getAddress() + "0600010002"; + String crc = getCRC(str); + str = str + crc; + result = executeInstructions(str, "关电源"); + if (result) { + //关机成功 + this.powerSupply = false; + } + //延迟一个Variable.CHECK_CLOSE_INFRARED时间来关闭空调物理电源 + RoomController.doNotNeedDelayCloseAirCondition = false; + } + return result; + } + + @Override + public boolean isPowerSupply() { + //当空调物理电源和红外电源同时开时,才为开 + return super.isPowerSupply() && RoomController.airCondition.isPowerSupply(); + } + + /** + * 修改模式,温度不变 + */ + public boolean setAirConditionMode(int newMode) { + return setAirCondition(newMode, getTemperature()); + } + + /** + * 修改温度,模式不变 + */ + public boolean setAirConditionTemperature(int temperature) { + return setAirCondition(getMode(), temperature); + } + + /** + * 切换模式与温度 + * + * @param newMode 0制热 1自动 2制冷 + * @param temperature 目标温度 + * @return 操作成功与否 + */ + public boolean setAirCondition(int newMode, int temperature) { + try { + //操作空调, + if (RoomSetting.isAirStartFirst()) { + if (!isPowerSupply() && !setPowerSupply(true)) { + //红外电源没开 并且 尝试开启失败 + return false; + } + //增加500ms延迟,避免出现“开空调后立即切换模式出现操作失败”的情况 + Thread.sleep(500); + } + if (!isAvailable() || temperature > 30 || temperature < 17) { + return false; + } + int instructionType = temperature; + String str = getAddress() + "06000100"; + if (newMode == 0) { + //制热 + instructionType -= 12; + } else if (newMode == 2) { + //制冷 + instructionType += 2; + } else if (newMode == 1) { + //自动 + instructionType += 16; + } else { + //除湿 + instructionType += 30; + } + str = str + String.format("%02X", instructionType); + str = str + getCRC(str); + if (executeInstructions(str, (newMode == 0 ? "制热" : newMode == 1 ? "自动" : newMode == 2 ? "制冷" : "除湿") + temperature + "℃")) { + setMode(newMode); + setTemperature(temperature); + this.powerSupply = true; + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * 学习指令 + * 执行后,红外控制器灯亮,在人工发送红外指令后,会熄灯 + * 之后可以通过“读取”操作查看是否学习成功 + * + * @param type 配置文件-红外编号 0为取消学习 + * @return 学习指令是否发送成功 + */ + public boolean learnInstruction(int type) { + String instruction = getAddress() + "06000000" + String.format("%02X", type); + instruction = instruction + getCRC(instruction); + return executeInstructions(instruction, "学习"); + } + + /** + * 执行指令 + * + * @param type 指令类型,见最下方指令对应表 + */ + public boolean executeInstruction(int type) { + String instruction = getAddress() + "06000100" + String.format("%02X", type); + instruction = instruction + getCRC(instruction); + return executeInstructions(instruction, "执行"); + } + + /** + * 读取对应红外编号的指令 + * + * @return 对应红外编号的指令 + */ + public String readInstruction(int type) { + try { + String instruction = getAddress() + "4103E800" + String.format("%02X", type); + instruction = instruction + getCRC(instruction); + String result = serialPortUtil.sendAndReceiveSerialData(this, instruction); + //截取result的第7位到倒数第3位 + if (result != null) { + return result.substring(6, result.length() - 4); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 手动写入对应红外编号的指令 + * + * @return 写入成功与否 + */ + public boolean writeInstruction(int type, String ins) { + String instruction = getAddress() + "4203E800" + String.format("%02X", type) + ins; + instruction = instruction + getCRC(instruction); + //截取result的第7位到倒数第3位 + String successIns = getAddress() + "4203E800" + String.format("%02X", type); + successIns = successIns + getCRC(successIns); + if (executeInstructions(instruction, successIns, "导入红外指令")) { + return true; + } + return false; + } + + /** + * 获取当前空调状态的介绍 + */ + public String getState() { + return !isAvailable() ? "未正确配置" : (isPowerSupply() ? + getTemperature() + "℃" + getModeName() + "中" : "未启动"); + } + + public int getTemperature() { + return MMKVUtil.get(TEMPERATURE, 26); + } + + public void setTemperature(int temperature) { + MMKVUtil.put(TEMPERATURE, temperature); + } + + /** + * 当前模式 + * + * @return 0制热 1自动 2制冷 + */ + public int getMode() { + return MMKVUtil.get(MODE, HEAT); + } + + /** + * 当前模式名 + */ + public String getModeName() { + int modeId = getMode(); + if (modeId == HEAT) { + return "制热"; + } else if (modeId == AUTO) { + return "自动"; + } else if (modeId == COLD) { + return "制冷"; + } else if (modeId == DEHUMIDIFY) { + return "除湿"; + } + return "未知"; + } + + public void setMode(int mode) { + MMKVUtil.put(this.MODE, mode); + } + + @Override + public String[] getModels() { + return new String[]{"标准Modbus-RTU型"}; + } + + @Override + public String getAddress() { + if (isIntegratedController()) { + return HardwareSetting.getCommonAddress(); + } else { + return super.getAddress(); + } + } + + boolean temp; + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress, String oldBaud, String newAddress) { + boolean result = false; + if (model.equals(getModels()[0])) { + //需要改地址 + String editAddress = oldAddress + "06000200" + newAddress; + editAddress = editAddress + getCRC(editAddress); + executeInstructions(editAddress, "改地址"); + //空调改地址不返回,默认成功 红外改地址 直接生效 + result = true; + } + return result; + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) { + boolean result = false; + if (model.equals(getModels()[0])) { + switch (newBaud) { + case "1200": + newBaud = "000C"; + break; + case "2400": + newBaud = "0018"; + break; + case "4800": + newBaud = "0030"; + break; + case "9600": + newBaud = "0060"; + break; + case "19200": + newBaud = "00C0"; + break; + case "38400": + newBaud = "0180"; + break; + case "57600": + newBaud = "0240"; + break; + case "115200": + newBaud = "0480"; + break; + default: + return false; + } + String editBaud = oldAddress + "060003" + newBaud; + editBaud = editBaud + getCRC(editBaud); + //重启后生效 + result = executeInstructions(editBaud, "改波特率"); + } + return result; + } + + @Override + public boolean test(String deviceModel, String deviceAddress) { + if (deviceModel.equals(getModels()[0])) { + String str = deviceAddress + (!temp ? "0600010002" : "0600010001"); + str = str + getCRC(str); + temp = !temp; + return executeInstructions(str, !temp ? "关电源" : "开电源"); + } + return false; + } + + @Override + public String getTestState() { + return "空调" + (temp ? "开启" : "关闭") + "指令已尝试发送"; + } + +/** + * 指令对应表 + * + * 1-空调开 + * 2-空调关 + * 3-制热 + * 4-制冷 + * 5-制热 17 度 + * 6-制热 18 度 + * 7-制热 19 度 + * 8-制热 20 度 + * 9-制热 21 度 + * 10-制热 22 度 + * 11-制热 23 度 + * 12-制热 24 度 + * 13-制热 25 度 + * 14-制热 26 度 + * 15-制热 27 度 + * 16-制热 28 度 + * 17-制热 29 度 + * 18-制热 30 度 + * 19-制冷 17 度 + * 20-制冷 18 度 + * 21-制冷 19 度 + * 22-制冷 20 度 + * 23-制冷 21 度 + * 24-制冷 22 度 + * 25-制冷 23 度 + * 26-制冷 24 度 + * 27-制冷 25 度 + * 28-制冷 26 度 + * 29-制冷 27 度 + * 30-制冷 28 度 + * 31-制冷 29 度 + * 32-制冷 30 度 + * 33-自动 17 度 + * 34-自动 18 度 + * 35-自动 19 度 + * 36-自动 20 度 + * 37-自动 21 度 + * 38-自动 22 度 + * 39-自动 23 度 + * 40-自动 24 度 + * 41-自动 25 度 + * 42-自动 26 度 + * 43-自动 27 度 + * 44-自动 28 度 + * 45-自动 29 度 + * 46-自动 30 度 + * 47-抽湿 17 度 + * 48-抽湿 18 度 + * 49-抽湿 19 度 + * 50-抽湿 20 度 + * 51-抽湿 21 度 + * 52-抽湿 22 度 + * 53-抽湿 23 度 + * 54-抽湿 24 度 + * 55-抽湿 25 度 + * 56-抽湿 26 度 + * 57-抽湿 27 度 + * 58-抽湿 28 度 + * 59-抽湿 29 度 + * 60-抽湿 30 度 + */ +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/Controller.java b/app/src/main/java/com/example/iot_controlhost/model/controller/Controller.java new file mode 100644 index 0000000..d9cd482 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/Controller.java @@ -0,0 +1,62 @@ +package com.example.iot_controlhost.model.controller; + +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.utils.log.MyLog; + +import android_serialport_api.SerialPortUtil; + +/** + * 控制器 + * + * @Author DuanKaiji + */ +public abstract class Controller extends Device { + /** + * 设备电源开关 + */ + protected boolean powerSupply; + + protected static SerialPortUtil serialPortUtil; + + public Controller(String name) { + super(name); + serialPortUtil = SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER); + } + + /** + * 设置开关 需要子类实现 + * + * @param state 目标状态 + * @return 操作成功与否 + */ + public abstract boolean setPowerSupply(boolean state); + + /** + * 执行控制器指令 + * + * @param instruction 指令 + * @param operateContent 操作内容 用于输出日志 + * @return 操作成功与否-发送指令与接收指令相同 + */ + protected boolean executeInstructions(String instruction, String operateContent) { + return executeInstructions(instruction, instruction, operateContent); + } + + + protected boolean executeInstructions(String instruction, String result, String operateContent) { + if (!isAvailable()) { + return false; + } + MyLog.controller("操作" + name + ":" + operateContent); + if (result != null && result.equals(serialPortUtil.sendAndReceiveSerialData(this, instruction))) { + return true; + } else { + return false; + } + } + + public boolean isPowerSupply() { + return powerSupply; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/DehumidifierInfrared.java b/app/src/main/java/com/example/iot_controlhost/model/controller/DehumidifierInfrared.java new file mode 100644 index 0000000..8d82115 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/DehumidifierInfrared.java @@ -0,0 +1,208 @@ +package com.example.iot_controlhost.model.controller; + +import com.example.iot_controlhost.base.SetAddress; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * 除湿机红外控制器 + * 此类的port为除湿机的,而非红外的 + */ +public class DehumidifierInfrared extends Controller implements SetAddress { + + public DehumidifierInfrared(String name) { + super(name); + } + + /** + * 使用红外遥控器调节 + * + * @param state 开/关 + * @return 调节电源成功与否 + * + * + * 注意:此方法不可直接调用,相关操作已移至RoomController.dehumidifier.setPowerSupply(),请从那里间接调用以确保除湿机物理电源和红外电源的正确配合 + */ + @Override + public boolean setPowerSupply(boolean state) { + if (!isAvailable()) { + MyLog.controllerError("除湿机红外:未配置但尝试开关"); + this.powerSupply = false; + return false; + } + this.powerSupply = state; + return state; + } + + @Override + public boolean isPowerSupply() { + //当除湿机物理电源和红外电源同时开时,才为开 + return super.isPowerSupply() && RoomController.dehumidifier.isPowerSupply(); + } + + /** + * 学习指令 + * 执行后,红外控制器灯亮,在人工发送红外指令后,会熄灯 + * 之后可以通过“读取”操作查看是否学习成功 + * + * @param type 配置文件-红外编号 0为取消学习 + * @return 学习指令是否发送成功 + */ + public boolean learnInstruction(int type) { + String instruction = getAddress() + "06000000" + String.format("%02X", type); + instruction = instruction + getCRC(instruction); + return executeInstructions(instruction, "学习"); + } + + /** + * 执行指令 + * + * @param type 指令类型,见最下方指令对应表 + */ + public boolean executeInstruction(int type) { + String instruction = getAddress() + "06000100" + String.format("%02X", type); + instruction = instruction + getCRC(instruction); + return executeInstructions(instruction, "执行"); + } + + /** + * 读取对应红外编号的指令 + * + * @return 对应红外编号的指令 + */ + public String readInstruction(int type) { + try { + String instruction = getAddress() + "4103E800" + String.format("%02X", type); + instruction = instruction + getCRC(instruction); + String result = serialPortUtil.sendAndReceiveSerialData(this, instruction); + //截取result的第7位到倒数第3位 + if (result != null) { + return result.substring(6, result.length() - 4); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 手动写入对应红外编号的指令 + * + * @return 写入成功与否 + */ + public boolean writeInstruction(int type, String ins) { + String instruction = getAddress() + "4203E800" + String.format("%02X", type) + ins; + instruction = instruction + getCRC(instruction); + //截取result的第7位到倒数第3位 + String successIns = getAddress() + "4203E800" + String.format("%02X", type); + successIns = successIns + getCRC(successIns); + if (executeInstructions(instruction, successIns, "导入红外指令")) { + return true; + } + return false; + } + + /** + * 获取当前除湿机状态的介绍 + */ + public String getState() { + return !isAvailable() ? "未正确配置" : (isPowerSupply() ? "已开启" : "未启动"); + } + + @Override + public String[] getModels() { + return new String[]{"标准Modbus-RTU型"}; + } + + @Override + public String getAddress() { + if (isIntegratedController()) { + return HardwareSetting.getCommonAddress(); + } else { + return super.getAddress(); + } + } + + boolean temp; + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress, String oldBaud, String newAddress) { + boolean result = false; + if (model.equals(getModels()[0])) { + //需要改地址 + String editAddress = oldAddress + "06000200" + newAddress; + editAddress = editAddress + getCRC(editAddress); + executeInstructions(editAddress, "改地址"); + //除湿机改地址不返回,默认成功 红外改地址 直接生效 + result = true; + } + return result; + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) { + boolean result = false; + if (model.equals(getModels()[0])) { + switch (newBaud) { + case "1200": + newBaud = "000C"; + break; + case "2400": + newBaud = "0018"; + break; + case "4800": + newBaud = "0030"; + break; + case "9600": + newBaud = "0060"; + break; + case "19200": + newBaud = "00C0"; + break; + case "38400": + newBaud = "0180"; + break; + case "57600": + newBaud = "0240"; + break; + case "115200": + newBaud = "0480"; + break; + default: + return false; + } + String editBaud = oldAddress + "060003" + newBaud; + editBaud = editBaud + getCRC(editBaud); + //重启后生效 + result = executeInstructions(editBaud, "改波特率"); + } + return result; + } + + @Override + public boolean test(String deviceModel, String deviceAddress) { + if (deviceModel.equals(getModels()[0])) { + String str = deviceAddress + (!temp ? "0600010002" : "0600010001"); + str = str + getCRC(str); + temp = !temp; + return executeInstructions(str, !temp ? "关电源" : "开电源"); + } + return false; + } + + @Override + public String getTestState() { + return "除湿机" + (temp ? "开启" : "关闭") + "指令已尝试发送"; + } + +/** + * 指令对应表 + * + * 200-除湿机开 C8 + * 201-除湿机关 C9 + */ +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/Floor.java b/app/src/main/java/com/example/iot_controlhost/model/controller/Floor.java new file mode 100644 index 0000000..638c09c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/Floor.java @@ -0,0 +1,292 @@ +package com.example.iot_controlhost.model.controller; + +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.util.List; + +/** + * 地暖 + * + * @Author DuanKaiji + */ +public class Floor extends Controller { + /** + * 地暖档位设置 + * 1-5 0关闭 + */ + int gear; + /** + * 地暖最大档位 + */ + public static String MAX_GEAR = "地暖最大档位"; + /** + * 各档位 + */ + private PWM pwm0 = new PWM("地暖0档", 0); + private PWM pwm1 = new PWM("地暖1档"); + private PWM pwm2 = new PWM("地暖2档"); + private PWM pwm3 = new PWM("地暖3档"); + private PWM pwm4 = new PWM("地暖4档"); + private PWM pwm5 = new PWM("地暖5档"); + + public Floor(String name) { + super(name); + } + + @Override + public boolean setPowerSupply(boolean state) { + return setGear(state ? 3 : 0); + } + + @Override + public boolean isPowerSupply() { + return getGear() != 0; + } + + @Override + public boolean isAvailable() { + //当开关、地址、PWM均已设置,则为可用 + if (!super.isAvailable()) { + return false; + } + for (int i = 1; i < 6; i++) { + if (getPwm(i).getPwm() == -1) { + //只要有一个档位没配置 即-1 则不可用 + return false; + } + } + return true; + } + + /** + * 设置地暖档位 + * 当档位为0时,等同于设置电源关 setPowerSupply(false), + * 关闭电源无需配置,但调节1-5档位需要先配置 + * + * @param gear 档位 + * @return 设置成功与否 + */ + public boolean setGear(int gear) { + if (gear > getMaxGear() || gear < 0) { + return false; + } + int pwm = getPwm(gear).getPwm(); + if (setGearByPWM(pwm)) { + this.gear = gear; + //继电器下的地暖与此处同步 + RoomController.relayFloor.setPowerSupply(gear != 0); + return true; + } else { + return false; + } + } + + public boolean setGearByPWM(int pwm) { + if (!isAvailable()) { + return false; + } + String str = null; + if (isIntegratedController()) { + //集成式控制器 + str = HardwareSetting.getCommonAddress(); + } else { + //装配式控制器 + if (getModel().equals(getModels()[0])) { + //能工RS458控制模式 + str = getAddress() + "060002"; + } else if (getModel().equals(getModels()[1]) || getModel().equals(getModels()[2])) { + //中胜0-5V模拟量控制/克能0-10V模拟量控制 + str = getAddress() + "06000A"; + } + str = str + String.format("%04X", pwm); + str = str + getCRC(str); + } + boolean result = true; + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[1]) || RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + //继电器使用中胜0-10V模拟量 / BBIT-H-v1.0 控制,则1.地暖使用继电器的地址;2.存在多个端口的情况; + List ports = RoomController.relayFloor.getPorts(); + if (ports.size() == 0) { + MyLog.controllerError("使用 中盛0-10V模拟量 / BBIT-H-v1.0 型号继电器控制,但未配置" + RoomSetting.getFloorName() + "端口"); + return false; + } + for (int port : ports) { + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[1])) { + //继电器和地暖地址相同,不需要再次设置继电器地址 + str = RoomController.relayFloor.getAddress() + "0600" + String.format("%02X", 9 + port) + String.format("%04X", pwm); + } else if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + //继电器使用BBIT-H-v1.0型号 + str = RoomController.relayFloor.getAddress() + "01" + String.format("%02X", port) + String.format("%04X", pwm); + } + str = str + getCRC(str); + boolean temp = executeInstructions(str, "控制第" + port + "号" + RoomSetting.getFloorName() + "端口PWM为:" + pwm); + //只要有一个失败,则结果为失败 + result = result ? temp : false; + } + } else { + result = executeInstructions(str, "控制" + RoomSetting.getFloorName() + "端口PWM为:" + pwm); + } + return result; + } + + /** + * 根据不同型号的地暖来获得最大电压 + * + * @return 十进制的电压 后续使用需要转为4位长的16进制并拼接在 + */ + public int getMaxPower() { + int result = 0; + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[1]) + || RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) + || getModel().equals(getModels()[2])) { + //先判断继电器中盛0-10V模拟量 || BBIT继电器 || 克能0-10V模拟量控制 + result = 10000; + } else if (getModel().equals(getModels()[0]) || RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + //能工RS458控制模式 || BBIT-H-v1.0 + result = 4096; + } else if (getModel().equals(getModels()[1])) { + //中胜0-5V模拟量控制 + result = 5000; + } + return result; + } + + public int getMinPower() { + return 900; + } + + @Override + public String[] getModels() { + return new String[]{"能工RS485", "中盛0-5V模拟量", "中盛0-10V模拟量"}; + } + + public String getState() { + return !isAvailable() ? "未正确配置" : (isPowerSupply() ? getGear() + "档升温中" : "未启动"); + } + + /** + * 功率档位记录 + */ + public class PWM { + /** + * PWM名称 + */ + private String pwmName; + /** + * 功率 + */ + private String power; + /** + * PWM参数 + */ + private String pwm; + + public PWM(String pwmName) { + this.pwmName = pwmName; + this.pwm = pwmName + "PWM"; + this.power = pwmName + "功率"; + } + + public PWM(String pwmName, int pwm) { + this.pwmName = pwmName; + this.pwm = pwmName + "PWM"; + this.power = pwmName + "功率"; + setPwm(pwm); + } + + public double getPower() { + return MMKVUtil.get(power, 0.0); + } + + public void setPower(double power) { + MyLog.controller("设置" + pwmName + "档位功率为" + power); + MMKVUtil.put(this.power, power); + } + + public int getPwm() { + return MMKVUtil.get(pwm, -1); + } + + public void setPwm(int pwm) { + MyLog.controller("设置" + pwmName + "档位PWM为" + pwm); + MMKVUtil.put(this.pwm, pwm); + } + + @Override + public String toString() { + return "PWM:" + (getPwm() == -1 ? "未配置" : getPwm()) + "\t\t功率:" + String.format("%.2f\t", getPower()); + } + } + + public PWM getPwm(int i) { + switch (i) { + case 0: + default: + return pwm0; + case 1: + return pwm1; + case 2: + return pwm2; + case 3: + return pwm3; + case 4: + return pwm4; + case 5: + return pwm5; + } + } + + public int getGear() { + return gear; + } + + /** + * 导入已有配置 + * 方便没有功耗传感器的用户 + */ + public static void importDefaultPWMToFloor() { + String model = RoomController.floor.getModel(); + RoomController.floor.getPwm(0).setPwm(0); + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[1]) + || RoomController.floor.getModels()[2].equals(model) + || RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + //先判断中盛0-10V模拟量 || 克能0-10V模拟量控制 || BBIT-H-v1.0 + RoomController.floor.getPwm(1).setPwm(2500); + RoomController.floor.getPwm(2).setPwm(4000); + RoomController.floor.getPwm(3).setPwm(6000); + RoomController.floor.getPwm(4).setPwm(8000); + RoomController.floor.getPwm(5).setPwm(10000); + } else if (RoomController.floor.getModels()[0].equals(model)) { + //能工RS458控制模式 + RoomController.floor.getPwm(1).setPwm(2500); + RoomController.floor.getPwm(2).setPwm(2817); + RoomController.floor.getPwm(3).setPwm(3500); + RoomController.floor.getPwm(4).setPwm(3900); + RoomController.floor.getPwm(5).setPwm(4096); + } else if (RoomController.floor.getModels()[1].equals(model)) { + //中胜0-5V模拟量控制 + RoomController.floor.getPwm(1).setPwm(2500); + RoomController.floor.getPwm(2).setPwm(3000); + RoomController.floor.getPwm(3).setPwm(3500); + RoomController.floor.getPwm(4).setPwm(4000); + RoomController.floor.getPwm(5).setPwm(5000); + } + RoomController.floor.getPwm(0).setPower(0); + RoomController.floor.getPwm(1).setPower(0); + RoomController.floor.getPwm(2).setPower(0); + RoomController.floor.getPwm(3).setPower(0); + RoomController.floor.getPwm(4).setPower(0); + RoomController.floor.getPwm(5).setPower(0); + } + + public int getMaxGear() { + return MMKVUtil.get(MAX_GEAR, 5); + } + + public void setMaxGear(int maxGear) { + MMKVUtil.put(MAX_GEAR, maxGear); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/AirCondition.java b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/AirCondition.java new file mode 100644 index 0000000..33d5f24 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/AirCondition.java @@ -0,0 +1,35 @@ +package com.example.iot_controlhost.model.controller.relay; + +import com.example.iot_controlhost.utils.MMKVUtil; + +/** + * @Description 继电器下-空调 + * @Author DuanKaiji + * @CreateTime 2023年12月11日 09:35:18 + */ +public class AirCondition extends Relay { + public AirCondition(String name) { + super(name); + } + + /** + * 获取空调型号 + * 因为getModel()方法在Relay中的setPowerSupply()中会被用来分辨获取继电器开关指令 + * 所以不能直接重写getModel()与setModel() + */ + public String getAirConditionModel() { + return MMKVUtil.get(name + "型号", "默认"); + } + + public void setAirConditionModel(String model) { + MMKVUtil.put(name + "型号", model); + } + + public String getAirConditionModel2() { + return MMKVUtil.get(name + "2型号", "默认"); + } + + public void setAirConditionModel2(String model) { + MMKVUtil.put(name + "2型号", model); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Dehumidifier.java b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Dehumidifier.java new file mode 100644 index 0000000..a52da47 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Dehumidifier.java @@ -0,0 +1,94 @@ +package com.example.iot_controlhost.model.controller.relay; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description 除湿机,核心控制地方 + * @Author DuanKaiji + * @CreateTime 2023年12月11日 09:35:18 + */ +public class Dehumidifier extends Relay { + public Dehumidifier(String name) { + super(name, R.mipmap.ven_open, R.mipmap.ven_close); + } + + @Override + public boolean isAvailable() { + // 除湿机的可用性取决于除湿机-继电器和除湿机-红外遥控器的可用性 + return super.isAvailable() && RoomController.dehumidifierInfrared.isAvailable(); + } + + public boolean setPowerSupplyForOld(boolean state) { + // 设置物理开关的方法 + return super.setPowerSupply(state); + } + @Override + public boolean setPowerSupply(boolean state) { + // 设置红外与物理开的方法 + boolean result; + if (state) { + //开除湿机:开除湿机物理开关->开遥控开关 + if (!this.powerSupply) { + MyLog.controller("除湿机电源当前为关闭状态,现开启除湿机物理开关"); + if (setPowerSupplyForOld(true)) { + MyLog.controller("除湿机电源打开成功,等待" + Variable.WAIT_START_INFRARED_FOR_DEHUMIDIFIER / 1000 + "s后使用红外遥控器打开除湿机"); + try { + Thread.sleep(Variable.WAIT_START_INFRARED_FOR_DEHUMIDIFIER); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + MyLog.controllerError("除湿机电源打开失败"); + return false; + } + } + //开机:先开机指令01,再调节指令setAirCondition + MyLog.controller("除湿机红外:开机"); + String str = getAddress() + "06000100C8"; + result = executeInstructions(str + getCRC(str), "开电源"); + // 设置开机成功与否 + this.powerSupply = result; + RoomController.dehumidifierInfrared.setPowerSupply(result); + } else { + //关机 + MyLog.controller("除湿机红外:关电源"); + String str = getAddress() + "06000100C9"; + String crc = getCRC(str); + str = str + crc; + result = executeInstructions(str, "关电源"); + if (result) { + //关机成功 + RoomController.dehumidifierInfrared.setPowerSupply(false); + this.powerSupply = false; + } + //延迟一个Variable.CHECK_CLOSE_INFRARED时间来关闭除湿机物理电源 + RoomController.doNotNeedDelayCloseDehumidifier = false; + } + return result; + } + + /** + * 获取空调型号 + * 因为getModel()方法在Relay中的setPowerSupply()中会被用来分辨获取继电器开关指令 + * 所以不能直接重写getModel()与setModel() + */ + public String getAirConditionModel() { + return MMKVUtil.get(name + "型号", "默认"); + } + + public void setAirConditionModel(String model) { + MMKVUtil.put(name + "型号", model); + } + + public String getAirConditionModel2() { + return MMKVUtil.get(name + "2型号", "默认"); + } + + public void setAirConditionModel2(String model) { + MMKVUtil.put(name + "2型号", model); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java new file mode 100644 index 0000000..749b75a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Fan.java @@ -0,0 +1,107 @@ +package com.example.iot_controlhost.model.controller.relay; + +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.SpinnerList; + +/** + * @Description 换气扇(总控所有换气扇) + * @Author DuanKaiji + * @CreateTime 2024年03月05日 15:33 + */ +public class Fan extends Relay { + + Relay[] fans = new Relay[]{RoomController.intakeFan, RoomController.exhaustFan}; + + public Fan(String name, int iconOpen, int iconClose) { + super(name, iconOpen, iconClose); + } + + @Override + public String[] getModels() { + return new String[]{"换气扇"}; + } + + + @Override + public boolean setPowerSupply(boolean target) { + boolean result = false; + if (target) { + if (SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode()) + || SpinnerList.VENTILATION_MODES[1].equals(AutoModelSet.getVentilatorMode())) { + result = MyUtil.autoControlOperateThird(fans[0], true,false); + } + if (SpinnerList.VENTILATION_MODES[0].equals(AutoModelSet.getVentilatorMode()) + || SpinnerList.VENTILATION_MODES[2].equals(AutoModelSet.getVentilatorMode())) { + result = MyUtil.autoControlOperateThird(fans[1], true,false) || result; + } + } else { + //防止出现“仅开进气扇-开启-设置为仅开排气扇-关闭”此时进气扇无法关闭的情况 所以每次都得全部关闭(各种换气扇) + result = MyUtil.autoControlOperateThird(fans[0], false,false); + result = MyUtil.autoControlOperateThird(fans[1], false,false) || result; + } + return result; + } + + @Override + public boolean isPowerSupply() { + return fans[0].isPowerSupply() || fans[1].isPowerSupply(); + } + + @Override + public boolean isAvailable() { + return fans[0].isAvailable() || fans[1].isAvailable(); + } + + @Override + public double getVoltage() { + int total = 0; + int number = 0; + if (fans[0].isAvailable()) { + total += fans[0].getVoltage(); + number++; + } + if (fans[1].isAvailable()) { + total += fans[1].getVoltage(); + number++; + } + return total / number; + } + + public double getCurrent() { + int total = 0; + int number = 0; + if (fans[0].isAvailable()) { + total += fans[0].getCurrent(); + number++; + } + if (fans[1].isAvailable()) { + total += fans[1].getCurrent(); + number++; + } + return total / number; + } + + public double getPower() { + int total = 0; + if (fans[0].isAvailable()) { + total += fans[0].getPower(); + } + if (fans[1].isAvailable()) { + total += fans[1].getPower(); + } + return total; + } + + public double getEnergyConsumption() { + int total = 0; + if (fans[0].isAvailable()) { + total += fans[0].getEnergyConsumption(); + } + if (fans[1].isAvailable()) { + total += fans[1].getEnergyConsumption(); + } + return total; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/FloorRelay.java b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/FloorRelay.java new file mode 100644 index 0000000..03d8cde --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/FloorRelay.java @@ -0,0 +1,33 @@ +package com.example.iot_controlhost.model.controller.relay; + +import com.example.iot_controlhost.utils.global.RoomController; + +/** + * @Description 有在装配式控制器-中盛0-10V模拟量型号的情况下使用 + * 应用场景:设置继电器时(端口);操作地暖时(地址);检测继电器功耗异常时(开关); + * @Author DuanKaiji + * @CreateTime 2023年12月05日 11:19:12 + */ +public class FloorRelay extends Relay { + + public FloorRelay(String name) { + super(name); + } + + @Override + public boolean setPowerSupply(boolean state) { + this.powerSupply = state; + return RoomController.floor.isPowerSupply() == state; + } + + @Override + public boolean isEnable() { + return RoomController.floor.isEnable(); + } + + @Override + public boolean isAvailable() { + return RoomController.floor.isAvailable(); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Relay.java b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Relay.java new file mode 100644 index 0000000..86d8cb9 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/controller/relay/Relay.java @@ -0,0 +1,403 @@ +package com.example.iot_controlhost.model.controller.relay; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.base.SetAddress; +import com.example.iot_controlhost.model.controller.Controller; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.MyLog; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import android_serialport_api.SerialPortUtil; + +/** + * @Description 继电器 + * @Author DuanKaiji + * @CreateTime 2023年10月20日 09:26:45 + */ +public class Relay extends Controller implements SetAddress { + /** + * 继电器下设备通用设置项名 + * 个性化设置项使用name,通用设置项使用TAG + */ + public static final String TAG = "继电器"; + /** + * 设备端口 + */ + public final String PORT = name + "端口"; + + /** + * 设备图标资源Id + */ + private final String ICON_OPEN = name + "icon_open"; + /** + * 设备图标资源Id + */ + private final String ICON_CLOSE = name + "icon_close"; + + public Relay(String name) { + super(name); + } + + public Relay(String name, int iconOpen, int iconClose) { + super(name); + setIconOpen(iconOpen); + setIconClose(iconClose); + } + + /** + * 继电器上设备的开关方法 + * 应用于:各种灯、换气扇、 + * + * @param state 开/关 + * @return 修改成功与否 + */ + public boolean setPowerSupply(boolean state) { + if (!isAvailable()) { + MyLog.controllerError(getName() + "未正确配置但尝试控制"); + return false; + } + boolean result = true; + for (int port : getPorts()) { + result = setPowerSupply(state, port); + } + return result; + } + + /** + * 继电器上设备的开关方法 + * + * @param state 开/关 + * @param port 设备端口 + * @return 操作成功与否 + */ + public boolean setPowerSupply(boolean state, int port) { + String str = null; + if (isIntegratedController()) { + str = HardwareSetting.getCommonAddress() + "01" + String.format("%02X", port) + (state ? "1000" : "0000"); + } else { + if (getModel().equals(getModels()[0])) { + //能工RS458控制模式 + str = getAddress() + "0500" + String.format("%02X", port - 1) + (state ? "FF00" : "0000"); + } else if (getModel().equals(getModels()[1])) { + //中盛0-10V模拟量控制 + str = getAddress() + "0600" + String.format("%02X", 9 + port) + (state ? "2710" : "0000"); + } else if (getModel().equals(getModels()[2])) { + //BBIT-H-v1.0 + /** 01指令 + * 格式: 地址 类型 端口号 数据位(高位) 数据位(低位) CRC高位 CRC底位 + * 举例: 01 01 02 0F A0 74 BC + * 数据位:0-4096 发送时需要以十六进制发送 + * 返回:执行成功后返回对应数据 + **/ + str = getAddress() + "01" + String.format("%02X", port) + (state ? "2710" : "0000"); + } + } + str = str + getCRC(str); + boolean result = executeInstructions(str, "第" + port + "端口状态设为:" + state); + if (result) { + //操作成功 修改状态 + powerSupply = state; + } + return result; + } + public SerialPortUtil getSerialPortUtil() { + return serialPortUtil; + } + + /** + * 这个方法已重写 + * 因为继电器下的设备用的地址都是Relay的地址,而非各自的地址 + */ + @Override + public void setAddress(String address) { + MMKVUtil.put(TAG + "地址", address); + } + + /** + * 这个方法已重写 + * 因为继电器下的设备用的地址都是Relay的地址,而非各自的地址 + */ + @Override + public String getAddress() { + return MMKVUtil.get(TAG + "地址", SpinnerList.ADDRESS[0]); + } + + public String getModel() { + return MMKVUtil.get(TAG + "型号", getModels()[0]); + } + + public void setModel(String model) { + MMKVUtil.put(TAG + "型号", model); + } + + public boolean isEnable() { + return MMKVUtil.get(TAG + "启用", false); + } + + public void setEnable(boolean enable) { + MMKVUtil.put(TAG + "启用", enable); + } + + public List getPorts() { + String json = MMKVUtil.get(PORT, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public int getIconOpen() { + return MMKVUtil.get(ICON_OPEN, 0); + } + + public void setIconOpen(int icon) { + MMKVUtil.put(ICON_OPEN, icon); + } + + public int getIconClose() { + return MMKVUtil.get(ICON_CLOSE, 0); + } + + public void setIconClose(int icon) { + MMKVUtil.put(ICON_CLOSE, icon); + } + + public void addPort(Integer port) { + String json = MMKVUtil.get(PORT, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + temp.add(port); + MMKVUtil.put(PORT, new Gson().toJson(temp)); + } + + public void removePort(Integer port) { + String json = MMKVUtil.get(PORT, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + temp.remove(port); + MMKVUtil.put(PORT, new Gson().toJson(temp)); + } + + public void clearAndSetPort(Integer port) { + List temp = new ArrayList<>(); + temp.add(port); + MMKVUtil.put(PORT, new Gson().toJson(temp)); + } + + public void clearAllPort() { + List temp = new ArrayList<>(); + MMKVUtil.put(PORT, new Gson().toJson(temp)); + } + + /** + * 继电器设备需要额外判断 + * + * @return 可用与否 + */ + @Override + public boolean isAvailable() { + // 1.是否启用继电器 + boolean b1 = isEnable(); + // 2.是否配置了继电器地址 + boolean b2 = !StringUtils.isEmpty(getAddress()); + // 3.为继电器下的设备且配置了端口 + boolean b3 = PORT.equals(TAG + "端口") || getPorts().size() != 0; + return b1 && b2 && b3; + } + + @Override + public String[] getModels() { + return new String[]{"MODBUS LH-08", "中盛0-10V模拟量控制", "BBIT-H-v1.0"}; + } + //____________________________________________________以下为BBIT-H-v1.0继电器特有功能:读取每个继电器每个端口的能耗数据____________________________________ + /** + * 电压 + */ + private double voltage; + /** + * 电流 + */ + private double current; + /** + * 实时功率 + */ + private double power; + /** + * 能耗 + */ + private double energyConsumption; + + /** + * 输出能耗传感器数据 + */ + public String getSensorData() { + return "电压:" + getVoltage() + "V\t\t\t\t电流:" + getCurrent() + "A\n功率:" + getPower() + "W\t\t\t\t总能耗:" + getEnergyConsumption() + "kw·h"; + } + + /** + * 更新能耗 + */ + public boolean collectSensorData() { + double tempVoltage = 0; + double tempCurrent = 0; + double tempPower = 0; + double tempConsumption = 0; + for (int port : getPorts()) { + String str = RoomController.relay.getAddress() + "02" + String.format("%02X", port) + "0006"; + str = str + getCRC(str); + String data = RoomController.relay.getSerialPortUtil().sendAndReceiveSerialData(RoomSensor.consumptionSensor, str); + if (StringUtils.isEmpty(data)) { + initData(); + return false; + } + data = data.substring(2); + //开始解析 + try { + String[] PowerArr = substring(data, 2); + if (PowerArr.length < 19) { + initData(); + return false; + } + long V_int = Long.parseLong(PowerArr[3] + PowerArr[4] + PowerArr[5] + PowerArr[6], 16); + long A_int = Long.parseLong(PowerArr[7] + PowerArr[8] + PowerArr[9] + PowerArr[10], 16); + long P_int = Long.parseLong(PowerArr[11] + PowerArr[12] + PowerArr[13] + PowerArr[14], 16); + long PCount_int = Long.parseLong(PowerArr[15] + PowerArr[16] + PowerArr[17] + PowerArr[18], 16); + double factor = 0.0001; + DecimalFormat df = new DecimalFormat("#.00"); + tempVoltage += Double.parseDouble(df.format(V_int * factor)); + tempCurrent += Double.parseDouble(df.format(A_int * factor)); + tempPower += Double.parseDouble(df.format(P_int * factor)); + tempConsumption += Double.parseDouble(df.format(PCount_int * factor)); + } catch (Exception e) { + e.printStackTrace(); + initData(); + return false; + } + } + this.voltage = tempVoltage; + this.current = tempCurrent; + this.power = tempPower; + this.energyConsumption = tempConsumption; + return true; + } + + /** + * 解析失败 + * 后的处理 + */ + private void initData() { + this.voltage = 0.0; + this.current = 0.0; + this.power = 0.0; + this.energyConsumption = 0.0; + } + public double getVoltage() { + return voltage; + } + + public void setVoltage(double voltage) { + this.voltage = voltage; + } + + public double getCurrent() { + return current; + } + + public void setCurrent(double current) { + this.current = current; + } + + public double getPower() { + return power; + } + + public void setPower(double power) { + this.power = power; + } + + public double getEnergyConsumption() { + return energyConsumption; + } + + public void setEnergyConsumption(double energyConsumption) { + this.energyConsumption = energyConsumption; + } + + boolean temp; + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress, String oldBaud, String newAddress) { + boolean result = false; + if (model.equals(getModels()[1])) { + String editAddress = oldAddress + "06003200" + newAddress; + editAddress = editAddress + getCRC(editAddress); + result = editAddress.equals(serialPortUtil.sendAndReceiveSerialData(this, editAddress)); + } + return result; + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) { + boolean result = true; + if (model.equals(getModels()[1])) { + switch (newBaud) { + case "4800": + newBaud = "00"; + break; + case "9600": + newBaud = "01"; + break; + case "14400": + newBaud = "02"; + break; + case "19200": + newBaud = "03"; + break; + case "38400": + newBaud = "04"; + break; + case "56000": + newBaud = "05"; + break; + case "57600": + newBaud = "06"; + break; + case "115200": + newBaud = "07"; + break; + default: + return false; + } + String editBaud = oldAddress + "06003300" + newBaud; + editBaud = editBaud + getCRC(editBaud); + result = editBaud.equals(serialPortUtil.sendAndReceiveSerialData(this, editBaud)); + } + return result; + } + + @Override + public boolean test(String deviceModel, String deviceAddress) { + if (deviceModel.equals(getModels()[1])) { + String str = deviceAddress + "06000A" + (!temp ? "2710" : "0000"); + str = str + getCRC(str); + temp = !temp; + return executeInstructions(str, "第1端口状态设为:" + temp); + } + return false; + } + + @Override + public String getTestState() { + return "继电器1号端口" + (temp ? "已开启" : "已关闭"); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/AIMode.java b/app/src/main/java/com/example/iot_controlhost/model/net/AIMode.java new file mode 100644 index 0000000..b3bde9c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/AIMode.java @@ -0,0 +1,69 @@ +package com.example.iot_controlhost.model.net; + +import com.example.iot_controlhost.model.AITHMode; +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description AI共育模板 + * @Author DuanKaiji + * @CreateTime 2023年11月06日 17:03:50 + */ +public class AIMode { + + @SerializedName("Code") + private int code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private DataDTO data; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("ProgramName") + private String programName; + @SerializedName("Items") + private List items; + + public String getProgramName() { + return programName; + } + + public void setProgramName(String programName) { + this.programName = programName; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/AirConditionList.java b/app/src/main/java/com/example/iot_controlhost/model/net/AirConditionList.java new file mode 100644 index 0000000..8dafa27 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/AirConditionList.java @@ -0,0 +1,77 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description 从服务器获得的空调列表 + * @Author DuanKaiji + * @CreateTime 2023年12月07日 17:23:27 + */ +public class AirConditionList { + + @SerializedName("Code") + private int code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private List data; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("ID") + private String id; + @SerializedName("Brand") + private String brand; + @SerializedName("Model") + private String model; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/AppVersion.java b/app/src/main/java/com/example/iot_controlhost/model/net/AppVersion.java new file mode 100644 index 0000000..7a43d50 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/AppVersion.java @@ -0,0 +1,84 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description 应用版本-网络请求 + * @Author DuanKaiji + * @CreateTime 2023年10月19日 16:06:36 + */ +public class AppVersion { + @SerializedName("Code") + private int code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private DataDTO data; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("VersionNumber") + private int versionNumber; + @SerializedName("VersionName") + private String versionName; + @SerializedName("VersionDescription") + private String versionDescription; + @SerializedName("Url") + private String url; + + public int getVersionNumber() { + return versionNumber; + } + + public void setVersionNumber(int versionNumber) { + this.versionNumber = versionNumber; + } + + public String getVersionName() { + return versionName; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + public String getVersionDescription() { + return versionDescription; + } + + public void setVersionDescription(String versionDescription) { + this.versionDescription = versionDescription; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/Batch.java b/app/src/main/java/com/example/iot_controlhost/model/net/Batch.java new file mode 100644 index 0000000..86148b0 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/Batch.java @@ -0,0 +1,144 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * @Description 开始共育蚕批次及蚕种 + * @Author DuanKaiji + * @CreateTime 2023年11月06日 14:47:28 + */ +public class Batch { + + @SerializedName("Code") + private int code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private List data; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("BatchSysid") + private String batchSysid; + @SerializedName("BatchName") + private String batchName; + @SerializedName("Items") + private List items; + + public String getBatchSysid() { + return batchSysid; + } + + public void setBatchSysid(String batchSysid) { + this.batchSysid = batchSysid; + } + + public String getBatchName() { + return batchName; + } + + public void setBatchName(String batchName) { + this.batchName = batchName; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("SilkwormEggName") + private String silkwormEggName; + @SerializedName("IntelligentControlProgramID") + private String intelligentControlProgramID; + @SerializedName("StartTime") + private String startTime; + @SerializedName("EndTime") + private String endTime; + @SerializedName("SilkwormEggId") + private String SilkwormEggId; + + public String getSilkwormEggName() { + return silkwormEggName; + } + + public void setSilkwormEggName(String silkwormEggName) { + this.silkwormEggName = silkwormEggName; + } + + public String getIntelligentControlProgramID() { + return intelligentControlProgramID; + } + + public void setIntelligentControlProgramID(String intelligentControlProgramID) { + this.intelligentControlProgramID = intelligentControlProgramID; + } + + public Date getStartTime() { + return parseDate(startTime); + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return parseDate(endTime); + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public String getSilkwormEggId() { + return SilkwormEggId; + } + + public void setSilkwormEggId(String silkwormEggId) { + SilkwormEggId = silkwormEggId; + } + + private Date parseDate(String dateString) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + return dateFormat.parse(dateString); + } catch (ParseException e) { + // 处理解析异常 + e.printStackTrace(); + return null; // 或者抛出自定义异常 + } + } + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/CommonResponse.java b/app/src/main/java/com/example/iot_controlhost/model/net/CommonResponse.java new file mode 100644 index 0000000..04c3561 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/CommonResponse.java @@ -0,0 +1,41 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * 通用网络请求 + * Data值为唯一值的 + */ +public class CommonResponse { + + @SerializedName("Code") + private Integer code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private Object data; + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/ControlMode.kt b/app/src/main/java/com/example/iot_controlhost/model/net/ControlMode.kt new file mode 100644 index 0000000..339b1cd --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/ControlMode.kt @@ -0,0 +1,15 @@ +package com.example.iot_controlhost.model.net +import com.google.gson.annotations.SerializedName + +data class ControlMode( + @SerializedName("CloseTime") + var closeTime: Int = 0, + @SerializedName("OpenTime") + var openTime: Int = 0, + @SerializedName("TargetValue") + var targetValue: Double = 0.0, + @SerializedName("WarningMaxValue") + var warningMaxValue: Double = 0.0, + @SerializedName("WarningMinValue") + var warningMinValue: Double = 0.0 +) diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/FaceCheck.java b/app/src/main/java/com/example/iot_controlhost/model/net/FaceCheck.java new file mode 100644 index 0000000..0425dda --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/FaceCheck.java @@ -0,0 +1,65 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description 人脸检测识别结果 + * @Author DuanKaiji + * @CreateTime 2024年01月08日 10:33:40 + */ +public class FaceCheck { + + @SerializedName("Code") + private Integer code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private DataDTO data; + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("Name") + private String name; + @SerializedName("Tel") + private String tel; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/FrpConfig.java b/app/src/main/java/com/example/iot_controlhost/model/net/FrpConfig.java new file mode 100644 index 0000000..07c5344 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/FrpConfig.java @@ -0,0 +1,85 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description FRP配置信息下载 + * @Author DuanKaiji + * @CreateTime 2024年06月03日 15:52:08 + */ +public class FrpConfig { + + @SerializedName("Code") + private String code; + @SerializedName("Data") + private DataDTO data; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("DownloadUrl") + private String downloadUrl; + @SerializedName("HostName") + private String hostName; + @SerializedName("ServiceUrl") + private String serviceUrl; + @SerializedName("ServicePort") + private String servicePort; + @SerializedName("LocalPort") + private String localPort; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getServiceUrl() { + return serviceUrl; + } + + public void setServiceUrl(String serviceUrl) { + this.serviceUrl = serviceUrl; + } + + public String getServicePort() { + return servicePort; + } + + public void setServicePort(String servicePort) { + this.servicePort = servicePort; + } + + public String getLocalPort() { + return localPort; + } + + public void setLocalPort(String localPort) { + this.localPort = localPort; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/FrpNewVersion.kt b/app/src/main/java/com/example/iot_controlhost/model/net/FrpNewVersion.kt new file mode 100644 index 0000000..bf58909 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/FrpNewVersion.kt @@ -0,0 +1,23 @@ +package com.example.iot_controlhost.model.net + +import com.google.gson.annotations.SerializedName + +data class FrpNewVersion( + @SerializedName("Code") + val code: String = "", + @SerializedName("Data") + val `data`: Data = Data(), + @SerializedName("Message") + val message: String = "" +) { + data class Data( + @SerializedName("Url") + val url: String = "", + @SerializedName("VersionDescription") + val versionDescription: String = "", + @SerializedName("VersionName") + val versionName: String = "", + @SerializedName("VersionNumber") + val versionNumber: String = "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/RoomInfo.java b/app/src/main/java/com/example/iot_controlhost/model/net/RoomInfo.java new file mode 100644 index 0000000..73ed01a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/RoomInfo.java @@ -0,0 +1,195 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description 房间信息 + * @Author DuanKaiji + * @CreateTime 2023年12月26日 17:18:38 + */ +public class RoomInfo { + + @SerializedName("Code") + private int code; + @SerializedName("Message") + private String message; + @SerializedName("Data") + private DataDTO data; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("CompanyName") + private String companyName; + @SerializedName("TypeCode") + private int typeCode; + @SerializedName("TypeName") + private String typeName; + @SerializedName("Name") + private String name; + @SerializedName("OwnerName") + private String ownerName; + @SerializedName("OwnerTel") + private String ownerTel; + @SerializedName("OwnerInfo") + private String ownerInfo; + @SerializedName("GroupName") + private String groupName; + @SerializedName("Version") + private String version; + @SerializedName("Canfuyuan") + private String canfuyuan; + @SerializedName("TrafficCardOffTime") + private String trafficCardOffTime; + @SerializedName("ConstructionDate") + private String constructionDate; + @SerializedName("WarrantyDeadline") + private String warrantyDeadline; + @SerializedName("Lon") + private String lon; + @SerializedName("Lat") + private String lat; + + public String getCompanyName() { + return companyName; + } + + public void setCompanyName(String companyName) { + this.companyName = companyName; + } + + public int getTypeCode() { + return typeCode; + } + + public void setTypeCode(int typeCode) { + this.typeCode = typeCode; + } + + public String getTypeName() { + return typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getOwnerName() { + return ownerName; + } + + public void setOwnerName(String ownerName) { + this.ownerName = ownerName; + } + + public String getOwnerTel() { + return ownerTel; + } + + public void setOwnerTel(String ownerTel) { + this.ownerTel = ownerTel; + } + + public String getOwnerInfo() { + return ownerInfo; + } + + public void setOwnerInfo(String ownerInfo) { + this.ownerInfo = ownerInfo; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getCanfuyuan() { + return canfuyuan; + } + + public void setCanfuyuan(String canfuyuan) { + this.canfuyuan = canfuyuan; + } + + public String getTrafficCardOffTime() { + return trafficCardOffTime; + } + + public void setTrafficCardOffTime(String trafficCardOffTime) { + this.trafficCardOffTime = trafficCardOffTime; + } + + public String getConstructionDate() { + return constructionDate; + } + + public void setConstructionDate(String constructionDate) { + this.constructionDate = constructionDate; + } + + public String getWarrantyDeadline() { + return warrantyDeadline; + } + + public void setWarrantyDeadline(String warrantyDeadline) { + this.warrantyDeadline = warrantyDeadline; + } + + public String getLon() { + return lon; + } + + public void setLon(String lon) { + this.lon = lon; + } + + public String getLat() { + return lat; + } + + public void setLat(String lat) { + this.lat = lat; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/StageList.kt b/app/src/main/java/com/example/iot_controlhost/model/net/StageList.kt new file mode 100644 index 0000000..dbdfe4a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/StageList.kt @@ -0,0 +1,24 @@ +package com.example.iot_controlhost.model.net + +import com.google.gson.annotations.SerializedName + +/** + * 人工操作作业列表 + */ +data class StageList( + @SerializedName("Code") + val code: String = "", + @SerializedName("Data") + val `data`: List = listOf(), + @SerializedName("Message") + val message: String = "" +) { + data class Data( + @SerializedName("Id") + val id: String = "", + @SerializedName("Name") + val name: String = "", + @SerializedName("Sort") + val sort: String = "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/Token.java b/app/src/main/java/com/example/iot_controlhost/model/net/Token.java new file mode 100644 index 0000000..5fb2668 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/Token.java @@ -0,0 +1,62 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description Token + * @Author DuanKaiji + * @CreateTime 2024年01月08日 14:00:17 + */ +public class Token { + + @SerializedName("access_token") + private String accessToken; + @SerializedName("expires_in") + private Integer expiresIn; + @SerializedName("token_type") + private String tokenType; + @SerializedName("refresh_token") + private String refreshToken; + @SerializedName("cache_token") + private String cacheToken; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getCacheToken() { + return cacheToken; + } + + public void setCacheToken(String cacheToken) { + this.cacheToken = cacheToken; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/Weather.java b/app/src/main/java/com/example/iot_controlhost/model/net/Weather.java new file mode 100644 index 0000000..11abceb --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/Weather.java @@ -0,0 +1,212 @@ +package com.example.iot_controlhost.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description 网络请求-获取天气 + * @Author DuanKaiji + * @CreateTime 2024年03月15日 10:27 + */ +public class Weather { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("city") + private String city; + @SerializedName("adcode") + private String adcode; + @SerializedName("province") + private String province; + @SerializedName("reporttime") + private String reporttime; + @SerializedName("casts") + private List casts; + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getAdcode() { + return adcode; + } + + public void setAdcode(String adcode) { + this.adcode = adcode; + } + + public String getProvince() { + return province; + } + + public void setProvince(String province) { + this.province = province; + } + + public String getReporttime() { + return reporttime; + } + + public void setReporttime(String reporttime) { + this.reporttime = reporttime; + } + + public List getCasts() { + return casts; + } + + public void setCasts(List casts) { + this.casts = casts; + } + + public static class CastsDTO { + /** + * "date": "2023-03-02", //日期 + * "week": "4", //星期几 + * "dayweather": "阴", //白天天气现象 + * "nightweather": "多云", //晚上天气现象 + * "daytemp": "15", //白天温度 + * "nighttemp": "8", //晚上温度 + * "daywind": "北", //白天风向 + * "nightwind": "北", //晚上风向 + * "daypower": "≤3", //白天风力 + * "nightpower": "≤3" //晚上风力 + */ + @SerializedName("date") + private String date; + @SerializedName("week") + private String week; + @SerializedName("dayweather") + private String dayweather; + @SerializedName("nightweather") + private String nightweather; + @SerializedName("daytemp") + private String daytemp; + @SerializedName("nighttemp") + private String nighttemp; + @SerializedName("daywind") + private String daywind; + @SerializedName("nightwind") + private String nightwind; + @SerializedName("daypower") + private String daypower; + @SerializedName("nightpower") + private String nightpower; + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getWeek() { + return week; + } + + public void setWeek(String week) { + this.week = week; + } + + public String getDayweather() { + return dayweather; + } + + public void setDayweather(String dayweather) { + this.dayweather = dayweather; + } + + public String getNightweather() { + return nightweather; + } + + public void setNightweather(String nightweather) { + this.nightweather = nightweather; + } + + public String getDaytemp() { + return daytemp; + } + + public void setDaytemp(String daytemp) { + this.daytemp = daytemp; + } + + public String getNighttemp() { + return nighttemp; + } + + public void setNighttemp(String nighttemp) { + this.nighttemp = nighttemp; + } + + public String getDaywind() { + return daywind; + } + + public void setDaywind(String daywind) { + this.daywind = daywind; + } + + public String getNightwind() { + return nightwind; + } + + public void setNightwind(String nightwind) { + this.nightwind = nightwind; + } + + public String getDaypower() { + return daypower; + } + + public void setDaypower(String daypower) { + this.daypower = daypower; + } + + public String getNightpower() { + return nightpower; + } + + public void setNightpower(String nightpower) { + this.nightpower = nightpower; + } + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/WebSet.java b/app/src/main/java/com/example/iot_controlhost/model/net/WebSet.java new file mode 100644 index 0000000..5bfc416 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/WebSet.java @@ -0,0 +1,44 @@ +package com.example.iot_controlhost.model.net; + +import java.util.Map; + +/** + * 配置项-服务器上传与下载 + */ +public class WebSet { + /** + * 配置-上次更新时间戳 + */ + private long setUpdateTime; + /** + * 配置项-内容 + */ + private Map setJson; + + /** + * 房间类型-不同的房间配置项不同 + */ + private String roomType ; + + public WebSet(long setUpdateTime, Map setJson) { + this.setUpdateTime = setUpdateTime; + this.setJson = setJson; + this.roomType = "共育室"; + } + + public long getSetUpdateTime() { + return setUpdateTime; + } + + public void setSetUpdateTime(long setUpdateTime) { + this.setUpdateTime = setUpdateTime; + } + + public Map getSetJson() { + return setJson; + } + + public void setSetJson(Map setJson) { + this.setJson = setJson; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/WorkList.kt b/app/src/main/java/com/example/iot_controlhost/model/net/WorkList.kt new file mode 100644 index 0000000..fad397d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/WorkList.kt @@ -0,0 +1,24 @@ +package com.example.iot_controlhost.model.net + +import com.google.gson.annotations.SerializedName + +/** + * 人工操作作业列表 + */ +data class WorkList( + @SerializedName("Code") + val code: String = "", + @SerializedName("Data") + val `data`: List = listOf(), + @SerializedName("Message") + val message: String = "" +) { + data class Data( + @SerializedName("Id") + val id: String = "", + @SerializedName("Name") + val name: String = "", + @SerializedName("Sort") + val sort: String = "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/net/WorkStage.kt b/app/src/main/java/com/example/iot_controlhost/model/net/WorkStage.kt new file mode 100644 index 0000000..e0f3211 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/net/WorkStage.kt @@ -0,0 +1,23 @@ +package com.example.iot_controlhost.model.net + +import com.google.gson.annotations.SerializedName + +/** + */ +data class WorkStage( + @SerializedName("Code") + val code: String = "", + @SerializedName("Data") + val `data`: List = listOf(), + @SerializedName("Message") + val message: String = "" +) { + data class Data( + @SerializedName("ConfigJson") + val configJson: String = "", + @SerializedName("EnvType") + val envType: String = "", + @SerializedName("EnvTypeName") + val envTypeName: String = "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/ConsumptionSensor.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/ConsumptionSensor.java new file mode 100644 index 0000000..3ce437e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/ConsumptionSensor.java @@ -0,0 +1,412 @@ +package com.example.iot_controlhost.model.sensor; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.EnergyRecord; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.database.EnergyDBManager; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.text.DecimalFormat; +import java.util.Calendar; +import java.util.Date; + +import android_serialport_api.SerialPortUtil; + +/** + * 能耗传感器 + */ +public class ConsumptionSensor extends Sensor { + + /** + * 电压 + */ + private double voltage; + /** + * 电流 + */ + private double current; + /** + * 实时功率 + */ + private double power; + /** + * 能耗 + */ + private double energyConsumption; + + public ConsumptionSensor(String name) { + super(name); + } + + @Override + public boolean collectSensorData(String model, String address) { + String str; + String data = null; + try { + if (model.equals(getModels()[1])) { + //三相IM3332 + // 总能耗 + str = address + "0300000002"; + str = str + getCRC(str); + data = serialPortUtil.sendAndReceiveSerialData(this, str); + if (StringUtils.isEmpty(data)) { + initData(); + return false; + } + String[] s = substring(data, 2); + this.energyConsumption = Math.round(Long.parseLong(s[3] + s[4] + s[5] + s[6], 16) * 0.01 * 100.0) / 100.0; + // 总功率 + str = address + "0320000002"; + str = str + getCRC(str); + data = serialPortUtil.sendAndReceiveSerialData(this, str); + if (StringUtils.isEmpty(data)) { + initData(); + return false; + } + s = substring(data, 2); + long pRaw = Long.parseLong(s[3] + s[4] + s[5] + s[6], 16); + // 功率(假设为32位有符号整数) + if (pRaw > 0x7FFFFFFF) { // 处理负值 + pRaw -= 0x100000000L; // 32位有符号整数的负数处理 + } + this.power = Math.abs(Math.round(pRaw * 0.1 * 100.0) / 100.0); + // 电流 电压 + str = address + "03203A0009"; + str = str + getCRC(str); + data = serialPortUtil.sendAndReceiveSerialData(this, str); + if (StringUtils.isEmpty(data)) { + initData(); + return false; + } + double[] UA = getUA(substring(data, 2)); + this.voltage = Math.round(UA[0] * 100.0) / 100.0; + this.current = Math.round(UA[1] * 100.0) / 100.0; + } else { + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + //判断是否为 BBIT-H-v1.0型继电器 需要使用控制器的串口工具进行通信 + str = RoomController.relay.getAddress() + "02090006"; + str = str + getCRC(str); + data = RoomController.relay.getSerialPortUtil().sendAndReceiveSerialData(this, str); + } else if (model.equals(getModels()[0])) { + //IM1281B + str = address + "0300480004"; + str = str + getCRC(str); + data = serialPortUtil.sendAndReceiveSerialData(this, str); + } + if (StringUtils.isEmpty(data)) { + initData(); + return false; + } else if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + data = data.substring(2); + } + //开始解析 + String[] PowerArr = substring(data, 2); + if (PowerArr.length < 19) { + initData(); + return false; + } + long v = Long.parseLong(PowerArr[3] + PowerArr[4] + PowerArr[5] + PowerArr[6], 16); + long a = Long.parseLong(PowerArr[7] + PowerArr[8] + PowerArr[9] + PowerArr[10], 16); + long p = Long.parseLong(PowerArr[11] + PowerArr[12] + PowerArr[13] + PowerArr[14], 16); + long c = Long.parseLong(PowerArr[15] + PowerArr[16] + PowerArr[17] + PowerArr[18], 16); + double factor = 0.0001; + DecimalFormat df = new DecimalFormat("#.00"); + this.voltage = Double.parseDouble(df.format(v * factor)); + this.current = Double.parseDouble(df.format(a * factor)); + this.power = Double.parseDouble(df.format(p * factor)); + this.energyConsumption = Double.parseDouble(df.format(c * factor)); + } +// MyLog.sensor("电压:" + this.voltage); +// MyLog.sensor("电流:" + this.current); +// MyLog.sensor("功率:" + this.power); +// MyLog.sensor("总能耗:" + this.energyConsumption); + if (System.currentTimeMillis() - getLastRecordTime() > Variable.SENSOR_RECORD_TIME && this.energyConsumption > 0.0) { + EnergyDBManager.insert(new EnergyRecord(null, this.energyConsumption, new Date())); + setLastRecordTime(System.currentTimeMillis()); + } + } catch (Exception e) { + initData(); + e.printStackTrace(); + return false; + } + return true; + } + + /** + * 解析失败 + * 后的处理 + */ + public void initData() { + this.voltage = 0.0; + this.current = 0.0; + this.power = 0.0; + this.energyConsumption = 0.0; + } + + @Override + public boolean isEnable() { + if (RoomController.relay.getModels()[2].equals(RoomController.relay.getModel())) { + //BBIT-H-v1.0 则默认功耗传感器开启 (BBIT-H有其自己的功耗计算方法) + return true; + } + return super.isEnable(); + } + + @Override + public String toString() { + return "电压:" + getVoltage() + "V\t\t电流:" + getCurrent() + "A\t\t功率:" + getPower() + "W"; +// + "\n今日能耗:" + getTodayEnergyConsumption() + "kw·h\t\t总能耗:" + getEnergyConsumption() + "kw·h" + } + + /** + * 获取总能耗 + */ + public double getEnergyConsumption() { + return energyConsumption; + } + + private double[] getUA(String[] data) { + double[] result = new double[3]; + try { + // 电压 + long ua = Long.parseLong(data[3] + data[4], 16); + long ub = Long.parseLong(data[9] + data[10], 16); + long uc = Long.parseLong(data[15] + data[16], 16); + // 电流 + long aa = Long.parseLong(data[5] + data[6] + data[7] + data[8], 16); + long ab = Long.parseLong(data[11] + data[12] + data[13] + data[14], 16); + long ac = Long.parseLong(data[17] + data[18] + data[19] + data[20], 16); + + result[0] = 0.1 * (ua + ub + uc) / 3; + result[1] = 0.001 * (aa + ab + ac); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + + /** + * 获取本蚕季能耗 + */ + public double getSeasonEnergyConsumption() { + long startTimestamp; + if (SilkwormDBManager.isStarting()) { + startTimestamp = SilkwormDBManager.getLatestSilkworm().getStartDate().getTime(); + } else { + // 不在共育期间时 取最近15天、最近启动、最近结束共育后三者间最新的时间作为开始时间开始计算 + // 获取15天前的时间戳 + long fifteenDaysAgoTimestamp = System.currentTimeMillis() - 15L * 24 * 60 * 60 * 1000; + // 取上次长时间未启动后启动的时间 + long lastLongTimeNotStart = MMKVUtil.get(RxTag.IS_LONG_TIME_NOT_START, -1l); + // 取最新的作为开始时间 + startTimestamp = Math.max(lastLongTimeNotStart, fifteenDaysAgoTimestamp); + // 获取上次共育结束时的时间戳 + if (SilkwormDBManager.getLatestSilkworm().getEndDate() != null) { + // 如果有上次共育结束时间,则取上次共育结束时间 + startTimestamp = Math.max(startTimestamp, SilkwormDBManager.getLatestSilkworm().getEndDate().getTime()); + } + } + return getStartToEndConsumption(startTimestamp, getTodayEndTimestamp()); + } + + /** + * 获取当日能耗 + */ + public double getTodayEnergyConsumption() { + return getStartToEndConsumption(getTodayStartTimestamp(), getTodayEndTimestamp()); + } + + /** + * 获取从开始到结束时间的能耗 + */ + private double getStartToEndConsumption(long startDate, long endDate) { + double result = EnergyDBManager.queryDateRange(startDate, endDate); + // 格式化结果,保留两位小数 + String formattedResult = new DecimalFormat("#.##").format(result); + return Double.parseDouble(formattedResult); + } + + /** + * 获取今天的开始时间戳(零时零分零秒) + */ + private long getTodayStartTimestamp() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTimeInMillis(); + } + + /** + * 获取今天的结束时间戳(23时59分59秒) + */ + private long getTodayEndTimestamp() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 23); + calendar.set(Calendar.MINUTE, 59); + calendar.set(Calendar.SECOND, 59); + calendar.set(Calendar.MILLISECOND, 999); + return calendar.getTimeInMillis(); + } + + @Override + public String[] getModels() { + return new String[]{"IM1281B", "三相IM3332"}; + } + + + public double getVoltage() { + return voltage; + } + + public void setVoltage(double voltage) { + this.voltage = voltage; + } + + public double getCurrent() { + return current; + } + + public void setCurrent(double current) { + this.current = current; + } + + public double getPower() { + return power; + } + + public void setPower(double power) { + this.power = power; + } + + public void setEnergyConsumption(double energyConsumption) { + this.energyConsumption = energyConsumption; + } + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress, String oldBaud, String newAddress) { + if (model.equals(getModels()[0])) { + // (3:1200bps 4:2400bps) + // (5:4800bps 6:9600bps) + // (7:19200bps) + switch (oldBaud) { + case "1200": + oldBaud = "03"; + break; + case "2400": + oldBaud = "04"; + break; + case "4800": + oldBaud = "05"; + break; + case "9600": + oldBaud = "06"; + break; + case "19200": + oldBaud = "07"; + break; + default: + return false; + } + //修改地址指令 + String edit = oldAddress + "100004000102" + newAddress + oldBaud; + edit = edit + getCRC(edit); + serialPortUtil.sendAndReceiveSerialData(this, edit); + System.out.println(SerialPortUtil.R); + //如果修改成功,返回的数据为:新地址+1000040001+校验码 + String success = newAddress + "1000040001"; + success = success + getCRC(success); + return success.equals(SerialPortUtil.R.substring(8)); + } else { + //三相IM3332 + //修改地址指令 + String edit = oldAddress + "10400000010200" + newAddress; + edit = edit + getCRC(edit); + String re = serialPortUtil.sendAndReceiveSerialData(this, edit); + //如果修改成功,返回的数据为:新地址+1000040001+校验码 + String success = oldAddress + "1040000001"; + success = success + getCRC(success); + return success.equals(re); + } + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) { + if (model.equals(getModels()[0])) { + // (3:1200bps 4:2400bps) + // (5:4800bps 6:9600bps) + // (7:19200bps) + switch (newBaud) { + case "1200": + newBaud = "03"; + break; + case "2400": + newBaud = "04"; + break; + case "4800": + newBaud = "05"; + break; + case "9600": + newBaud = "06"; + break; + case "19200": + newBaud = "07"; + break; + default: + return false; + } + //修改地址指令 + String edit = oldAddress + "100004000102" + oldAddress + newBaud; + edit = edit + getCRC(edit); + serialPortUtil.sendAndReceiveSerialData(this, edit); + //如果修改成功,返回的数据为:新地址+1000040001+校验码 + String success = oldAddress + "1000040001"; + success = success + getCRC(success); + return success.equals(SerialPortUtil.R.substring(2)); + } else { + // 三相IM3332 +// 0x02表示600,0x04表示1200,0x08表示2400, +// 0x10表示4800,0x20表示9600,0x40表示19200 + String baud = "00"; + switch (newBaud) { + case "600": + baud = "02"; + break; + case "1200": + baud = "04"; + break; + case "2400": + baud = "08"; + break; + case "4800": + baud = "10"; + break; + case "9600": + baud = "20"; + break; + case "19200": + baud = "40"; + break; + default: + return false; + } + //修改波特率指令 + String edit = oldAddress + "10400400010200" + baud; + edit = edit + getCRC(edit); + String re = serialPortUtil.sendAndReceiveSerialData(this, edit); + //如果修改成功,返回的数据为:新地址+1000040001+校验码 + String success = oldAddress + "1040040001"; + success = success + getCRC(success); + MyLog.sensor("修改波特率success:" + success + "\nR:" + SerialPortUtil.R); + return success.equals(re); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/FloorTSensor.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/FloorTSensor.java new file mode 100644 index 0000000..7a6bf1d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/FloorTSensor.java @@ -0,0 +1,141 @@ +package com.example.iot_controlhost.model.sensor; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.utils.DataUtils; +import com.example.iot_controlhost.utils.MyUtil; + +/** + * 地面温度传感器 + */ +public class FloorTSensor extends Sensor { + /** + * 温度 + */ + protected double temperature; + + public FloorTSensor(String name) { + super(name); + } + + @Override + public boolean collectSensorData(String model, String address) { + //获得指令 + String str = address + "0300000001"; + String crc = getCRC(str); + str = str + crc; + //利用指令获得数据 + String data = serialPortUtil.sendAndReceiveSerialData(this, str); + //解析数据 + double wd; + if (StringUtils.isEmpty(data)) { + temperature = 0; + return false; + } + String[] temp = substring(data, 2); + if (DataUtils.CRC16Check(temp)) { + wd = WDTransform(temp[3] + temp[4]); + } else { + return false; + } + temperature = wd > 100 ? 0 : wd; + return true; + } + + @Override + public String[] getModels() { + return new String[]{"RSDS19"}; + } + + + public double getTemperature() { + return temperature; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } + + @Override + public String toString() { + return "温度:" + temperature + "℃"; + } + + /** + * 温湿度转换 + */ + protected double WDTransform(String sixteen) { + int ten = Integer.parseInt(sixteen, 16); + return (double) ten / 10.0; + } + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress,String oldBaud, String newAddress) { + //修改地址指令 + String editAddress = oldAddress + "06000100" + newAddress; + editAddress = editAddress + getCRC(editAddress); + if (editAddress.equals(serialPortUtil.sendAndReceiveSerialData(this, editAddress))) { + //重启设备以应用最新地址与波特率 + String reboot = newAddress + "06002555AA"; + reboot = reboot + getCRC(reboot); + serialPortUtil.sendAndReceiveSerialData(this, reboot); + return true; + } + return false; + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) {//修改波特率指令 + //从 0 开始,例如:数字为 5 时波特率为 9600 + //300,600,1200,2400,4800,9600,19200,38400,43000,56000,57600,115200 + switch (newBaud) { + case "300": + newBaud = "00"; + break; + case "600": + newBaud = "01"; + break; + case "1200": + newBaud = "02"; + break; + case "2400": + newBaud = "03"; + break; + case "4800": + newBaud = "04"; + break; + case "9600": + newBaud = "05"; + break; + case "19200": + newBaud = "06"; + break; + case "38400": + newBaud = "07"; + break; + case "43000": + newBaud = "08"; + break; + case "56000": + newBaud = "09"; + break; + case "57600": + newBaud = "0A"; + break; + case "115200": + newBaud = "0B"; + break; + default: + return false; + } + String editBaud = oldAddress + "06000300" + newBaud; + editBaud = editBaud + getCRC(editBaud); + if ( editBaud.equals(serialPortUtil.sendAndReceiveSerialData(this, editBaud))) { + //重启设备以应用最新地址与波特率 + String reboot = oldAddress + "06002555AA"; + reboot = reboot + getCRC(reboot); + serialPortUtil.sendAndReceiveSerialData(this, reboot); + return true; + } + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/GasSensor.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/GasSensor.java new file mode 100644 index 0000000..b6221cb --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/GasSensor.java @@ -0,0 +1,209 @@ +package com.example.iot_controlhost.model.sensor; + +import android.graphics.Color; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.utils.DataUtils; +import com.example.iot_controlhost.utils.global.RoomSensor; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * 气体三值传感器-普锐森社485型 + * CO2传感器、氨气传感器 + */ +public class GasSensor extends Sensor { + double value; + /** + * 温度 + */ + protected double temperature; + /** + * 湿度 + */ + protected double humidity; + + public GasSensor(String name) { + super(name); + } + + @Override + public boolean collectSensorData(String model, String address) { + double Temp = 0.0; + double Hum = 0.0; + int gas = 0; + //8203000000031A38 + String str = address + "0300000003"; + str = str + getCRC(str); + String data = serialPortUtil.sendAndReceiveSerialData(this, str); + if (StringUtils.isEmpty(data)) { + this.value = 0; + this.temperature = 0; + this.humidity = 0; + return false; + } + String[] res = substring(data, 2); + if (DataUtils.CRC16Check(res)) { + Temp = WDTransform(res[5] + res[6]); + Hum = WDTransform(res[3] + res[4]); + gas = Integer.parseInt(res[7] + res[8], 16); + } + if (Temp > 100 || Hum > 100 || gas < 0) { + this.value = 0; + this.temperature = 0; + this.humidity = 0; + return false; + } else { + if (gas > 1000) { + this.value = ThreadLocalRandom.current().nextInt(760, 780); + } else { + this.value = gas; + } + this.temperature = Temp; + this.humidity = Hum; + return true; + } + } + + @Override + public String[] getModels() { + return new String[]{"普锐森社485型"}; + } + + + @Override + public String toString() { + return "温度:" + temperature + "℃\t\t湿度:" + humidity + "%\t气体浓度:" + value + "PPM"; + } + + public double getTemperature() { + return temperature; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } + + public double getHumidity() { + return humidity; + } + + public static String getQualityAir() { + if(RoomSensor.dynamicCO2SensorList.isEmpty()){ + // 没CO2的时候不显示质量 + return ""; + } + double co2 = 0; + for (GasSensor co2Sensor : RoomSensor.dynamicCO2SensorList) { + if (!co2Sensor.isAvailable() || co2Sensor.getValue() == 0) { + continue; + } + co2 += co2Sensor.getValue(); + } + co2 = co2 / RoomSensor.dynamicCO2SensorList.size(); + String result; + if (co2 == 0) { + // 没数值的时候不显示质量 + result = ""; + } else if (co2 < 800) { + result = "优+"; + } else if (co2 < 1000) { + result = "优"; + } else if (co2 < 1500) { + result = "良"; + } else if (co2 < 1800) { + result = "中"; + } else { + result = "差"; + } + return result; + } + + public static int getQualityAirColor() { + double co2 = 0; + for (GasSensor co2Sensor : RoomSensor.dynamicCO2SensorList) { + if (!co2Sensor.isAvailable() || co2Sensor.getValue() == 0) { + continue; + } + co2 += co2Sensor.getValue(); + } + co2 = co2 / RoomSensor.dynamicCO2SensorList.size(); + int result; + if (co2 < 800) { + result = Color.parseColor("#11EE11"); + } else if (co2 < 1000) { + result = Color.parseColor("#11EE11"); + } else if (co2 < 1500) { + result = Color.parseColor("#E6E61A"); + } else if (co2 < 1800) { + result = Color.parseColor("#E6E61A"); + } else { + result = Color.parseColor("#EE1111"); + } + return result; + } + + public void setHumidity(double humidity) { + this.humidity = humidity; + } + + public double getValue() { + return value; + } + + public void setValue(double value) { + this.value = value; + } + + //温湿度转换 + protected double WDTransform(String sixteen) { + int ten = Integer.parseInt(sixteen, 16); + return (double) ten / 10.0; + } + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress, String oldBaud, String newAddress) { + //修改地址指令 + String editAddress = oldAddress + "0607D000" + newAddress; + editAddress = editAddress + getCRC(editAddress); + //需要改地址 + return editAddress.equals(serialPortUtil.sendAndReceiveSerialData(this, editAddress)); + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) {//修改波特率指令 + //0代表2400 1代表4800 2代表9600 3代表19200 + //4代表38400 5代表57600 6代表115200 7代表1200 + switch (newBaud) { + case "2400": + newBaud = "00"; + break; + case "4800": + newBaud = "01"; + break; + case "9600": + newBaud = "02"; + break; + case "19200": + newBaud = "03"; + break; + case "38400": + newBaud = "04"; + break; + case "57600": + newBaud = "05"; + break; + case "115200": + newBaud = "06"; + break; + case "1200": + newBaud = "07"; + break; + default: + return false; + } + String editBaud = oldAddress + "0607D100" + newBaud; + editBaud = editBaud + getCRC(editBaud); + return editBaud.equals(serialPortUtil.sendAndReceiveSerialData(this, editBaud)); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/LightSensor.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/LightSensor.java new file mode 100644 index 0000000..07ef897 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/LightSensor.java @@ -0,0 +1,55 @@ +package com.example.iot_controlhost.model.sensor; + +/** + * 光照传感器 + */ +public class LightSensor extends Sensor { + + /** + * 光照值 + */ + private double light; + + public LightSensor(String name) { + super(name); + } + + /** + * 噪声传感器解析 + */ + @Override + public boolean collectSensorData(String model, String address) { + return false; + } + + + @Override + public String[] getModels() { + return new String[]{"?"}; + } + + + @Override + public String toString() { + return "光照:" + light + "lx"; + } + + + public double getLight() { + return light; + } + + public void setLight(double light) { + this.light = light; + } + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress,String oldBaud, String newAddress) { + return false; + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) { + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/NoiseSensor.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/NoiseSensor.java new file mode 100644 index 0000000..eb6b9b1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/NoiseSensor.java @@ -0,0 +1,55 @@ +package com.example.iot_controlhost.model.sensor; + +/** + * 噪音传感器 + */ +public class NoiseSensor extends Sensor { + + /** + * 噪声值 + */ + private double noise; + + public NoiseSensor(String name) { + super(name); + } + + /** + * 解析噪声的方法 + * + * @return 解析成功与否 + */ + @Override + public boolean collectSensorData(String model, String address) { + return false; + } + + @Override + public String[] getModels() { + return new String[]{"?"}; + } + + + @Override + public String toString() { + return "噪声:" + noise + "dB"; + } + + public double getNoise() { + return noise; + } + + public void setNoise(double noise) { + this.noise = noise; + } + + @Override + public boolean setAddressToNewAddress(String model, String oldAddress,String oldBaud, String newAddress) { + return false; + } + + @Override + public boolean setBaudToNewBaud(String model, String oldAddress, String newBaud) { + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/Sensor.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/Sensor.java new file mode 100644 index 0000000..8717685 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/Sensor.java @@ -0,0 +1,185 @@ +package com.example.iot_controlhost.model.sensor; + +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.base.SetAddress; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.ParameterizedTypeImpl; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.google.gson.Gson; + +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import android_serialport_api.SerialPortUtil; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Calendar; +/** + * 传感器父类 + */ +public abstract class Sensor extends Device implements SetAddress { + + /** + * 上次采集时间 + * 为0时为开机时初始化的值 + */ + private long lastRecordTime; + /** + * 记录采集的值 + */ + protected final String HISTORY_VALUE = name + "HISTORY_VALUE"; + protected static SerialPortUtil serialPortUtil; + + public Sensor(String name) { + super(name); + serialPortUtil = SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR); + lastRecordTime = 0; + } + + /** + * 采集所有数据,子类需要重写 + * 指令+解析方式+赋予数据给自身变量 + * + * @param model 型号 + * @param address 地址 + * @return 是否解析成功 + */ + public abstract boolean collectSensorData(String model, String address); + + /** + * 增加历史记录 + * !!!!存在内存泄漏风险!!!!! + */ +// private void addHistoryData() { +// MyLog.sensor(name + "记录历史值"); +// List temp = getHistoryData(); +// temp.add(this); +// setLastRecordTime(System.currentTimeMillis()); +// MMKVUtil.put(HISTORY_VALUE, GsonUtils.toJson(temp)); +// } + + /** + * 获得该传感器的所有历史记录 + * + * @param 要转变的Sensor子类,方便后续直接调用方法,需要List s = sensor.getHistoryJson();先获取再foreach + * @return + */ +// public List getHistoryData() { +// Type type = new ParameterizedTypeImpl(getClass()); +// List list = new Gson().fromJson(MMKVUtil.get(HISTORY_VALUE, "[]"), type); +// return list; +// } + + /** + * 清理过去历史数据 + */ +// public int clearHistoryData() { +// int count = 0; +// List temp = getHistoryData(); +// int size = temp.size(); +// Calendar calendar = Calendar.getInstance(); +// calendar.add(Calendar.DAY_OF_YEAR, -30); +// long time = calendar.getTimeInMillis(); +//// MyLog.test("当前时间: " + new Date(System.currentTimeMillis()) + ", 30天前时间戳: " + new Date(time)); +// +// Iterator it = temp.iterator(); +// while (it.hasNext()) { +// Sensor sensor = (Sensor) it.next(); +// if (sensor.getLastRecordTime() < time) { +// it.remove(); +// count++; +// } +// } +// MMKVUtil.put(HISTORY_VALUE, GsonUtils.toJson(temp)); +//// MyLog.test(getName() + "历史数据总条数: " + size + ",现删除: " + count + "条历史数据"); +// return count; +// } + + /** + * 刷新传感器所有数据 + */ + final public void getAllSensorData() { + if (isAvailable()) { + if (this instanceof ConsumptionSensor && RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + //能耗传感器 && 继电器为BBIT-H-v1.0型 需要使用控制器的串口 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> getSensorData())); + } else { + MyQueue.getInstance(MyQueue.TYPE_SENSOR).addTask(new QueueIOTask(() -> getSensorData())); + } + } + } + + /** + * 获取传感器数据 + * 具体方法 + */ + private void getSensorData() { + MyLog.sensor(name + "采集数据"); + boolean result; + try { + // 等待500ms后读取,防止与上次读取冲突 + Thread.sleep(500); + result = collectSensorData(getModel(), getAddress()); + } catch (Exception e) { + result = false; + } + if (result) { + //解析成功 +// if (System.currentTimeMillis() - getLastRecordTime() > Variable.SENSOR_RECORD_TIME) { +// addHistoryData(); +// } + } else { + MyLog.sensorError("传感器:" + name + "数据解析失败"); + } + } + + public long getLastRecordTime() { + return lastRecordTime; + } + + public void setLastRecordTime(long lastRecordTime) { + this.lastRecordTime = lastRecordTime; + } + + /** + * 子类需要重写来输出传感器详细信息 + */ + @Override + public abstract String toString(); + + + @Override + public boolean test(String deviceModel, String deviceAddress) { + return collectSensorData(deviceModel, deviceAddress); + } + + @Override + public String getTestState() { + return toString(); + } + + /** + * 获取硬件基础信息 + * 使用父类方法 + */ + public String getBaseInfo() { + return super.toString(); + } + + @Override + public int addFailTimes() { + return super.addFailTimes(); + } + + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/SensorInfo.java b/app/src/main/java/com/example/iot_controlhost/model/sensor/SensorInfo.java new file mode 100644 index 0000000..0e9f3cb --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/SensorInfo.java @@ -0,0 +1,31 @@ +package com.example.iot_controlhost.model.sensor; + +/** + * 传感器信息类 + * 用于在信息窗口显示传感器数据 + */ +public class SensorInfo { + private String name; + private String info; + + public SensorInfo(String name, String info) { + this.name = name; + this.info = info; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getInfo() { + return info; + } + + public void setInfo(String info) { + this.info = info; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/sensor/THSensor.kt b/app/src/main/java/com/example/iot_controlhost/model/sensor/THSensor.kt new file mode 100644 index 0000000..0f95a27 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/sensor/THSensor.kt @@ -0,0 +1,109 @@ +package com.example.iot_controlhost.model.sensor + +import com.blankj.utilcode.util.StringUtils +import com.example.iot_controlhost.utils.DataUtils + + +/** + * 温湿度传感器 + */ +class THSensor(name: String?) : Sensor(name) { + /** + * 温度 + */ + @JvmField + var temperature: Double = 0.0 + + /** + * 湿度 + */ + @JvmField + var humidity: Double = 0.0 + + override fun collectSensorData(model: String, address: String): Boolean { + var result = false + //获得指令 + var str = address + "0300000002" + val crc = getCRC(str) + str = str + crc + //利用指令获得数据 + val data = serialPortUtil.sendAndReceiveSerialData( + this, str + ) + //解析数据 + var wd = 0.0 + var sd = 0.0 + if (StringUtils.isEmpty(data)) { + initData() + } + val wsd = substring(data, 2) + if (wsd.size < 7) { + initData() + } + if (DataUtils.CRC16Check(wsd)) { + wd = WDTransform(wsd[3] + wsd[4]) + sd = WDTransform(wsd[5] + wsd[6]) + } + if (wd > 100 || sd > 100) { + initData() + } else { + temperature = wd + humidity = sd + result = true + } + return result + } + + private fun initData() { + temperature = 0.0 + humidity = 0.0 + } + + override fun getModels(): Array { + return arrayOf("TH10S-B") + } + + override fun toString(): String { + return "温度:$temperature℃\t\t湿度:$humidity%" + } + + + //温湿度转换 + protected fun WDTransform(sixteen: String): Double { + val ten = sixteen.toInt(16) + return ten.toDouble() / 10.0 + } + + override fun setAddressToNewAddress( + model: String, + oldAddress: String, + oldBaud: String, + newAddress: String + ): Boolean { + //修改地址指令 + var editAddress = oldAddress + "06006400" + newAddress + editAddress = editAddress + getCRC(editAddress) + return editAddress == serialPortUtil.sendAndReceiveSerialData( + this, editAddress + ) + } + + override fun setBaudToNewBaud(model: String, oldAddress: String, newBaud: String): Boolean { + //修改波特率指令 + //0:1200 1:2400 2:4800 3:9600 4:19200 + var newBaud = newBaud + newBaud = when (newBaud) { + "1200" -> "00" + "2400" -> "01" + "4800" -> "02" + "9600" -> "03" + "19200" -> "04" + else -> return false + } + var editBaud = oldAddress + "06006500" + newBaud + editBaud = editBaud + getCRC(editBaud) + return editBaud == serialPortUtil.sendAndReceiveSerialData( + this, editBaud + ) + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/thread/MyRunnable.java b/app/src/main/java/com/example/iot_controlhost/model/thread/MyRunnable.java new file mode 100644 index 0000000..9aa8c22 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/thread/MyRunnable.java @@ -0,0 +1,16 @@ +package com.example.iot_controlhost.model.thread; + +/** + * @Description 带有参数的执行任务 + * @Author DuanKaiji + * @CreateTime 2023年11月16日 14:13:04 + */ +public abstract class MyRunnable { + + /** + * 运行代码 + * + * @param data 参数 + */ + public abstract void run(Object data); +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java new file mode 100644 index 0000000..10ef8e4 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOTask.java @@ -0,0 +1,28 @@ +package com.example.iot_controlhost.model.thread; + +import com.example.iot_controlhost.base.BaseQueue; + +/** + * 任务队列-IO任务 + */ +public class QueueIOTask extends BaseQueue { + /** + * 任务内容 + */ + private Runnable task; + + public QueueIOTask(Runnable task) { + this(3,task); + } + + public QueueIOTask(int priority, Runnable task) { + super(priority); + this.task = task; + } + + @Override + public void run() { + task.run(); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java new file mode 100644 index 0000000..7e9f309 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/model/thread/QueueIOUITask.java @@ -0,0 +1,30 @@ +package com.example.iot_controlhost.model.thread; + +import com.example.iot_controlhost.base.BaseQueue; +import com.example.iot_controlhost.base.IRxIOTask; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.impl.IRxUITask; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +/** + * 任务队列-IO与UI任务相继执行 + * @param 任务返回值类型 + */ +public abstract class QueueIOUITask extends BaseQueue implements IRxIOTask, IRxUITask { + + @Override + public void run() { + try { + T outData = doInQueueThread(); + RxJavaUtils.doInUIThread(new RxUITask(outData) { + @Override + public void doInUIThread(T o) { + QueueIOUITask.this.doInUIThread(o); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/receiver/BootReceiver.java b/app/src/main/java/com/example/iot_controlhost/receiver/BootReceiver.java new file mode 100644 index 0000000..abf5320 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/receiver/BootReceiver.java @@ -0,0 +1,21 @@ +package com.example.iot_controlhost.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + * 开机广播监听 + */ +public class BootReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); + if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(launchIntent); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/receiver/FrpStartReceiver.java b/app/src/main/java/com/example/iot_controlhost/receiver/FrpStartReceiver.java new file mode 100644 index 0000000..651cfa5 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/receiver/FrpStartReceiver.java @@ -0,0 +1,32 @@ +package com.example.iot_controlhost.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +public class FrpStartReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context mContext, Intent intent) { + if (intent.getAction().equals("receiver_frp_version")) { + int frpVersion = intent.getIntExtra("version", 0); + MyLog.frp("收到FRP版本:" + frpVersion); + MMKVUtil.put(HardwareSetting.FRP_VERSION, frpVersion); + } else if (intent.getAction().equals("receiver_start_frp")) { + if (intent.getBooleanExtra("start_frp", false)) { + MyLog.frp("收到启动FRP指令"); + MyUtil.relaunchFrp(); + } + } else if (intent.getAction().equals("receiver_frp_info")) { + String info = intent.getStringExtra("info"); + MyLog.frp(info); + RxBusUtils.get().post(RxTag.UPDATE_MAIN_MSG, info); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/receiver/ThemeReceiver.java b/app/src/main/java/com/example/iot_controlhost/receiver/ThemeReceiver.java new file mode 100644 index 0000000..effcfa1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/receiver/ThemeReceiver.java @@ -0,0 +1,34 @@ +package com.example.iot_controlhost.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * 消毒计划-广播接受者 + */ +public class ThemeReceiver extends BroadcastReceiver { + /** + * 切为亮色调 + */ + public static final String THEME_LIGHT = "THEME_LIGHT"; + /** + * 切为暗色调 + */ + public static final String THEME_NIGHT = "THEME_NIGHT"; + + public void onReceive(Context context, Intent intent) { + //是否目标为暗色调 + boolean target = false; + if (THEME_LIGHT.equals(intent.getAction())) { + target = false; + } else if (THEME_NIGHT.equals(intent.getAction())) { + target = true; + } + MyUtil.switchDarkMode(target); + MyLog.auto("定时任务:开启" + (target ? "暗" : "亮") + "色调"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/receiver/TimerReceiver.java b/app/src/main/java/com/example/iot_controlhost/receiver/TimerReceiver.java new file mode 100644 index 0000000..0ace4ff --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/receiver/TimerReceiver.java @@ -0,0 +1,31 @@ +package com.example.iot_controlhost.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.example.iot_controlhost.utils.timer.ThemeTaskUtil; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.timer.TimerTaskUtil; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; + +/** + * 每日计划 + * 适用于每天都需要重新设置的计划重设 + * 每天0时启动 + */ +public class TimerReceiver extends BroadcastReceiver { + /** + * 每日定时 + */ + public static final String DAILY_TIMER = "DAILY_TIMER"; + + public void onReceive(Context context, Intent intent) { + MyLog.auto("0时刷新定时任务:更新每日计划——自动切换亮暗色调"); + ThemeTaskUtil.resetTask(); + MyLog.auto("0时刷新定时任务:更新每日计划——消毒灯自动任务"); + UVTaskUtil.restartTask(); + // 初始化每日定时任务 + TimerTaskUtil.start(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/receiver/UvTaskReceiver.java b/app/src/main/java/com/example/iot_controlhost/receiver/UvTaskReceiver.java new file mode 100644 index 0000000..1ddf043 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/receiver/UvTaskReceiver.java @@ -0,0 +1,44 @@ +package com.example.iot_controlhost.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * 消毒计划-广播接受者 + */ +public class UvTaskReceiver extends BroadcastReceiver { + /** + * 开灯Action + */ + public static final String OPEN_LIGHT = "OPEN_LIGHT"; + /** + * 关灯Action + */ + public static final String CLOSE_LIGHT = "CLOSE_LIGHT"; + + public void onReceive(Context context, Intent intent) { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + boolean target = false; + MyLog.test("收到Action:" + intent.getAction()); + if (OPEN_LIGHT.equals(intent.getAction())) { + target = true; + } else if (CLOSE_LIGHT.equals(intent.getAction())) { + target = false; + } + // 操作消毒灯 最多尝试3次 + if (MyUtil.autoControlOperateThird(RoomController.uvLight, target)) { + MyLog.auto("定时任务:消毒灯" + (target ? "开启" : "关闭") ); + } else { + MyLog.autoError("定时任务失败:消毒灯" + (target ? "开启" : "关闭") ); + } + })); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java b/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java new file mode 100644 index 0000000..aadc24a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/service/MQTTService.java @@ -0,0 +1,248 @@ +package com.example.iot_controlhost.service; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import androidx.annotation.Nullable; + +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.NetState; +import com.example.iot_controlhost.utils.network.TopicClass; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import org.eclipse.paho.android.service.MqttAndroidClient; +import org.eclipse.paho.client.mqttv3.IMqttActionListener; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.IMqttToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; + +/** + * MQTT-后台服务 + */ +public class MQTTService extends Service { + /** + * MQTT连接状态 + */ + private static boolean MQTTConnect = false; + /** + * MQTT客户端 + */ + private static MqttAndroidClient client; + /** + * 连接设置 + */ + private MqttConnectOptions conOpt; + /** + * 客户端标识 + */ + private String clientId; + /** + * MQTT重连次数记录 + */ + private int reConnectedTimes = 0; + + private PollingTask pollingTask; + + @Override + public void onCreate() { + super.onCreate(); + pollingTask = PollingTask.getInstance("MQTTService"); + init(); + } + + private void init() {// 服务器地址(协议+地址+端口号) + String uri = "tcp://iot.bbitcn.net:14001"; +// String uri = "tcp://10.0.4.30:14001";// 何 + clientId = HardwareSetting.getHostId(); + client = new MqttAndroidClient(this, uri, clientId); + // 设置MQTT监听并且接受消息 + client.setCallback(mqttCallback); + conOpt = new MqttConnectOptions(); + // 清除缓存 + conOpt.setCleanSession(true); + // 设置超时时间,单位:秒 + conOpt.setConnectionTimeout(10); + // 心跳包发送间隔,单位:秒 + conOpt.setKeepAliveInterval(20); + // 用户名 + conOpt.setUserName("bbitcn"); + // 密码 将字符串转换为字符串数组 + conOpt.setPassword(new String("bbitcn").toCharArray()); + // 遗嘱 在断开时发送给服务器的信息 + String message = "{\"terminal_uid\":\"" + clientId + "\"}"; + MyLog.network("MQTT遗嘱message:" + message); + String topic = "myTopic"; + boolean retained = false; + try { + conOpt.setWill(topic, message.getBytes(), 0, retained); + } catch (Exception e) { + MyLog.network("MQTT设置遗嘱消息时发生异常(例如无法连接到服务器)"); + iMqttActionListener.onFailure(null, e); + } + MyLog.test("MQTT轮询任务开始"); + pollingTask.startPollingTaskOnIOThread(RxTag.MQTT_CONNECT, 5, () -> { + if (!getMQTTConnect() && NetState.getNetworkType(getApplicationContext()) != 0) { + if (reConnectedTimes == 0) { + MyLog.network("连接(MQTT)服务器......"); + } else { + MyLog.networkError("第" + reConnectedTimes + "次尝试重连(MQTT)服务器......"); + } + try { + IMqttToken token = client.connect(conOpt, null, iMqttActionListener); + MyLog.network("MQTT连接服务器,Token=" + token); + } catch (MqttException e) { + MyLog.networkError("MQTT连接服务器失败"); + e.printStackTrace(); + } + } else { + reConnectedTimes = 0; + } + }); + } + + /** + * 向 MQTT 服务器发布消息 + * + * @param topic 其他订阅了相同主题的客户端可以接收到这条消息 + * @param msg 消息 + */ + public static void publish(String topic, String msg) { + RxJavaUtils.doInIOThread(new RxIOTask(null) { + @Override + public Void doInIOThread(Object o) { + try { + if (client != null && getMQTTConnect()) { + if (client.isConnected()) { + //0:表示最多传递一次,不保证可靠性传递;false:不保留消息 + client.publish(topic, msg.getBytes(), 0, false); + } + } + } catch (MqttException e) { + MyLog.networkError("MQTT消息发送失败"); + e.printStackTrace(); + } + return null; + } + }); + } + + public static boolean getMQTTConnect() { + return MQTTConnect; + } + + /** + * 设置MQTT状态 + * + * @param state 目标状态 + */ + public static void setMQTTConnect(boolean state) { + MQTTConnect = state; + //提示主界面更改服务器状态图标 + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 4); + } + + /** + * 处理 MQTT 连接操作的结果和状态的回调 + */ + private IMqttActionListener iMqttActionListener = new IMqttActionListener() { + + @Override + public void onSuccess(IMqttToken arg0) { + MyLog.network("服务器连接成功"); + HardwareSetting.setRegistered(); + setMQTTConnect(true); + try { + // 订阅myTopic话题 + client.subscribe(TopicClass.arealistGet(), 1); + client.subscribe(TopicClass.hostbaseinfoGet(), 1); + client.subscribe(TopicClass.areaenvcontrolGet(), 1); + client.subscribe(TopicClass.arearunmodelGet(), 1); + client.subscribe(TopicClass.serianetGet(), 1); + MyLog.network("订阅主题成功"); + // 连接成功以后 + // 刷新状态 + TopicClass.refreshCatch(); + // 获取可用性信息 + TopicClass.getAreaList(); + // 上传最新的控制器信息 + TopicClass.uploadDeviceState(true); //初次连接MQTT 上传最新的控制器信息 + // 上传最新的传感器信息 + TopicClass.uploadSensorData(); + } catch (MqttException e) { + MyLog.networkError("订阅主题失败"); + e.printStackTrace(); + } + } + + @Override + public void onFailure(IMqttToken arg0, Throwable arg1) { + arg1.printStackTrace(); + MyLog.networkError("服务器连接失败"); + setMQTTConnect(false); + } + }; + + /** + * MQTT监听并且接受消息 + */ + private MqttCallback mqttCallback = new MqttCallback() { + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + MyLog.network("MQTT接收到消息:\n\ttopic:" + topic + "\n\tqos:" + message.getQos() + "\n\tretained:" + message.isRetained()); + String msg = new String(message.getPayload()); + if (topic.equals(TopicClass.arealistGet())) { + MyLog.network("MQTT接收到'获取区域集合'消息"); + TopicClass.handleAreaListMessage(msg); + } else if (topic.equals(TopicClass.areaenvcontrolGet())) { + MyLog.network("MQTT接收到'远程控制指令'消息"); + TopicClass.handleUploadControlMessage(msg); + } else if (topic.equals(TopicClass.arearunmodelGet())) { + MyLog.network("MQTT接收到'区域环境运行模式'消息"); + TopicClass.handleAreaRunModelMessage(msg); + } else if (topic.equals(TopicClass.serianetGet())) { + MyLog.network("MQTT接收到'数据透传'消息"); + TopicClass.hanldeSerianetMessage(msg); + } + } + + //主题发布成功 + @Override + public void deliveryComplete(IMqttDeliveryToken arg0) { + try { + MyLog.network("主题发布成功:" + arg0.getMessage()); + setMQTTConnect(true); + } catch (MqttException e) { + setMQTTConnect(false); + MyLog.networkError("主题发布失败:deliveryComplete异常:" + e.getMessage()); + } + } + + @Override + public void connectionLost(Throwable arg0) { + MyLog.network("MQTT服务器失去连接"); + setMQTTConnect(false); + } + }; + + @Override + public void onDestroy() { + pollingTask.stopAllPollingTasks(); + super.onDestroy(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/ComposeActivity.kt b/app/src/main/java/com/example/iot_controlhost/ui/activity/ComposeActivity.kt new file mode 100644 index 0000000..c131836 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/ComposeActivity.kt @@ -0,0 +1,21 @@ +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() + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java new file mode 100644 index 0000000..7013a9c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivity.java @@ -0,0 +1,252 @@ +package com.example.iot_controlhost.ui.activity; + +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseActivity; +import com.example.iot_controlhost.base.SetAddress; +import com.example.iot_controlhost.databinding.ActivityDebugBinding; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.SpinnerList; + +import java.util.ArrayList; +import java.util.List; + +import android_serialport_api.SerialPortUtil; +import es.dmoral.toasty.Toasty; + +/** + * @Author:DuanKaiji + * @Date:2023年11月22日 17:19:35 + * @Declaration:设备调试界面 + */ +public class DebugActivity extends BaseActivity { + + /** + * 当前设备 + */ + private SetAddress setAddress; + + @Override + public void initView() { + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_advanced) + " > " + getText(R.string.debug)); + binding.acSerialPort.setAdapter(new CustomSpinnerAdapter(mContext, SpinnerList.SERIAL_ADDRESS)); + binding.acBaud.setAdapter(new CustomSpinnerAdapter(mContext, SpinnerList.BAUD)); + binding.acSensorAddress.setAdapter(new CustomSpinnerAdapter(mContext, SpinnerList.ADDRESS)); + binding.acNewAddress.setAdapter(new CustomSpinnerAdapter(mContext, SpinnerList.ADDRESS)); + binding.acNewBaud.setAdapter(new CustomSpinnerAdapter(mContext, SpinnerList.BAUD)); + List deviceNames = new ArrayList<>(); + deviceNames.add(RoomController.relay.getName()); + deviceNames.add(RoomController.airConditionInfrared.getName()); + deviceNames.addAll(RoomSensor.getAllSensorNames()); + binding.acSensorType.setAdapter(new CustomSpinnerAdapter(mContext, deviceNames.toArray(new String[0]))); + binding.acSensorType.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + binding.acSensorModel.setText(""); + binding.acSensorModel.setAdapter(new CustomSpinnerAdapter(mContext, new String[]{})); + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + //在选择设备类型后,自动选择设备型号 + if (charSequence.toString().equals(RoomController.relay.getName())) { + setAddress = RoomController.relay; + binding.acSensorModel.setAdapter(new CustomSpinnerAdapter(mContext, new String[]{RoomController.relay.getModels()[1]})); + } else if (charSequence.toString().equals(RoomController.airConditionInfrared.getName())) { + //调试时,只需要一个空调即可,RoomController.airConditionInfrared + setAddress = RoomController.airConditionInfrared; + binding.acSensorModel.setAdapter(new CustomSpinnerAdapter(mContext, new String[]{RoomController.airConditionInfrared.getModels()[0]})); + } else { + binding.btnReadAndParse.setEnabled(true); + for (Sensor sensor : RoomSensor.getAllSensors()) { + if (sensor.getName().equals(charSequence.toString())) { + setAddress = sensor; + binding.acSensorModel.setAdapter(new CustomSpinnerAdapter(mContext, sensor.getModels())); + break; + } + } + } + } + + @Override + public void afterTextChanged(Editable editable) { + } + }); + binding.acSerialPort.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void afterTextChanged(Editable editable) { + binding.btnLink.setEnabled(true); + binding.btnUnlink.setEnabled(false); + } + }); + binding.acBaud.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void afterTextChanged(Editable editable) { + binding.btnLink.setEnabled(true); + binding.btnUnlink.setEnabled(false); + } + }); + } + + @Override + public void initListener() { + binding.btnBack.setOnClickListener(v -> finish()); + binding.btnLink.setOnClickListener(this); + binding.btnUnlink.setOnClickListener(this); + binding.btnReadAndParse.setOnClickListener(this); + binding.btnSetNewAddress.setOnClickListener(this); + binding.btnSetNewBaud.setOnClickListener(this); + } + + @Override + public void myClick(View view) { + String serialPort = binding.acSerialPort.getText().toString(); + String baud = binding.acBaud.getText().toString(); + String sensorType = binding.acSensorType.getText().toString(); + String sensorModel = binding.acSensorModel.getText().toString(); + String sensorAddress = binding.acSensorAddress.getText().toString(); + String newAddress = binding.acNewAddress.getText().toString(); + String newBaud = binding.acNewBaud.getText().toString(); + int id = view.getId(); + if (id == R.id.btn_link) { + if (StringUtils.isEmpty(serialPort) || StringUtils.isEmpty(baud)) { + Toasty.error(mContext, "请先选择串口和波特率").show(); + return; + } + if (SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR).openSerialPort(serialPort, baud) && + SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort(serialPort, baud)) { + successTips("串口打开成功"); + binding.btnLink.setEnabled(false); + binding.btnUnlink.setEnabled(true); + } else { + errorTips(); + } + return; + } else if (id == R.id.btn_unlink) { + SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR).close(); + SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).close(); + successTips("串口连接已断开"); + binding.btnLink.setEnabled(true); + binding.btnUnlink.setEnabled(false); + return; + } + + if (StringUtils.isEmpty(sensorType) || StringUtils.isEmpty(sensorModel)) { + Toasty.error(mContext, "请先选择设备类型和型号").show(); + return; + } + if (id == R.id.btn_read_and_parse) { + // 测试 + binding.tvSend.setText(null); + binding.tvReceive.setText(null); + if (StringUtils.isEmpty(sensorAddress)) { + Toasty.error(mContext, "请先选择设备当前地址").show(); + return; + } + showLoadingDialog("正在执行测试指令"); + MyQueue.start(MyQueue.TYPE_SENSOR, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + try{ + return setAddress.test(sensorModel, sensorAddress); + }catch (Exception e){ + return false; + } + } + + @Override + public void doInUIThread(Boolean aBoolean) { + binding.tvSend.setText(SerialPortUtil.T); + binding.tvReceive.setText(SerialPortUtil.R); + binding.tvParse.setText(aBoolean ? setAddress.getTestState() : "数据采集失败"); + hideLoadingDialog(); + } + }); + } else if (id == R.id.btn_set_new_address) { + // 设置新地址 + if (StringUtils.isEmpty(sensorAddress) || StringUtils.isEmpty(newAddress)) { + Toasty.error(mContext, "请先选择:现地址和新地址").show(); + return; + } + new ConfirmDialog(mContext, getString(R.string.tip), "确定设置地址为" + sensorAddress + "的" + sensorModel + "型设备" + + "\n新地址为:" + newAddress + "吗?", () -> { + binding.tvSend.setText(SerialPortUtil.T); + binding.tvReceive.setText(SerialPortUtil.R); + showLoadingDialog("正在设置新地址"); + MyQueue.start(MyQueue.TYPE_SENSOR, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + return setAddress.setAddressToNewAddress(sensorModel, sensorAddress, baud, newAddress); + } + + @Override + public void doInUIThread(Boolean aBoolean) { + if (aBoolean) { + binding.tvParse.setText("地址设置成功"); + successTips("地址设置成功"); + binding.acSensorAddress.setText(null); + } else { + binding.tvParse.setText("地址设置失败"); + errorTips("地址设置失败"); + } + hideLoadingDialog(); + } + }); + }).show(); + } else if (id == R.id.btn_set_new_baud) { + // 设置新波特率 + if (StringUtils.isEmpty(sensorAddress) || StringUtils.isEmpty(newBaud)) { + Toasty.error(mContext, "请先选择:现地址和新波特率").show(); + return; + } + new ConfirmDialog(mContext, getString(R.string.tip), "确定设置地址为" + sensorAddress + "的" + sensorModel + "型设备" + + "\n新波特率为:" + newBaud + "吗?", () -> { + binding.tvSend.setText(null); + binding.tvReceive.setText(null); + showLoadingDialog("正在设置新波特率"); + MyQueue.start(MyQueue.TYPE_SENSOR, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + return setAddress.setBaudToNewBaud(sensorModel, sensorAddress, newBaud); + } + + @Override + public void doInUIThread(Boolean aBoolean) { + binding.tvParse.setText("波特率设置命令已发送"); + binding.acSensorAddress.setText(null); + Toasty.info(mContext, "波特率设置命令已发送").show(); + hideLoadingDialog(); + } + }); + }).show(); + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivityPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivityPresenter.java new file mode 100644 index 0000000..c7a5530 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/DebugActivityPresenter.java @@ -0,0 +1,19 @@ +package com.example.iot_controlhost.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.base.BasePresenter; + +/** + * @Author:DuanKaiji + * @Date:2023年11月22日 17:19:35 + */ +public class DebugActivityPresenter extends BasePresenter { + + public DebugActivityPresenter(@NonNull Application application) { + super(application); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/InfoActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/InfoActivity.java new file mode 100644 index 0000000..916ec47 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/InfoActivity.java @@ -0,0 +1,117 @@ +package com.example.iot_controlhost.ui.activity; + +import android.app.Dialog; +import android.graphics.Bitmap; +import android.view.Gravity; +import android.widget.ImageView; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.SensorInfoAdapter; +import com.example.iot_controlhost.base.BaseActivity; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.ActivityInfoBinding; +import com.example.iot_controlhost.databinding.ItemSensorInfoBinding; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.ui.dialog.EnergyDialog; +import com.example.iot_controlhost.utils.AppUpdateUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.QRCodeUtil; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.network.PublicNetRequest; + +import es.dmoral.toasty.Toasty; + +public class InfoActivity extends BaseActivity { + + SensorInfoAdapter sensorInfoAdapter; + + @Override + public void initView() { + sensorInfoAdapter = new SensorInfoAdapter(null, ItemSensorInfoBinding.class); + //初始化传感器详细数据显示框架 + binding.recycleView.setLayoutManager(new LinearLayoutManager(this)); + binding.recycleView.setAdapter(sensorInfoAdapter); + //观察显示最新传感器数值 + presenter.getSensorInfoList().observe(this, list -> sensorInfoAdapter.setDataList(list)); + binding.tvVersion.setText(MyUtil.getVersionId(this) + "-" + MyUtil.getVersionName(this)); + binding.tvDeviceId.setText(HardwareSetting.getHostId()); + //加载二维码 + PublicNetRequest.loadSignImage(this, binding.ivQrcode); + //初始化基础数据 + initData(); + binding.setVm(presenter); + presenter.getNoticeLiveData().observe(this, integer -> { + switch (integer) { + case 0 -> //关闭软件 +// System.out.println(1/0); + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_shutdown), () -> MyUtil.shutDown()).show(); + case 1 -> //重启APP + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_reboot), () -> MyUtil.relaunchApp()).show(); + case 2 -> //显示硬件码的二维码 + showConfirmDialog(QRCodeUtil.createQRCodeBitmap(HardwareSetting.getHostId(), 300, 300, null, 30)); + case 4 -> //检查更新 + AppUpdateUtil.updateAPP(mContext, false, false); + case 5 -> //关闭页面 + finish(); + } + }); + } + + @Override + public void initListener() { + super.initListener(); + binding.cardRenew.setOnClickListener(v -> { +// new RenewDialog(mContext).show();//todo 续费功能 + Toasty.info(mContext, getString(R.string.developing,"续费")).show(); + }); + binding.tvEnergy.setOnLongClickListener(v -> { + if(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])){ + new EnergyDialog(mContext).show(); + } else { + Toasty.info(mContext, "仅BBIT继电器支持查看能耗详情").show(); + } + return true; + }); + } + + /** + * 初始化固定数据 + */ + private void initData() { + //初始化共育状态 + fillDataInText(binding.tvHead, RoomSetting.getRoomName()); + fillDataInText(binding.tvSim, HardwareSetting.getImei()); + if (SilkwormDBManager.isStarting()) { + //正在共育中 + SilkwormNew silkworm = SilkwormDBManager.getLatestSilkworm(); + fillDataInText(binding.tvAge, SilkwormDBManager.getStartToNowDays()); + fillDataInText(binding.tvVariety, silkworm.getVariety()); + fillDataInText(binding.tvSeason, silkworm.getName()); + } + } + + /** + * 显示二维码弹窗 + * + * @param bitmap 图片内容 + */ + public void showConfirmDialog(Bitmap bitmap) { + Dialog dialog = new Dialog(mContext, R.style.MyDialogTheme); + dialog.setContentView(R.layout.dialog_host_id); + dialog.findViewById(R.id.iv_dialog_close).setOnClickListener(view -> dialog.dismiss()); + dialog.findViewById(R.id.btn_yes).setOnClickListener(view -> dialog.dismiss()); + dialog.setCanceledOnTouchOutside(true); + ((ImageView) dialog.findViewById(R.id.iv_pic)).setImageBitmap(bitmap); + dialog.getWindow().setGravity(Gravity.CENTER); + dialog.getWindow().setAttributes(BaseDialog.getAttributes(dialog)); + dialog.show(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/InfoActivityPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/InfoActivityPresenter.java new file mode 100644 index 0000000..20d54ae --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/InfoActivityPresenter.java @@ -0,0 +1,104 @@ +package com.example.iot_controlhost.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.SensorInfo; +import com.example.iot_controlhost.utils.database.EnergyDBManager; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description 信息页面业务逻辑 + * @Author DuanKaiji + * @CreateTime 2024年01月15日 15:14 + */ +public class InfoActivityPresenter extends BasePresenter { + + private MutableLiveData noticeLiveData = new MutableLiveData<>(); + public MutableLiveData todayConsumption = new MutableLiveData<>(); + public MutableLiveData seasonConsumption = new MutableLiveData<>(); + public MutableLiveData allConsumption = new MutableLiveData<>(); + public MutableLiveData indoorTemperature = new MutableLiveData<>(); + public MutableLiveData indoorHumidity = new MutableLiveData<>(); + public MutableLiveData qualityAir = new MutableLiveData<>("-"); + public MutableLiveData qualityAirOut = new MutableLiveData<>("-"); + public MutableLiveData outdoorHumidity = new MutableLiveData<>(); + public MutableLiveData outdoorTemperature = new MutableLiveData<>(); + public MutableLiveData> sensorInfoList = new MutableLiveData<>(); + + public MutableLiveData> getSensorInfoList() { + return sensorInfoList; + } + + public InfoActivityPresenter(@NonNull Application application) { + super(application); + // 读取服务到期时间 +// loadServerExpirationDate(); + // 初始化温湿度数据 + pollingTask.startPollingTaskOnIOThread(RxTag.INFO_GET_SENSOR_DATA, Variable.SENSOR_REFRESH_TIME, () -> { + todayConsumption.postValue(RoomSensor.consumptionSensor.getTodayEnergyConsumption()); + seasonConsumption.postValue(RoomSensor.consumptionSensor.getSeasonEnergyConsumption()); + allConsumption.postValue(EnergyDBManager.queryMaxDate()); + indoorTemperature.postValue(RoomSensor.getAverageIndoorTemperature()); + indoorHumidity.postValue(RoomSensor.getAverageIndoorHumidity()); + qualityAir.postValue(GasSensor.getQualityAir()); + qualityAirOut.postValue(GasSensor.getQualityAir()); + outdoorTemperature.postValue(RoomSensor.outdoorTHSensor.temperature); + outdoorHumidity.postValue(RoomSensor.outdoorTHSensor.humidity); + List infoList = new ArrayList<>(); + // 所有温湿度传感器的数值 + for (Sensor sensor : RoomSensor.getAllAvailableSensors()) { + infoList.add(new SensorInfo(sensor.getName(), sensor.toString())); + } + sensorInfoList.postValue(infoList); + }); + } + + public MutableLiveData getNoticeLiveData() { + return noticeLiveData; + } + + public void notice(int type) { + noticeLiveData.setValue(type); + } + + + /** + * 读取服务到期时间 + */ + private void loadServerExpirationDate() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.serverInfo); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + } + + @Override + public void error(Throwable e) { + MyLog.appError("服务信息获取失败"); + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/MainActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/MainActivity.java new file mode 100644 index 0000000..c36419e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/MainActivity.java @@ -0,0 +1,324 @@ +package com.example.iot_controlhost.ui.activity; + +import android.content.Intent; +import android.graphics.Color; +import android.view.MotionEvent; +import android.view.View; + +import androidx.lifecycle.ViewModelProvider; + +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.ModelsAdapter; +import com.example.iot_controlhost.base.BaseActivity; +import com.example.iot_controlhost.databinding.ActivityIncubationBinding; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.model.net.WorkList; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.ui.dialog.LogDialog; +import com.example.iot_controlhost.ui.dialog.MessageDialog; +import com.example.iot_controlhost.ui.dialog.PasswordDialog; +import com.example.iot_controlhost.ui.dialog.SetBaseDialog; +import com.example.iot_controlhost.ui.dialog.THBiasDialog; +import com.example.iot_controlhost.ui.dialog.WeatherDialog; +import com.example.iot_controlhost.ui.dialog.WorkDialog; +import com.example.iot_controlhost.ui.dialog.WorkNewDialog; +import com.example.iot_controlhost.ui.viewmodel.AppBarViewModel; +import com.example.iot_controlhost.utils.AppUpdateUtil; +import com.example.iot_controlhost.utils.DateUtils; +import com.example.iot_controlhost.utils.FrpUpdateUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.MyLog; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask; + +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import es.dmoral.toasty.Toasty; + +public class MainActivity extends BaseActivity { + /** + * 实现再按一次退出程序 + */ + private long mExitTime; + AppBarViewModel appBarViewModel; + WorkDialog workDialog; + + @Override + public void initView() { + //判断是否需要升级 + AppUpdateUtil.updateAPP(mContext, true, false); + //观察显示开始共育弹窗 + if (RoomSetting.isDarkTheme()) { + binding.textView7.setTextColor(Color.parseColor("#33FFFFFF")); + binding.textView8.setTextColor(Color.parseColor("#33FFFFFF")); + binding.textView0.setTextColor(Color.parseColor("#FFFFFF")); + } else { + binding.textView7.setTextColor(Color.parseColor("#993A3A3A")); + binding.textView8.setTextColor(Color.parseColor("#993A3A3A")); + binding.textView0.setTextColor(Color.parseColor("#803A3A3A")); + } + appBarViewModel = new ViewModelProvider(this).get(AppBarViewModel.class); + binding.include.setVm(appBarViewModel); + appBarViewModel.setMainActivityContext(this, binding.include.llError); + appBarViewModel.getIvServer().observe(this, drawable -> binding.include.ivNetServer.setImageDrawable(getDrawable(drawable))); + appBarViewModel.getIvNetwork().observe(this, drawable -> binding.include.ivNetState.setImageDrawable(getDrawable(drawable))); + appBarViewModel.getNoticeLiveData().observe(this, integer -> { + switch (integer) { + case 0 ->//询问进入配置 + new PasswordDialog(mContext, getString(R.string.insert_password), RoomSetting.getSetPassword(), true, + v2 -> MyUtil.stopAppControl("正在关闭所有设备", () -> { + //这里额外去掉用户使用的需要的轮询任务 + PollingTask.getInstance().stopAllPollingTasks(); + startActivity(new Intent(MainActivity.this, SetActivity.class)); + finish(); + }), false).show(); + case 2 ->//SOS + new ConfirmDialog(mContext, "提示", "是否请求远程服务?", () -> { + appBarViewModel.sos(mContext); + }).show(); + } + }); + //显示天气详细信息 + appBarViewModel.getWeatherLiveData().observe(this, weather -> { + if (weather.getData() != null) { + new WeatherDialog(mContext, weather).show(); + } + }); + binding.setVm(presenter); + presenter.getWorkListMutableLiveData().observe(this, workList -> { + workDialog = new WorkDialog(mContext, workList); + workDialog.show(); + }); + presenter.getNewWorkListMutableLiveData().observe(this, workList -> new WorkNewDialog(mContext).show()); + presenter.getChangeMode().observe(this, mode -> { + binding.vp2Model.setCurrentItem(mode, false); + changeModelView(mode); + }); + presenter.getNoticeLiveData().observe(this, integer -> { + switch (integer) { + case 2 ->//锁屏 + new PasswordDialog(mContext, "请输入锁屏密码", RoomSetting.getMainActivityLock(), v1 -> { + binding.mainLock.setVisibility(View.GONE); + binding.mainKey.setImageDrawable(getDrawable(R.mipmap.unlock)); + refreshLock(); + }).show(); + case 3 ->//进入设置 + new SetBaseDialog(mContext).show(); + case 4 -> {//结束登记弹窗 + workDialog = new WorkDialog(mContext, null); + workDialog.show(); + } + case 5 ->//显示详细信息 + startActivity(new Intent(mContext, InfoActivity.class)); + case 7 ->//切换为手动模式 + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_manual), () -> presenter.initMode(0)).show(); + case 8 ->//切换为自动模式 + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_auto), () -> presenter.initMode(1)).show(); + case 9 ->//切换为智能模式 + new ConfirmDialog(mContext, getString(R.string.warning), getString(R.string.ask_turn_intelligence), () -> { + if (!SilkwormDBManager.isStarting()) { + new ConfirmDialog(mContext, getString(R.string.error), "当前未在" + (RoomSetting.getType() == 4 ? "催青" : "共育") + ",无法进入智能模式", null).show(); + } else { + presenter.initMode(2); + } + }).show(); + case 10 ->//刷新共育状态 + refreshCultivateState(true); + case 11 ->//刷新共育状态 + refreshCultivateState(false); + } + }); + //模式切换 + binding.vp2Model.setAdapter(new ModelsAdapter(this)); + binding.vp2Model.setOffscreenPageLimit(2); + binding.vp2Model.setUserInputEnabled(false); + refreshCultivateState(false); + refreshLock(); + // 下载启动最新版本FRP + FrpUpdateUtil.check(mContext, false); + } + + /** + * 刷新共育状态 + * + * @param needSwitchMode 是否需要刷新状态时匹配状态的模式 + */ + private void refreshCultivateState(boolean needSwitchMode) { + if (workDialog != null) { + workDialog.dismiss(); + } + RxJavaUtils.executeAsyncTask(new RxAsyncTask(null) { + @Override + public CulInfo doInIOThread(Object o) { + boolean isStarting = SilkwormDBManager.isStarting(); + if (isStarting) { + SilkwormNew silkworm = SilkwormDBManager.getLatestSilkworm(); + int now = DateUtils.daysFromTodayInclusive(silkworm.getStartDate()); + int process = now / AIModelSet.getAiControlProgramTotalDays() * 100; + return new CulInfo(isStarting, + "第" + SilkwormDBManager.getStartToNowDays() + "天", + silkworm.getVariety(), + silkworm.getNumber() + "张", + silkworm.getName(), + process); + } else { + String none = "未在" + (RoomSetting.getType() == 4 ? "催青" : "共育"); + return new CulInfo(false, none, none, none, none, 0); + } + } + + @Override + public void doInUIThread(CulInfo culInfo) { + if (needSwitchMode) { + presenter.initMode(culInfo.isStarting ? 1 : 0); + } + fillDataInText(binding.tvAge, culInfo.age); + fillDataInText(binding.tvVariety, culInfo.variety); + fillDataInText(binding.tvQuentity, culInfo.quantity); + fillDataInText(binding.tvSeason, culInfo.season); + if (RoomSetting.getType() == 4) { + fillDataInText(binding.tvCulNumber, "催青数量:"); + fillDataInText(binding.tvCulBatch, "催青批次:"); + fillDataInText(binding.tvCulProcess, "催青进度:"); + fillDataInText(binding.tvCulKinds, "催青品种:"); + fillDataInText(binding.textView8, "打造智慧催青室,服务蚕农朋友!"); + } else { + fillDataInText(binding.tvCulNumber, "共育数量:"); + fillDataInText(binding.tvCulBatch, "共育批次:"); + fillDataInText(binding.tvCulProcess, "共育进度:"); + fillDataInText(binding.tvCulKinds, "共育品种:"); + } + binding.lpProcess.setProgressCompat(culInfo.process, true); + } + }); + } + + /** + * 共育窗口数据类 + */ + private class CulInfo { + boolean isStarting; + String age; + String variety; + String quantity; + String season; + int process; + + public CulInfo(boolean isStarting, String age, String variety, String quantity, String season, int process) { + this.isStarting = isStarting; + this.age = age; + this.variety = variety; + this.quantity = quantity; + this.season = season; + this.process = process; + } + } + + @Override + public void initListener() { + binding.vp2Model.setCurrentItem(RoomSetting.getMode(), false); + changeModelView(RoomSetting.getMode()); + binding.cardKey.setOnClickListener(v -> lock()); + binding.mainEnable.setOnTouchListener((v, event) -> true); + binding.textView8.setOnClickListener(v -> { +// startActivity(new Intent(this,ComposeActivity.class)); + }); + binding.textView0.setOnClickListener(v -> { + // todo 消息系统开发完成后打开 +// new MessageDialog(mContext).show(); + }); + binding.tempCard.setOnClickListener(v -> new THBiasDialog(mContext,true).show()); + binding.humidityCard.setOnClickListener(v -> new THBiasDialog(mContext,false).show()); + } + + /** + * 模式切换的UI效果转换 + * + * @param target 要转换为的目标模式-手自智 + */ + private void changeModelView(int target) { + binding.btnManual.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33969696" : "#FFFFFF")); + binding.btnAuto.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33969696" : "#FFFFFF")); + binding.btnIntelligence.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33969696" : "#FFFFFF")); + binding.tvManual.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#414141")); + binding.tvAuto.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#414141")); + binding.tvIntelligence.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#414141")); + binding.ivManual.setImageDrawable(getDrawable(R.mipmap.mode_manual_close)); + binding.ivAuto.setImageDrawable(getDrawable(R.mipmap.mode_auto_close)); + binding.ivIntelligence.setImageDrawable(getDrawable(R.mipmap.mode_intelligence_close)); + if (target == 0) { + //手动模式 + binding.btnManual.setBackgroundColor(Color.parseColor("#00A1FF")); + binding.ivManual.setImageDrawable(getDrawable(R.mipmap.mode_manual_open)); + binding.tvManual.setTextColor(Color.parseColor("#FFFFFF")); + } else if (target == 1) { + //自动模式 + binding.btnAuto.setBackgroundColor(Color.parseColor("#00A1FF")); + binding.ivAuto.setImageDrawable(getDrawable(R.mipmap.mode_auto_open)); + binding.tvAuto.setTextColor(Color.parseColor("#FFFFFF")); + } else { + //智能模式 + binding.btnIntelligence.setBackgroundColor(Color.parseColor("#00A1FF")); + binding.ivIntelligence.setImageDrawable(getDrawable(R.mipmap.mode_intelligence_open)); + binding.tvIntelligence.setTextColor(Color.parseColor("#FFFFFF")); + } + } + + /** + * 刷新锁屏时间 + */ + private void refreshLock() { +// MyLog.app("刷新主屏幕锁时间" + RoomSetting.getMainActivityOperateLockTimeOut()); + if (mainLockExecutorService != null) { + mainLockExecutorService.shutdownNow(); + } + mainLockExecutorService = Executors.newSingleThreadScheduledExecutor(); + mainLockExecutorService.schedule(() -> lock(), RoomSetting.getMainActivityOperateLockTimeOut(), TimeUnit.SECONDS); + } + + private void lock() { + MyLog.app("屏幕已锁"); + mainLockExecutorService.shutdownNow(); + runOnUiThread(() -> { + binding.mainLock.setVisibility(View.VISIBLE); + binding.mainKey.setImageDrawable(getDrawable(R.mipmap.lock)); + }); + } + + private ScheduledExecutorService mainLockExecutorService; + + @Override + public boolean onTouchEvent(MotionEvent event) { + int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + refreshLock(); + } + return super.onTouchEvent(event); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + if (System.currentTimeMillis() - mExitTime < 2000) { + finish(); + System.exit(0); + } else { + mExitTime = System.currentTimeMillis(); + Toasty.info(mContext, "再按一次返回键退出程序").show(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/MainActivityPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/MainActivityPresenter.java new file mode 100644 index 0000000..958eeb2 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/MainActivityPresenter.java @@ -0,0 +1,278 @@ +package com.example.iot_controlhost.ui.activity; + + +import android.app.Application; +import android.content.Context; +import android.os.Build; +import android.telephony.TelephonyManager; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.GsonUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.ModelsAdapter; +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.SingleLiveEvent; +import com.example.iot_controlhost.model.net.Token; +import com.example.iot_controlhost.model.net.WorkList; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.TopicClass; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.ArrayList; + +public class MainActivityPresenter extends BasePresenter { + + private MutableLiveData noticeLiveData = new SingleLiveEvent<>(); + private MutableLiveData workListMutableLiveData = new SingleLiveEvent<>(); + private MutableLiveData workNewListMutableLiveData = new SingleLiveEvent<>(); + public MutableLiveData temperatureStr = new MutableLiveData<>(); + public MutableLiveData temperatureStrOut = new MutableLiveData<>(); + public MutableLiveData humidityStr = new MutableLiveData<>(); + public MutableLiveData humidityStrOut = new MutableLiveData<>(); + public MutableLiveData roomEnable = new MutableLiveData<>(true); + public MutableLiveData roomName = new MutableLiveData<>("正在加载中"); + public MutableLiveData changeMode = new SingleLiveEvent<>(); + public MutableLiveData noticeMessage = new SingleLiveEvent<>(); + public MutableLiveData isNoticeVisible = new MutableLiveData<>(false); + + public MainActivityPresenter(@NonNull Application application) { + super(application); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1 || HardwareSetting.isYSAndroid()) { + // 旧版Android7.1 || 新版Android11 + TelephonyManager telephonyManager = (TelephonyManager) getApplication().getSystemService(Context.TELEPHONY_SERVICE); + MMKVUtil.put(HardwareSetting.IMEI, telephonyManager.getSimSerialNumber()); + MMKVUtil.put(HardwareSetting.IMSI, telephonyManager.getSubscriberId()); + } else { + // 旧版Android11 + } + MyLog.test("SIM:" + HardwareSetting.getImei()); + //更新数据 + RxJavaUtils.doInIOThread(new RxIOTask<>(null) { + @Override + public Void doInIOThread(Object x) { + // 刷新共育状态 + pollingTask.startPollingTaskOnIOThread(RxTag.REFRESH_CUL_STATE, Variable.REFRESH_CULTURE_STATE_TIME, () -> noticeLiveData.setValue(11)); + //更新主界面UI + pollingTask.startPollingTaskOnIOThread(RxTag.MAIN_UI, 5, () -> { + temperatureStr.postValue(String.valueOf(RoomSensor.getAverageIndoorTemperature())); + humidityStr.postValue(String.valueOf(RoomSensor.getAverageIndoorHumidity())); + temperatureStrOut.postValue(String.valueOf(RoomSensor.outdoorTHSensor.temperature)); + humidityStrOut.postValue(String.valueOf(RoomSensor.outdoorTHSensor.humidity)); + }); + //上报信息 + pollingTask.startPollingTaskOnIOThread(RxTag.MAIN_UPLOAD, Variable.UPLOAD_SENSOR_DATA_TIME, () -> { + TopicClass.uploadSensorData();//上报传感器信息 + TopicClass.uploadAreaCustomData();//上传传感器细则信息 + TopicClass.uploadDeviceState(false);//每隔10s 自动上报(不同的)设备状态 + TopicClass.areaEnergyData();//上报能耗数据 + }); + //更新主界面UI + RxBusUtils.get().onMainThread(RxTag.UPDATE_MAIN, Integer.class, i -> { + switch (i) { + case 1 -> { + //设置为区域可用性 + MyLog.app("远程设置房间:" + (RoomSetting.isRoomEnable() ? "可用" : "不可用")); + roomEnable.setValue(RoomSetting.isRoomEnable()); + // 更新房间名 + roomName.setValue(RoomSetting.getRoomName()); + if (!RoomSetting.isRoomEnable()) { + //刷新切换为手动模式 + initMode(0); + } + } + case 3 -> //刷新切换为自动模式 + initMode(1); + case 5 -> //刷新切换为手动模式 + initMode(0); + case 6 -> //刷新切换为智能模式 + initMode(2); + case 7 -> //刷新共育信息 并刷新模式 + noticeLiveData.setValue(10); + } + }); + RxBusUtils.get().onMainThread(RxTag.UPDATE_MAIN_MSG, String.class, msg -> { + noticeMessage(msg); + }); + return null; + } + }); + roomName.setValue(RoomSetting.getRoomName()); + refreshToken(); + } + + /** + * 进入新模式 + * + * @param targetMode 新模式 + */ + public void initMode(int targetMode) { + String modeName; + if (targetMode == 0) { + modeName = "手动模式"; + } else if (targetMode == 1) { + modeName = "自动模式"; + } else { + modeName = "智能模式"; + } + showLoadingDialog("正在切换为" + modeName + "中"); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask<>() { + @Override + public Object doInQueueThread() { + ModelsAdapter.getModeByPosition(RoomSetting.getMode()).stopMode(); + MMKVUtil.put(RoomSetting.MODE, targetMode); + UserLog.operate("切换为:" + modeName); + return null; + } + + @Override + public void doInUIThread(Object o) { + changeMode.setValue(targetMode); + ModelsAdapter.getModeByPosition(targetMode).initMode(); + hideLoadingDialog(); + } + }); + } + + + public MutableLiveData getNoticeLiveData() { + return noticeLiveData; + } + + public MutableLiveData getWorkListMutableLiveData() { + return workListMutableLiveData; + } + + public MutableLiveData getNewWorkListMutableLiveData() { + return workNewListMutableLiveData; + } + + public MutableLiveData getChangeMode() { + return changeMode; + } + + public void notice(View view) { + int viewId = view.getId(); + if (viewId == R.id.main_lock) { + // 锁屏 + noticeLiveData.setValue(2); + } else if (viewId == R.id.btn_info) { + // 基础信息 + noticeLiveData.setValue(5); + } else if (viewId == R.id.btn_sign) { + // 操作登记 + if (RoomSetting.getWorkState() == null) { + // 开始登记 + getWorkList(); + } else { + // 结束登记 + noticeLiveData.setValue(4); + // MyLog.test("当前作业名:" + RoomSetting.getWorkState().get("name")); + // MyLog.test("当前作业码:" + RoomSetting.getWorkState().get("data")); + } + } else if (viewId == R.id.btn_sign_new) { + // 开始登记 + workNewListMutableLiveData.setValue(true); + } else if (viewId == R.id.btn_set) { + // 设置 + noticeLiveData.setValue(3); + } else if (viewId == R.id.btn_manual) { + // 切换为手动模式 核心 + if (RoomSetting.getMode() != 0) { + noticeLiveData.setValue(7); + } + } else if (viewId == R.id.btn_auto) { + // 切换为自动模式 + if (RoomSetting.getMode() != 1) { + noticeLiveData.setValue(8); + } + } else if (viewId == R.id.btn_intelligence) { + // 切换为智能模式 + if (RoomSetting.getMode() != 2) { + noticeLiveData.setValue(9); + } + } + } + + /** + * 通知方法,更新主界面下方通知内容 + * + * @param msg 消息 + */ + private void noticeMessage(String msg) { + isNoticeVisible.setValue(true); + noticeMessage.setValue(msg); + } + + /** + * 获取工作登记列表-网络请求 + */ + public void getWorkList() { + showLoadingDialog("正在读取作业列表中..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getWorkList); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(WorkList model) { + // json转对象 + workListMutableLiveData.setValue(model); + } + + @Override + public void error(Throwable e) { + errorTips("作业列表获取失败,请检查网络"); + UserLog.taskError("作业列表获取失败" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + hideLoadingDialog(); + } + }); + } + + /** + * 获取Token + */ + public void refreshToken() { + pollingTask.startPollingTaskOnIOThread(RxTag.GET_TOKEN, 60 * 60, () -> { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getToken); + params.addParameter("Account", "bbitiotcontrol"); + params.addParameter("Password", "jA7@mD1?"); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(Token model) { + MyLog.app("Token获取成功"); + MMKVUtil.put(RoomSetting.TOKEN, model.getTokenType() + " " + model.getAccessToken()); + } + + @Override + public void error(Throwable e) { + MyLog.appError("Token获取失败"); + } + + }); + }); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java new file mode 100644 index 0000000..53ec78b --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivity.java @@ -0,0 +1,550 @@ +package com.example.iot_controlhost.ui.activity; + +import android.content.Intent; +import android.os.Build; +import android.provider.Settings; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.SetAdapter; +import com.example.iot_controlhost.base.BaseActivity; +import com.example.iot_controlhost.databinding.ActivitySetBinding; +import com.example.iot_controlhost.databinding.ItemSetBinding; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.Set; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.controller.DehumidifierInfrared; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.net.AirConditionList; +import com.example.iot_controlhost.model.sensor.ConsumptionSensor; +import com.example.iot_controlhost.model.sensor.FloorTSensor; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.ui.dialog.AirConditionAddDialog; +import com.example.iot_controlhost.ui.dialog.AirDialog; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.ui.dialog.DehumidityAddDialog; +import com.example.iot_controlhost.ui.dialog.PasswordDialog; +import com.example.iot_controlhost.ui.dialog.SetDeviceBaseDialog; +import com.example.iot_controlhost.ui.dialog.SetPWMDialog; +import com.example.iot_controlhost.ui.dialog.SetPortDialog; +import com.example.iot_controlhost.ui.dialog.SetSensorDialog; +import com.example.iot_controlhost.ui.dialog.SpinnerDialog; +import com.example.iot_controlhost.ui.dialog.TextViewDialog; +import com.example.iot_controlhost.utils.AppUpdateUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorAmmoniaSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import es.dmoral.toasty.Toasty; + +public class SetActivity extends BaseActivity { + + /** + * 当前菜单是否在主菜单 + */ + private int toMain; + List setList; + SetAdapter setAdapter; + + @Override + public void initView() { + setList = new ArrayList<>(); + setAdapter = new SetAdapter(setList, ItemSetBinding.class); + binding.rvSet.setLayoutManager(new LinearLayoutManager(mContext)); + binding.rvSet.setAdapter(setAdapter); + setMain(); + presenter.getRefreshLiveData().observe(this, integer -> { + switch (integer) { + case 0 -> setFloor(); + case 1 -> MyUtil.relaunchApp(); + case 2 -> setAirCondition(); + case 3 -> setAdvanced(); + } + }); + presenter.getShowImportAirConditionInstructionDialogLiveData().observe(this, + pair -> showImportAirConditionInstructionDialog(pair.first, pair.second)); + presenter.getShowImportHumidifierInstructionDialogLiveData().observe(this, + this::showImportDehumidifierInstructionDialog); + } + + @Override + public void initListener() { + //返回按钮 + binding.btnBack.setOnClickListener(view -> { + if (toMain == -1) { + // 重启APP以应用最新配置 + presenter.uploadSet(); + } else if (toMain == 0) { + setMain(); + } else if (toMain == 1) { + setSensor(); + } else if (toMain == 2) { + setAirCondition(); + } + }); + } + + /** + * 主菜单 + */ + private void setMain() { +// 输出每个继电器下设备的端口情况 +// for (Relay controller : RoomController.getAllRelayDevice()) { +// System.out.print(controller.getName() + ":"); +// for (int controllerPort : controller.getPorts()) { +// System.out.print("\t端口" + controllerPort); +// } +// System.out.println(); +// } + toMain = -1; + setList.clear(); + binding.tvTitle.setText(getText(R.string.set)); + setList.add(new Set(getString(R.string.set_app), view -> setApp())); + setList.add(new Set(getString(R.string.set_controller), view -> setController())); + if (HardwareSetting.getWorkMode().equals(HardwareSetting.CONTROLLER_WORK_MODE[0])) { + //控制版本-从机模式 + setList.add(new Set(getString(R.string.set_relay), view -> setRelay())); + setList.add(new Set(getString(R.string.set_sensor), view -> setSensor())); + setList.add(new Set(getString(R.string.set_air_condition), view -> setAirCondition())); + setList.add(new Set("除湿机配置", view -> setDeHumidity())); + setList.add(new Set(RoomSetting.getFloorName() + "配置", view -> setFloor())); + } else { + //显示版本-主机模式 + } + setList.add(new Set(getString(R.string.set_advanced), view -> new PasswordDialog(mContext, "请输入运维密码", + ti -> presenter.checkDevOpsPassword(ti.getText().toString())).show())); + setList.add(new Set(getString(R.string.device_info), view -> info())); + setAdapter.setDataList(setList); + } + + /** + * 显示设备信息 + */ + private void info() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.device_info)); + setList.clear(); + setList.add(new Set(getString(R.string.device_model), Build.MODEL)); + setList.add(new Set(getString(R.string.device_manufacturer), Build.MANUFACTURER)); + setList.add(new Set(getString(R.string.device_brand), Build.BRAND)); + setList.add(new Set(getString(R.string.device_serial), Build.SERIAL)); + setList.add(new Set(getString(R.string.android_version), Build.VERSION.RELEASE)); + setList.add(new Set(getString(R.string.api_level), String.valueOf(Build.VERSION.SDK_INT))); + setList.add(new Set(getString(R.string.product_name), Build.PRODUCT)); + setList.add(new Set(getString(R.string.board_name), Build.BOARD)); + setList.add(new Set(getString(R.string.imei), HardwareSetting.getImei())); + setList.add(new Set(getString(R.string.device_id), HardwareSetting.getHostId())); + setList.add(new Set(getString(R.string.version), MyUtil.getVersionId(this) + "-" + MyUtil.getVersionName(this))); + setList.add(new Set(getString(R.string.check_update), view -> AppUpdateUtil.updateAPP(mContext, false, false))); + setAdapter.setDataList(setList); + } + + /** + * 系统配置 + */ + private void setApp() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_app)); + setList.clear(); + setList.add(new Set(getString(R.string.set_password), "******", view -> + new PasswordDialog(mContext, getString(R.string.set_password), null, ti -> + MMKVUtil.put(RoomSetting.SET_PASSWORD, ti.getText().toString())).show())); + setList.add(new Set(getString(R.string.set_password_main_lock), "******", view -> new PasswordDialog(mContext, getString(R.string.set_password_main_lock), + null, ti -> { + if (ti.getText().toString().length() != 6) { + new ConfirmDialog(mContext, "提示", "设置失败,锁屏密码长度为6位,请重新设置", null).show(); + return; + } + MMKVUtil.put(RoomSetting.MAIN_ACTIVITY_LOCK, ti.getText().toString()); + }).show())); + setList.add(new Set("导入配置", view -> new SpinnerDialog(mContext, "导入配置", "请选择配置来源", + new String[]{"预设", "基地", "会东", "旧版本", "兴文石海", "主干继电器-从机模式"}, + selection -> presenter.importSet(selection)).show())); + setList.add(new Set("上传日志", v -> { + Toasty.warning(mContext, "正在开发中").show(); +// presenter.uploadLog(); + })); + setAdapter.setDataList(setList); + } + + /** + * 控制器配置 + */ + private void setController() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_controller)); + setList.clear(); +// setList.add(new Set(getString(R.string.set_controller_type), Controller.getControllerModel(), view -> +// new SpinnerDialog(mContext, getString(R.string.set_controller), "请选择控制器类别", Controller.CONTROLLER_MODEL, selection -> { +// Controller.setControllerModel(selection); +// setController(); +// }).show())); +// if (Device.isIntegratedController()) { +//// 选择——集成控制器-集成控制器工作方式有主机模式、从机模式 +// setList.add(new Set(getString(R.string.work_mode), HardwareSetting.getWorkMode(), view -> +// new SpinnerDialog(mContext, getString(R.string.work_mode), "请选择工作模式", HardwareSetting.CONTROLLER_WORK_MODE, selection -> { +// HardwareSetting.setWorkMode(selection); +// setController(); +// }).show())); +// setList.add(new Set(getString(R.string.serial_port_common), HardwareSetting.getControllerSerialCom() + "\t:\t" + HardwareSetting.getControllerSerialBaud(), view -> +// showSetBaseDialog(mContext, getString(R.string.serial_port_common), SpinnerList.SERIAL_ADDRESS, getString(R.string.serial_port_common), +// SpinnerList.BAUD, getString(R.string.baud), (serial, baud) -> { +// HardwareSetting.setControllerSerialCom(serial); +// HardwareSetting.setSensorSerialCom(serial); +// HardwareSetting.setControllerSerialBaud(baud); +// HardwareSetting.setSensorSerialComBaud(baud); +// setController(); +// }))); +// setList.add(new Set(getString(R.string.serial_address_common), HardwareSetting.getCommonAddress(), view -> +// new SpinnerDialog(mContext, getString(R.string.serial_address_common), "请选择通用地址", SpinnerList.ADDRESS, selection -> { +// HardwareSetting.setCommonAddress(selection); +// setController(); +// }).show())); +// } else { +//// 选择——装配式控制器-拼装式控制器工作方式只有从机模式 +// HardwareSetting.setWorkMode(HardwareSetting.CONTROLLER_WORK_MODE[0]); +// setList.add(new Set(getString(R.string.work_mode), HardwareSetting.getWorkMode())); + +// //非集成控制器则需要设置传感器串口 + 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) -> { + HardwareSetting.setSensorSerialCom(serial); + HardwareSetting.setSensorSerialComBaud(baud); + setController(); + }).show())); + setList.add(new Set(getString(R.string.serial_port_controller), HardwareSetting.getControllerSerialCom() + "\t:\t" + HardwareSetting.getControllerSerialBaud(), view -> + new SetDeviceBaseDialog(mContext, getString(R.string.serial_port_controller), SpinnerList.SERIAL_ADDRESS, getString(R.string.serial_port_sensor), + SpinnerList.BAUD, getString(R.string.baud), (serial, baud) -> { + HardwareSetting.setControllerSerialCom(serial); + HardwareSetting.setControllerSerialBaud(baud); + setController(); + }).show())); +// } + setAdapter.setDataList(setList); + } + + /** + * 继电器配置 + */ + private void setRelay() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_relay)); + setList.clear(); + if (!Device.isIntegratedController()) { + //装配式控制器 + setList.add(new Set(getString(R.string.enable_relay), RoomController.relay.isEnable(), (compoundButton, b) -> { + RoomController.relay.setEnable(b); + setRelay(); + })); + setList.add(new Set(getString(R.string.set_base), RoomController.relay.toString(), view -> new SetDeviceBaseDialog(mContext, getString(R.string.set_relay), RoomController.relay.getModels(), + getString(R.string.mode), SpinnerList.ADDRESS, getString(R.string.address), (model, address) -> { + RoomController.relay.setModel(model); + RoomController.relay.setAddress(address); + setRelay(); + }).show())); + } + for (int i = 1; i <= 8; i++) { + setList.add(new Set(getString(R.string.port) + i, MyUtil.getPortToDeviceName(i), + v -> new SetPortDialog(mContext, + Integer.parseInt(((TextView) v.findViewById(R.id.tv_set_title)).getText().toString().substring(2)), + () -> setRelay()).show())); + } + setAdapter.setDataList(setList); + } + + /** + * 除湿机配置 + */ + void setDeHumidity() { + toMain = 0; + DehumidifierInfrared deHumidityInfrared = RoomController.dehumidifierInfrared; + binding.tvTitle.setText(getText(R.string.set) + " > 除湿机配置"); + setList.clear(); + setList.add(new Set(getString(R.string.enable_air_condition_infrared), deHumidityInfrared.isEnable(), (compoundButton, b) -> { + deHumidityInfrared.setEnable(b); + setDeHumidity(); + })); + setList.add(new Set(getString(R.string.set_base_infrared), deHumidityInfrared.toString(), view -> + 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) -> { + deHumidityInfrared.setModel(model); + deHumidityInfrared.setAddress(address); + setDeHumidity(); + }).show())); + setList.add(new Set("导入除湿机指令", "除湿机指令集", view -> + presenter.getAirConditionModelList(-1))); + setAdapter.setDataList(setList); + } + /** + * 空调配置 + */ + void setAirCondition() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_air_condition)); + setList.clear(); + setList.add(new Set("配置" + getText(R.string.set_air_condition1), view -> setAirConditionDetail(1, RoomController.airConditionInfrared))); + setList.add(new Set("配置" + getText(R.string.set_air_condition2), view -> setAirConditionDetail(2, RoomController.airConditionInfrared2))); + setList.add(new Set(getString(R.string.set_time_air_auto_stop_relay), RoomSetting.getTimeOfAirStopRelay() + "秒", view -> + new PasswordDialog(mContext, "关闭物理电源的等待时间", null, false, ti -> { + try { + int seconds = Integer.parseInt(ti.getText().toString()); + if (seconds < 3) { + new ConfirmDialog(mContext, "提示", "设置失败,等待时间不可少于3秒", null).show(); + return; + } + MMKVUtil.put(RoomSetting.TIME_OF_AIR_STOP_RELAY, seconds); + setAirCondition(); + } catch (Exception e) { + new ConfirmDialog(mContext, "提示", "设置失败,等待时间必须为整数", null).show(); + } + }, true).show())); + setList.add(new Set("手动/自动控制时:先开物理开关,再切换温度", RoomSetting.isAirStartFirst(), (compoundButton, b) -> { + MMKVUtil.put(RoomSetting.CONTROL_AIR_START_FIRST, b); + setAirCondition(); + })); + setList.add(new Set("自动控制时:循环发送控制指令", RoomSetting.isAirRecycleControl(), (compoundButton, b) -> { + MMKVUtil.put(RoomSetting.AIR_RECYCLE_CONTROL, b); + setAirCondition(); + })); + setAdapter.setDataList(setList); + } + + /** + * 设置空调1 + */ + void setAirConditionDetail(int airId, AirConditionInfrared airConditionInfrared) { + toMain = 2; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_air_condition) + " > " + airConditionInfrared.getName()); + setList.clear(); + setList.add(new Set(getString(R.string.enable_air_condition_infrared), airConditionInfrared.isEnable(), (compoundButton, b) -> { + airConditionInfrared.setEnable(b); + setAirConditionDetail(airId, airConditionInfrared); + })); + setList.add(new Set(getString(R.string.set_base_infrared), airConditionInfrared.toString(), view -> + 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) -> { + airConditionInfrared.setModel(model); + airConditionInfrared.setAddress(address); + setAirConditionDetail(airId, airConditionInfrared); + }).show())); + String airInstruction = airId == 1 ? RoomController.airCondition.getAirConditionModel() : RoomController.airCondition.getAirConditionModel2(); + setList.add(new Set(getString(R.string.import_air_condition_instruction), airInstruction + "指令集", view -> + presenter.getAirConditionModelList(airId))); + setList.add(new Set(getString(R.string.air_conditioning_test), null, view -> new AirDialog(mContext, airConditionInfrared).show())); + setAdapter.setDataList(setList); + } + + /** + * 空调型号选择弹窗 + */ + void showImportAirConditionInstructionDialog(int airId, List list) { + String[] models = list.stream() + .map(data -> data.getBrand() + "-" + data.getModel()) + .toArray(String[]::new); + new SpinnerDialog(mContext, "空调型号", "请选择空调型号", models, selection -> { + //从服务器读取空调指令json,并导入至红外控制器 + presenter.importAirConditionInstruction(list, selection, airId); + }).show(); + } + /** + * 除湿机型号选择弹窗 + */ + void showImportDehumidifierInstructionDialog( List list) { + String[] models = list.stream() + .map(data -> data.getBrand() + "-" + data.getModel()) + .toArray(String[]::new); + new SpinnerDialog(mContext, "除湿机型号(注意区别空调)", "请选择型号", models, selection -> { + //从服务器读取空调指令json,并导入至红外控制器 + presenter.importDehumidifierInstruction(list, selection); + }).show(); + } + + /** + * 地暖配置 + */ + void setFloor() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + RoomSetting.getFloorName() + "配置"); + setList.clear(); + setList.add(new Set("开启" + RoomSetting.getFloorName(), RoomController.floor.isEnable(), (compoundButton, b) -> { + RoomController.floor.setEnable(b); + setFloor(); + })); + setList.add(new Set("地暖别名", RoomSetting.getFloorName(), view -> + new SpinnerDialog(mContext, "地暖别名", "请选择显示名称", new String[]{"地暖", "升温板"}, str -> { + MMKVUtil.put(RoomSetting.FLOOR_NAME, str); + setFloor(); + }).show())); + setList.add(new Set(getString(R.string.max_gear), RoomController.floor.getMaxGear() + "档", v1 -> + new SpinnerDialog(mContext, getString(R.string.max_gear), "请选择" + RoomSetting.getFloorName() + "最大档位", new String[]{"1", "2", "3", "4", "5"}, selection -> { + RoomController.floor.setMaxGear(Integer.parseInt(selection)); + setFloor(); + successTips("最大档位已设置为:" + Integer.parseInt(selection) + "档"); + }).show())); + if (!Device.isIntegratedController() //集成式控制器 + && !RoomController.relay.getModel().equals(RoomController.relay.getModels()[1])//型号为中胜0-10V模拟量控制的继电器 + && !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 -> + new SetDeviceBaseDialog(mContext, "地暖" + RoomSetting.getFloorName(), RoomController.floor.getModels(), + getString(R.string.mode), SpinnerList.ADDRESS, getString(R.string.address), (model, address) -> { + RoomController.floor.setModel(model); + RoomController.floor.setAddress(address); + setFloor(); + }).show())); + } + for (int i = 1; i <= 5; i++) { + int finalI = i; + setList.add(new Set(i + getString(R.string.gear), RoomController.floor.getPwm(i).toString(), view -> + new SetPWMDialog(mContext, finalI, () -> setFloor()).show())); + } + setList.add(new Set("导入预设PWM", view -> { + if (StringUtils.isEmpty(RoomController.floor.getModel())) { + Toasty.info(mContext, "请先配置" + RoomSetting.getFloorName() + "型号").show(); + new ConfirmDialog(mContext, getString(R.string.error), RoomSetting.getFloorName() + "型号尚未配置", null).show(); + } else { + new ConfirmDialog(mContext, getString(R.string.set_pwm), "确定导入" + RoomSetting.getFloorName() + "预设配置吗?", () -> { + Floor.importDefaultPWMToFloor(); + setFloor(); + successTips("成功导入预设配置"); + }).show(); + } + })); + if (RoomSensor.consumptionSensor.isAvailable()) { + //只有能耗传感器可用,才可自动设置PWM + setList.add(new Set("自动匹配" + RoomSetting.getFloorName() + "功率", view -> + new ConfirmDialog(mContext, getString(R.string.set_pwm), "是否开始自动设置PWM与功率?\n此过程需要能耗传感器支持且耗时较长", () -> presenter.autoSetFloorPWM()).show())); + } + setAdapter.setDataList(setList); + } + + /** + * 传感器配置 + */ + private void setSensor() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_sensor)); + setList.clear(); + setList.add(new Set("动态配置-室内温湿传感器", view -> setIndoorSensor(0))); + setList.add(new Set("动态配置-室内CO2传感器", view -> setIndoorSensor(1))); + setList.add(new Set("动态配置-室内氨气传感器", view -> setIndoorSensor(2))); + setList.add(new Set("动态配置-地面温度传感器", view -> setIndoorSensor(3))); + boolean needHideConsumptionSensor = RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]); + List devices = new ArrayList<>(); + Collections.addAll(devices, RoomSensor.outdoorTHSensor, RoomSensor.consumptionSensor, RoomSensor.airTHSensor, RoomSensor.co2OutdoorSensor); + for (Sensor sensor : devices) { + if (!needHideConsumptionSensor || !(sensor instanceof ConsumptionSensor)) { + setList.add(new Set(sensor.getName(), sensor.getBaseInfo(), view -> new SetSensorDialog(mContext, sensor, s -> setSensor()).show())); + } + } + setAdapter.setDataList(setList); + } + + /** + * 室内温湿度传感器设置 + * + * @param type 0 温湿度传感器,1 CO2传感器, 2 氨气传感器 3 地面温度传感器 + */ + private void setIndoorSensor(int type) { + toMain = 1; + String typeStr = type == 0 ? "温湿度" : type == 1 ? "CO2" : type == 2 ? "氨气" : "地面温度"; + String title = "动态配置-" + typeStr; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_sensor) + " > " + title + "传感器"); + setList.clear(); + setList.add(new Set("新增", view -> new TextViewDialog(mContext, "请输入" + title + "传感器名称", typeStr + "传感器", str -> { + if (StringUtils.isEmpty(str)) { + Toasty.error(mContext, "传感器名称不能为空").show(); + return; + } + String message = "新增传感器" + str + "成功"; + boolean success = type == 0 ? IndoorTHSensorUtil.insert(new THSensor(str)) : + type == 1 ? IndoorCO2SensorUtil.insert(new GasSensor(str)) : + type == 2 ? IndoorAmmoniaSensorUtil.insert(new GasSensor(str)) : IndoorFloorTSensorUtil.insert(new FloorTSensor(str)); + if (success) { + Toasty.success(mContext, message).show(); + } else { + Toasty.error(mContext, message.replace("成功", "失败,已存在同名传感器")).show(); + } + setIndoorSensor(type); + }, false).show())); + setList.add(new Set("删除", view -> new SpinnerDialog(mContext, "删除" + title + "传感器", "请选择要删除的传感器", + type == 0 ? IndoorTHSensorUtil.getAllNames() : type == 1 ? IndoorCO2SensorUtil.getAllNames() : type == 2 ? + IndoorAmmoniaSensorUtil.getAllNames() : IndoorFloorTSensorUtil.getAllNames(), str -> { + if (type == 0) { + IndoorTHSensorUtil.deleteByName(str); + } else if (type == 1) { + IndoorCO2SensorUtil.deleteByName(str); + } else if (type == 2) { + IndoorAmmoniaSensorUtil.deleteByName(str); + } else if (type == 3) { + IndoorFloorTSensorUtil.deleteByName(str); + } + Toasty.success(mContext, "删除传感器" + str + "成功").show(); + setIndoorSensor(type); + }).show())); + List sensors = type == 0 ? IndoorTHSensorUtil.getAllSensors() : + type == 1 ? IndoorCO2SensorUtil.getAllSensors() : type == 2 ? IndoorAmmoniaSensorUtil.getAllSensors() : IndoorFloorTSensorUtil.getAllSensors(); + if (sensors != null) { + for (Sensor sensor : sensors) { + setList.add(new Set(sensor.getName(), sensor.getBaseInfo(), view -> new SetSensorDialog(mContext, sensor, s -> { + IndoorTHSensorUtil.update(s); + setIndoorSensor(type); + }).show())); + } + } + setAdapter.setDataList(setList); + } + + /** + * 高级配置,需要运维密码 + */ + public void setAdvanced() { + toMain = 0; + binding.tvTitle.setText(getText(R.string.set) + " > " + getText(R.string.set_advanced)); + setList.clear(); + setList.add(new Set("恢复出厂设置", view -> new ConfirmDialog(mContext, getString(R.string.error), "确定恢复出厂设置吗?\n设备将在初始化后重启", () -> { + MMKVUtil.clear(); + //清空数据后需要恢复主机码 否则无法上传最新配置 + MMKVUtil.put(HardwareSetting.HOST_ID, Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)); + presenter.uploadSet(); + }).show())); + setList.add(new Set("调试工具", view -> startActivity(new Intent(mContext, DebugActivity.class)))); + setList.add(new Set("指令设置——" + RoomController.airConditionInfrared.getName(), null, view -> { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.airCondition.setPowerSupply(true))); + new AirConditionAddDialog(mContext, RoomController.airConditionInfrared).show(); + })); + setList.add(new Set("指令设置——" +RoomController.airConditionInfrared2.getName(), null, view -> { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.airCondition.setPowerSupply(true))); + new AirConditionAddDialog(mContext, RoomController.airConditionInfrared2).show(); + })); + setList.add(new Set("指令设置——" +RoomController.dehumidifierInfrared.getName(), null, view -> { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifierInfrared.setPowerSupply(true))); + new DehumidityAddDialog(mContext, RoomController.dehumidifierInfrared).show(); + })); + setList.add(new Set("PID测试及自整定", null, view -> { + startActivity(new Intent(this, ComposeActivity.class)); + ; + })); + setAdapter.setDataList(setList); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivityPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivityPresenter.java new file mode 100644 index 0000000..e827f1c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/SetActivityPresenter.java @@ -0,0 +1,780 @@ +package com.example.iot_controlhost.ui.activity; + +import android.app.Application; +import android.util.Pair; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.GsonUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.model.ParameterizedTypeImpl; +import com.example.iot_controlhost.model.SingleLiveEvent; +import com.example.iot_controlhost.model.controller.Controller; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.net.AirConditionList; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.net.WebSet; +import com.example.iot_controlhost.model.sensor.FloorTSensor; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.CsvConverter; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorAmmoniaSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.example.iot_controlhost.utils.old.OldSet; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.io.File; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import android_serialport_api.SerialPortUtil; + +public class SetActivityPresenter extends BasePresenter { + + public MutableLiveData refreshLiveData = new SingleLiveEvent<>(); + public MutableLiveData>> showImportAirConditionInstructionDialogLiveData = new MutableLiveData<>(); + public MutableLiveData> showImportDehumidifierInstructionDialogLiveData = new MutableLiveData<>(); + + public SetActivityPresenter(@NonNull Application application) { + super(application); + } + + public MutableLiveData getRefreshLiveData() { + return refreshLiveData; + } + + public MutableLiveData>> getShowImportAirConditionInstructionDialogLiveData() { + return showImportAirConditionInstructionDialogLiveData; + } + + public MutableLiveData> getShowImportHumidifierInstructionDialogLiveData() { + return showImportDehumidifierInstructionDialogLiveData; + } + + /** + * 地暖-自动设置PWM + */ + public void autoSetFloorPWM() { + showLoadingDialog("正在自动设置中,请耐心等待"); + //打开传感器与控制器串口 + if (SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR).openSerialPort() && SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + // 持续采集能耗信息 + pollingTask.startPollingTaskOnIOThread(RxTag.TAG_COLLECT_SENSOR_SET, 1, () -> RoomSensor.consumptionSensor.getAllSensorData()); + } else { + MyLog.controllerError("串口打开失败,无法设置" + RoomSetting.getFloorName()); + } + RxJavaUtils.executeAsyncTask(new RxAsyncTask(null) { + @Override + public Boolean doInIOThread(String str) {// 清空能耗传感器数据 + try { + RoomSensor.consumptionSensor.initData(); + //获取当前最低功率 + double minPower = RoomSensor.consumptionSensor.getPower(); + int maxPWM = RoomController.floor.getMaxPower(); + MyLog.controller(RoomSetting.getFloorName() + "打开最高功率,PWM值:" + maxPWM); + showLoadingDialogInIO("打开" + RoomSetting.getFloorName() + "最高功率,PWM值:" + maxPWM); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.floor.setGearByPWM(maxPWM))); + Thread.sleep(5000); + double maxPower = RoomSensor.consumptionSensor.getPower(); + showLoadingDialogInIO("获取当前最高功率:" + maxPower + "\t最低功率:" + minPower); + MyLog.controller("获取当前最高功率:" + maxPower + "\t最低功率:" + minPower); + if (minPower >= maxPower || maxPower == 0) { + MyLog.controllerError("采集到的最高功率异常,已提前结束匹配。"); + return false; + } + RoomController.floor.getPwm(5).setPwm(maxPWM); + RoomController.floor.getPwm(5).setPower(maxPower - minPower); + //获取调节功率偏差 + double powerBias = maxPower * Variable.FLOOR_GEAR_BIAS; + MyLog.controller("获取调节功率偏差:" + powerBias); + //获得每级档位应该增加的功率 + double avg = (maxPower - minPower) / 5.0; + MyLog.controller("获得每级档位应该增加的功率:" + avg); + //PWM逐渐增加 设置PWM + int PWM = 1000; + int temp = maxPWM / 30; + for (int i = 1; i < 5; i++) { + double targetPower = minPower + i * avg; + showLoadingDialogInIO(i + "档目标功率:" + targetPower); + MyLog.controller(i + "档目标功率:" + targetPower); + for (int pwm = PWM; pwm < maxPWM; pwm += temp) { + int finalPwm = pwm; + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.floor.setGearByPWM(finalPwm))); + showLoadingDialogInIO("逐级增加等待读取最新功率,当前PWM值:" + pwm); + MyLog.controller("逐级增加等待读取最新功率,当前PWM值:" + pwm); + Thread.sleep(4000); + double curPower = RoomSensor.consumptionSensor.getPower(); + MyLog.controller("最新功率:" + curPower); + showLoadingDialogInIO("最新功率:" + curPower); + if (curPower > targetPower - powerBias) { + //功率符合条件,则记录当前PWM和功率到对应档位信息中 + showLoadingDialogInIO(curPower + "W功率符合条件,记录当前PWM和功率到" + i + "档信息中"); + MyLog.controller("功率符合条件,记录当前PWM和功率到" + i + "档信息中"); + RoomController.floor.getPwm(i).setPwm(pwm); + RoomController.floor.getPwm(i).setPower(curPower - minPower); + PWM = pwm; + break; + } else { + showLoadingDialogInIO("当前PWM值:" + pwm + "\t当前功率:" + curPower + "不符合要求\n" + + "要求最小功率:" + (targetPower - powerBias)); + MyLog.controller("当前PWM值:" + pwm + "\t当前功率:" + curPower + "不符合要求\n" + + "要求最小功率:" + (targetPower - powerBias)); + } + } + } + showLoadingDialogInIO("自动匹配完成"); + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + return false; + } + pollingTask.stopPollingTask(RxTag.TAG_COLLECT_SENSOR_SET); + RoomController.floor.setGear(0); + MyLog.controller(RoomSetting.getFloorName() + "PWM自动匹配成功"); + return true; + } + + @Override + public void doInUIThread(Boolean success) { + hideLoadingDialog(); + if (success) { + successTips(RoomSetting.getFloorName() + "PWM自动匹配成功"); + } else { + errorTips("采集到的最高功率异常,已提前结束匹配。\n请检查能耗传感器配置或" + RoomSetting.getFloorName() + "配置"); + } + refreshLiveData.setValue(0); + } + }); + } + + /** + * 获得空调型号列表 + * + * @param airId 空调Id 第几个空调 为-1的时候是除湿机 + */ + public void getAirConditionModelList(int airId) { + showLoadingDialog(getString(R.string.in_get_air_condition_model_list)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getAirConditionerModelList); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(AirConditionList model) { + hideLoadingDialog(); + if (airId == -1) { + showImportDehumidifierInstructionDialogLiveData.setValue(model.getData()); + } else { + showImportAirConditionInstructionDialogLiveData.setValue(new Pair(airId, model.getData())); + } + } + + @Override + public void error(Throwable e) { + //获取空调型号列表失败 + errorTips("获取空调型号列表失败"); + hideLoadingDialog(); + } + }); + } + + /** + * 导入空调指令 + * + * @param list 空调指令列表 + * @param targetAirCondition 目标空调型号 + * @param airId 空调Id + */ + public void importAirConditionInstruction(List list, String targetAirCondition, int airId) { + showLoadingDialog(getString(R.string.in_import_air_condition_instruction)); + String targetId = null; + for (AirConditionList.DataDTO dataDTO : list) { + if ((dataDTO.getBrand() + "-" + dataDTO.getModel()).equals(targetAirCondition)) { + targetId = dataDTO.getId(); + break; + } + } + + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getAirConditionerCommandList); + params.addParameter("id", targetId); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + String json = model.getData().toString(); + String unescapedJsonString = json.replace("\\\"", "\""); + //成功获得列表 + List instructions = GsonUtils.fromJson(unescapedJsonString, new ParameterizedTypeImpl(json.getClass())); + //开始导入空调指令 + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + boolean result = true; + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + return false; + } + int size = instructions.size(); + for (int i = 1; i <= size; i++) { + String instruction = instructions.get(i - 1); + if (MyUtil.checkInstruction(instruction)) { + if (airId == 1) { + result = RoomController.airConditionInfrared.writeInstruction(i, instruction) && result; + } else { + result = RoomController.airConditionInfrared2.writeInstruction(i, instruction) && result; + } + showLoadingDialogInIO("正在导入" + targetAirCondition + "型空调\n第" + i + "/" + size + "条指令"); + MyLog.controller("正在导入第" + i + "/" + size + "条指令:" + instruction); + } else { + showLoadingDialogInIO("正在导入" + targetAirCondition + "型空调\n第" + i + "条,指令错误!!!"); + MyLog.controller("正在导入第" + i + "条指令:" + ",云端指令有误"); + Thread.sleep(100); + } + } + if (airId == 1) { + RoomController.airCondition.setAirConditionModel(targetAirCondition); + } else { + RoomController.airCondition.setAirConditionModel2(targetAirCondition); + } + return result; + } + + @Override + public void doInUIThread(Boolean b) { + hideLoadingDialog(); + if (b) { + successTips("成功导入所有空调指令"); + } else { + errorTips("导入空调指令失败"); + } + refreshLiveData.setValue(2); + } + }); + } + + @Override + public void error(Throwable e) { + //获取空调指令列表失败 + hideLoadingDialog(); + errorTips("获取空调指令列表失败"); + } + }); + } + + /** + * 导入除湿机指令 + * + * @param list 指令列表 + * @param targetAirCondition 目标空调型号 + */ + public void importDehumidifierInstruction(List list, String targetAirCondition) { + showLoadingDialog(getString(R.string.in_import_air_condition_instruction)); + String targetId = null; + for (AirConditionList.DataDTO dataDTO : list) { + if ((dataDTO.getBrand() + "-" + dataDTO.getModel()).equals(targetAirCondition)) { + targetId = dataDTO.getId(); + break; + } + } + + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getAirConditionerCommandList); + params.addParameter("id", targetId); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + String json = model.getData().toString(); + String unescapedJsonString = json.replace("\\\"", "\""); + //成功获得列表 + List instructions = GsonUtils.fromJson(unescapedJsonString, new ParameterizedTypeImpl(json.getClass())); + //开始导入指令 + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + return false; + } + // 只有开机关机两条指令 + String openInstruction = instructions.get(0); + boolean result = RoomController.airConditionInfrared.writeInstruction(200, openInstruction); + if (!result) { + showLoadingDialogInIO("导入" + targetAirCondition + "型空调\n开机指令,指令错误!!!"); + MyLog.controller("导入开机指令,云端指令有误"); + Thread.sleep(100); + return false; + } + String closeInstruction = instructions.get(1); + result = RoomController.airConditionInfrared.writeInstruction(201, closeInstruction); + if (!result) { + showLoadingDialogInIO("导入" + targetAirCondition + "型空调\n关机指令,指令错误!!!"); + MyLog.controller("导入关机指令,云端指令有误"); + Thread.sleep(100); + return false; + } + RoomController.dehumidifierInfrared.setModel(targetAirCondition); + return result; + } + + @Override + public void doInUIThread(Boolean b) { + hideLoadingDialog(); + if (b) { + successTips("成功导入所有空调指令"); + } else { + errorTips("导入空调指令失败"); + } + refreshLiveData.setValue(2); + } + }); + } + + @Override + public void error(Throwable e) { + //获取空调指令列表失败 + hideLoadingDialog(); + errorTips("获取空调指令列表失败"); + } + }); + } + + /** + * 导入配置 + */ + public void importSet(String selection) { + if (selection.equals("预设")) { + importDefaultSet(); + } else if (selection.equals("基地")) { + importBaseSet(); + } else if (selection.equals("会东")) { + importBaseSetHD(); + } else if (selection.equals("旧版本")) { + OldSet.importOldSet(getApplication()); + } else if (selection.equals("兴文石海")) { + importXWSKSet(); + } else if (selection.equals("主干继电器-从机模式")) { + importBBITRelaySet(); + } + successTips("成功导入<" + selection + ">配置"); + } + + /** + * 主干继电器配置 + */ + private void importBBITRelaySet() { + HardwareSetting.setSensorSerialCom("/dev/ttyS0"); + HardwareSetting.setSensorSerialComBaud("9600"); + HardwareSetting.setControllerSerialCom("/dev/ttyS8"); + HardwareSetting.setControllerSerialBaud("9600"); + //继电器配置 + RoomController.relay.setEnable(true); + RoomController.relay.setAddress("01"); + RoomController.relay.setModel(RoomController.relay.getModels()[2]); + RoomController.intakeFan.clearAndSetPort(1); + RoomController.evenFan.clearAndSetPort(2); + RoomController.airCondition.clearAndSetPort(3); + RoomController.humidifier.clearAndSetPort(4); + RoomController.relayFloor.clearAndSetPort(5); + //空调默认配置 + RoomController.airConditionInfrared.setEnable(true); + RoomController.airConditionInfrared.setModel(RoomController.airConditionInfrared.getModels()[0]); + RoomController.airConditionInfrared.setAddress("02"); + //空调2默认配置 + RoomController.airConditionInfrared2.setEnable(false); + RoomController.airConditionInfrared2.setModel(RoomController.airConditionInfrared2.getModels()[0]); + RoomController.airConditionInfrared2.setAddress("02"); + //地暖默认配置 + RoomController.floor.setEnable(true); + MMKVUtil.put(RoomSetting.FLOOR_NAME, "升温板"); + Floor.importDefaultPWMToFloor(); + //传感器配置 + THSensor thSensor1 = new THSensor("室内温湿度传感器1"); + thSensor1.setAddress("01"); + thSensor1.setEnable(true); + IndoorTHSensorUtil.insert(thSensor1); + THSensor thSensor2 = new THSensor("室内温湿度传感器2"); + thSensor2.setAddress("02"); + thSensor2.setEnable(true); + IndoorTHSensorUtil.insert(thSensor2); + RoomSensor.outdoorTHSensor.setAddress("03"); + RoomSensor.outdoorTHSensor.setEnable(true); + GasSensor gasSensor = new GasSensor("室内CO2传感器1"); + gasSensor.setAddress("05"); + gasSensor.setEnable(true); + IndoorCO2SensorUtil.insert(gasSensor); + } + + /** + * 兴文石海配置 + */ + private void importXWSKSet() { + HardwareSetting.setControllerSerialCom("/dev/ttyS8"); + HardwareSetting.setSensorSerialCom("/dev/ttyS0"); + //继电器配置 + RoomController.relay.setEnable(true); + RoomController.relay.setAddress("02"); + RoomController.relay.setModel(RoomController.relay.getModels()[1]); + RoomController.intakeFan.clearAndSetPort(1); + RoomController.humidifier.clearAndSetPort(2); + RoomController.airCondition.clearAndSetPort(3); + RoomController.exhaustFan.clearAndSetPort(4); + RoomController.relayFloor.clearAndSetPort(5); + //空调默认配置 + RoomController.airConditionInfrared.setEnable(true); + RoomController.airConditionInfrared.setModel(RoomController.airConditionInfrared.getModels()[0]); + RoomController.airConditionInfrared.setAddress("01"); + RoomController.airConditionInfrared2.setEnable(false); + RoomController.airConditionInfrared2.setModel(RoomController.airConditionInfrared2.getModels()[0]); + RoomController.airConditionInfrared2.setAddress("01"); + //地暖默认配置 + RoomController.floor.setEnable(true); + Floor.importDefaultPWMToFloor(); + //传感器配置 + THSensor thSensor1 = new THSensor("室内温湿度传感器1"); + thSensor1.setAddress("01"); + thSensor1.setEnable(true); + IndoorTHSensorUtil.insert(thSensor1); + THSensor thSensor2 = new THSensor("室内温湿度传感器2"); + thSensor2.setAddress("02"); + thSensor2.setEnable(true); + IndoorTHSensorUtil.insert(thSensor2); + RoomSensor.outdoorTHSensor.setAddress("03"); + RoomSensor.outdoorTHSensor.setEnable(true); + RoomSensor.consumptionSensor.setAddress("04"); + RoomSensor.consumptionSensor.setEnable(true); + GasSensor gasSensor = new GasSensor("室内CO2传感器1"); + gasSensor.setAddress("05"); + gasSensor.setEnable(true); + IndoorCO2SensorUtil.insert(gasSensor); + FloorTSensor floorTSensor = new FloorTSensor("地面温度传感器"); + floorTSensor.setAddress("06"); + floorTSensor.setEnable(true); + IndoorFloorTSensorUtil.insert(floorTSensor); + } + + /** + * 旧系统配置-公司 + */ + public void importDefaultSet() { + //控制器默认配置 + Controller.setControllerModel(Controller.CONTROLLER_MODEL[0]); + HardwareSetting.setWorkMode(HardwareSetting.CONTROLLER_WORK_MODE[0]); + HardwareSetting.setControllerSerialCom("/dev/ttyS5"); + HardwareSetting.setControllerSerialBaud("9600"); + HardwareSetting.setSensorSerialCom("/dev/ttyS1"); + HardwareSetting.setSensorSerialComBaud("9600"); + //继电器配置 + RoomController.relay.setEnable(true); + RoomController.relay.setAddress("02"); + RoomController.relay.setModel(RoomController.relay.getModels()[0]); + RoomController.relayFloor.clearAllPort(); + RoomController.airCondition.clearAndSetPort(1); + RoomController.humidifier.clearAndSetPort(2); + RoomController.intakeFan.clearAndSetPort(3); + RoomController.evenFan.clearAndSetPort(4); + RoomController.exhaustFan.clearAndSetPort(5); + RoomController.redLight.clearAndSetPort(6); + RoomController.whiteLight.clearAndSetPort(7); + RoomController.uvLight.clearAndSetPort(8); + //空调默认配置 + RoomController.airConditionInfrared.setEnable(true); + RoomController.airConditionInfrared.setModel(RoomController.airConditionInfrared.getModels()[0]); + RoomController.airConditionInfrared.setAddress("01"); + //地暖默认配置 + RoomController.floor.setEnable(true); + RoomController.floor.setModel(RoomController.floor.getModels()[0]); + RoomController.floor.setAddress("05"); + RoomController.floor.setMaxGear(5); + Floor.importDefaultPWMToFloor(); + //初始化传感器型号为默认型号 + for (Sensor sensor : RoomSensor.getAllSensors()) { + sensor.setModel(sensor.getModels()[0]); + } + //传感器配置 + THSensor thSensor1 = new THSensor("室内温湿度传感器1"); + thSensor1.setAddress("01"); + thSensor1.setEnable(true); + IndoorTHSensorUtil.insert(thSensor1); + THSensor thSensor2 = new THSensor("室内温湿度传感器2"); + thSensor2.setAddress("02"); + thSensor2.setEnable(true); + IndoorTHSensorUtil.insert(thSensor2); + RoomSensor.outdoorTHSensor.setAddress("03"); + RoomSensor.outdoorTHSensor.setEnable(true); + RoomSensor.consumptionSensor.setAddress("04"); + RoomSensor.consumptionSensor.setEnable(true); + GasSensor gasSensor = new GasSensor("室内CO2传感器1"); + gasSensor.setAddress("05"); + gasSensor.setEnable(true); + IndoorCO2SensorUtil.insert(gasSensor); + FloorTSensor floorTSensor = new FloorTSensor("地面温度传感器"); + floorTSensor.setAddress("06"); + floorTSensor.setEnable(true); + IndoorFloorTSensorUtil.insert(floorTSensor); + GasSensor gasSensor2 = new GasSensor("氨气传感器1"); + gasSensor.setAddress("07"); + gasSensor.setEnable(true); + IndoorAmmoniaSensorUtil.insert(gasSensor2); + RoomSensor.airTHSensor.setEnable(false); + } + + public void importBaseSetHD() { + //控制器默认配置 + Controller.setControllerModel(Controller.CONTROLLER_MODEL[0]); + HardwareSetting.setWorkMode(HardwareSetting.CONTROLLER_WORK_MODE[0]); + HardwareSetting.setControllerSerialCom("/dev/ttyS8"); + HardwareSetting.setControllerSerialBaud("9600"); + HardwareSetting.setSensorSerialCom("/dev/ttyS0"); + HardwareSetting.setSensorSerialComBaud("9600"); + //继电器配置 + RoomController.relay.setEnable(true); + RoomController.relay.setAddress("01"); + RoomController.relay.setModel(RoomController.relay.getModels()[2]); + RoomController.relayFloor.clearAndSetPort(3); + RoomController.airCondition.clearAndSetPort(4); + RoomController.redLight.clearAndSetPort(5); + RoomController.humidifier.clearAndSetPort(2); + RoomController.intakeFan.clearAndSetPort(1); + //地暖默认配置 + RoomController.floor.setEnable(true); + RoomController.floor.setModel(RoomController.floor.getModels()[0]); + RoomController.floor.setAddress("01"); + RoomController.floor.setMaxGear(5); + Floor.importDefaultPWMToFloor(); + //空调默认配置 + RoomController.airConditionInfrared.setEnable(true); + RoomController.airConditionInfrared.setModel(RoomController.airConditionInfrared.getModels()[0]); + RoomController.airConditionInfrared.setAddress("01"); + //初始化传感器型号为默认型号 + for (Sensor sensor : RoomSensor.getAllSensors()) { + sensor.setModel(sensor.getModels()[0]); + } + //传感器配置 + THSensor thSensor1 = new THSensor("室内温湿度传感器1"); + thSensor1.setAddress("01"); + thSensor1.setEnable(true); + IndoorTHSensorUtil.insert(thSensor1); + THSensor thSensor2 = new THSensor("室内温湿度传感器2"); + thSensor2.setAddress("02"); + thSensor2.setEnable(true); + IndoorTHSensorUtil.insert(thSensor2); + RoomSensor.outdoorTHSensor.setAddress("03"); + RoomSensor.outdoorTHSensor.setEnable(true); + GasSensor gasSensor = new GasSensor("室内CO2传感器1"); + gasSensor.setAddress("05"); + gasSensor.setEnable(true); + IndoorCO2SensorUtil.insert(gasSensor); + + RoomSensor.consumptionSensor.setEnable(false); + RoomSensor.airTHSensor.setEnable(false); + } + + /** + * 旧系统配置-基地 + */ + public void importBaseSet() { + importDefaultSet(); + HardwareSetting.setControllerSerialCom("/dev/ttyS1"); + HardwareSetting.setSensorSerialCom("/dev/ttyS0"); + RoomController.relay.setAddress("01"); + RoomController.intakeFan.clearAndSetPort(1); + RoomController.humidifier.clearAndSetPort(2); + RoomController.airCondition.clearAndSetPort(3); + RoomController.evenFan.clearAllPort(); + RoomController.exhaustFan.clearAllPort(); + RoomController.redLight.clearAllPort(); + RoomController.whiteLight.clearAllPort(); + RoomController.uvLight.clearAllPort(); + RoomController.floor.setAddress("01"); + RoomSensor.airTHSensor.setEnable(false); + } + + public static String getCurrentSetJsonString() { + Map map = new LinkedHashMap<>(); + //基础信息 + map.put(RoomSetting.USER_NAME, RoomSetting.getUserName()); + //系统配置 + map.put(RoomSetting.SET_PASSWORD, RoomSetting.getSetPassword()); + map.put(RoomSetting.MAIN_ACTIVITY_LOCK, RoomSetting.getMainActivityLock()); + map.put(RoomSetting.MAIN_ACTIVITY_OPERATE_LOCK_TIMEOUT, RoomSetting.getMainActivityOperateLockTimeOut()); + map.put(RoomSetting.NOTICE_TYPE, RoomSetting.getNoticeType()); + //控制器配置 + map.put(Device.MY_CONTROLLER_MODEL, Controller.getControllerModel()); + map.put(HardwareSetting.MY_CONTROLLER_WORK_MODE, HardwareSetting.getWorkMode()); + //集成式 + map.put(HardwareSetting.CONTROLLER_SERIAL_COM, HardwareSetting.getControllerSerialCom()); + map.put(HardwareSetting.CONTROLLER_SERIAL_BAUD, HardwareSetting.getControllerSerialBaud()); + map.put(HardwareSetting.SENSOR_SERIAL_COM, HardwareSetting.getSensorSerialCom()); + map.put(HardwareSetting.SENSOR_SERIAL_COM_BAUD, HardwareSetting.getSensorSerialComBaud()); + //装配式 + map.put(HardwareSetting.COMMON_ADDRESS, HardwareSetting.getCommonAddress()); + //继电器配置 + map.put(Relay.TAG + Device.ENABLE, RoomController.relay.isEnable()); + map.put(Relay.TAG + Device.MODEL, RoomController.relay.getModel()); + map.put(Relay.TAG + Device.ADDRESS, RoomController.relay.getAddress()); + for (Relay relay : RoomController.getAllRelayDevice()) { + map.put(relay.PORT, MMKVUtil.get(relay.PORT, "[]")); + } + //空调配置 + map.put(RoomController.DEVICE_NAME[9] + Device.ENABLE, RoomController.airConditionInfrared.isEnable()); + map.put(RoomController.DEVICE_NAME[9] + Device.MODEL, RoomController.airConditionInfrared.getModel()); + map.put(RoomController.DEVICE_NAME[9] + Device.ADDRESS, RoomController.airConditionInfrared.getAddress()); + //除湿器配置 + map.put(RoomController.DEVICE_NAME[15] + Device.ENABLE, RoomController.dehumidifierInfrared.isEnable()); + map.put(RoomController.DEVICE_NAME[15] + Device.MODEL, RoomController.dehumidifierInfrared.getModel()); + map.put(RoomController.DEVICE_NAME[15] + Device.ADDRESS, RoomController.dehumidifierInfrared.getAddress()); + //空调2配置 + map.put(RoomController.DEVICE_NAME[14] + Device.ENABLE, RoomController.airConditionInfrared2.isEnable()); + map.put(RoomController.DEVICE_NAME[14] + Device.MODEL, RoomController.airConditionInfrared2.getModel()); + map.put(RoomController.DEVICE_NAME[14] + Device.ADDRESS, RoomController.airConditionInfrared2.getAddress()); + //地暖配置 + map.put(RoomController.DEVICE_NAME[0] + Device.ENABLE, RoomController.floor.isEnable()); + map.put(RoomController.DEVICE_NAME[0] + Device.MODEL, RoomController.floor.getModel()); + map.put(RoomController.DEVICE_NAME[0] + Device.ADDRESS, RoomController.floor.getAddress()); + map.put(Floor.MAX_GEAR, RoomController.floor.getMaxGear()); + for (int i = 1; i <= 5; i++) { + Floor.PWM pwm = RoomController.floor.getPwm(i); + map.put(RoomController.DEVICE_NAME[0] + i + "档PWM", pwm.getPwm()); + map.put(RoomController.DEVICE_NAME[0] + i + "档功率", pwm.getPower()); + } + //传感器配置 + for (Sensor sensor : RoomSensor.getAllSensors()) { + map.put(sensor.getName() + Device.ENABLE, sensor.isEnable()); + map.put(sensor.getName() + Device.MODEL, sensor.getModel()); + map.put(sensor.getName() + Device.ADDRESS, sensor.getAddress()); + } + return GsonUtils.toJson(new WebSet(System.currentTimeMillis(), map)); + } + + /** + * 上传当前APP所有配置至服务器 + */ + public void uploadSet() { + showLoadingDialog("正在上传配置,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.uploadConfig); + params.addParameter("ConfigJson", getCurrentSetJsonString()); + params.addParameter("hostCode", HardwareSetting.getHostId()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.network("配置已上传"); + successTips("配置已上传"); + } + + @Override + public void error(Throwable e) { + errorTips("配置上传失败"); + MyLog.networkError("配置上传失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + hideLoadingDialog(); + refreshLiveData.setValue(1); + } + }); + } + + /** + * 验证运维密码 + */ + public void checkDevOpsPassword(String pwd) { + showLoadingDialog("正在验证运维密码,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.checkDevOpsPassword); + params.addParameter("AreaCode", RoomSetting.getAreaCode()); + params.addParameter("Secret", pwd); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.network("运维密码验证通过"); + String msg = model.getMessage(); + if ("验证通过".equals(msg)) { + successTips(msg); + refreshLiveData.setValue(3); + } else { + errorTips(msg); + } + } + + @Override + public void onSuccessError(CommonResponse data) { + super.onSuccessError(data); + errorTips(data.getMessage()); + } + + @Override + public void error(Throwable e) { + errorTips("运维密码验证失败"); + MyLog.networkError("运维密码验证失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + hideLoadingDialog(); + } + }); + } + + public void uploadLog() { + showLoadingDialog("正在上传日志"); + RxJavaUtils.executeAsyncTask(new RxAsyncTask(null) { + @Override + public File doInIOThread(Object o) { + List myObjects = LogDBManager.queryAllList(); // 获取对象列表 + return CsvConverter.writeLogToCsvFile(myObjects); +// CsvConverter.saveSensorDataToCsv(); + } + + @Override + public void doInUIThread(File file) { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.uploadLog); + params.addParameter("hostCode", HardwareSetting.getHostId()); + params.addParameter("log", file); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.network("日志已上传"); + successTips("日志已上传"); + } + + @Override + public void error(Throwable e) { + errorTips("日志上传失败"); + MyLog.networkError("日志上传失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + file.delete(); + hideLoadingDialog(); + } + }); + } + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/SplashActivity.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/SplashActivity.java new file mode 100644 index 0000000..0749e67 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/SplashActivity.java @@ -0,0 +1,44 @@ +package com.example.iot_controlhost.ui.activity; + +import android.app.Dialog; +import android.content.Intent; + +import com.example.iot_controlhost.base.BaseActivity; +import com.example.iot_controlhost.databinding.ActivitySplashBinding; +import com.example.iot_controlhost.ui.dialog.RegisterDialog; +import com.example.iot_controlhost.utils.global.HardwareSetting; + +public class SplashActivity extends BaseActivity { + /** + * 注册弹窗 + */ + Dialog registerDialog; + + @Override + public void initView() { + if (!HardwareSetting.isRegistered()) { + registerDialog = new RegisterDialog(mContext); + registerDialog.show(); + } + presenter.setActivity(this); + presenter.getProcessLiveData().observe(this, info -> { + try { + binding.progressBar.setProgress(info.getProcess(), true); + } catch (Exception e) { + e.printStackTrace(); + } + binding.tvTip.setText(info.getTips()); + if(info.getProcess() == 100){ + startActivity(new Intent(getApplication(), MainActivity.class)); + finish(); + } + }); + presenter.getNeedHideRegisterDialog().observe(this, aBoolean -> { + if (aBoolean && registerDialog != null) { + registerDialog.dismiss(); + } + }); + presenter.init(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/activity/SplashActivityPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/activity/SplashActivityPresenter.java new file mode 100644 index 0000000..a7ece1a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/activity/SplashActivityPresenter.java @@ -0,0 +1,327 @@ +package com.example.iot_controlhost.ui.activity; + + +import android.app.Activity; +import android.app.Application; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.net.WebSet; +import com.example.iot_controlhost.model.sensor.FloorTSensor; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.service.MQTTService; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.control.DeviceAnomalyDetection; +import com.example.iot_controlhost.utils.database.EnergyDBManager; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorAmmoniaSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.hjq.permissions.OnPermissionCallback; +import com.hjq.permissions.Permission; +import com.hjq.permissions.XXPermissions; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @Author:DuanKaiji + * @Date:2023-05-11/09:45 + * @Declaration: + */ +public class SplashActivityPresenter extends BasePresenter { + private MutableLiveData processLiveData = new MutableLiveData<>(); + private MutableLiveData needHideRegisterDialog = new MutableLiveData<>(); + private Activity currentActivity; + + public SplashActivityPresenter(@NonNull Application application) { + super(application); + } + + public void init() { + processLiveData.postValue(new ProcessInfo(1, "正在初始化Frp服务...")); + RxJavaUtils.doInIOThread(new RxIOTask(null) { + @Override + public Void doInIOThread(Object o) { + // 检查向后兼容性 + checkOldVersion(); + processLiveData.postValue(new ProcessInfo(5, "正在清除过期日志...")); + //清除1个月前的日志 + LogDBManager.clearLogsOlderThanOneMonthsSync(); + // 记录是否长时间未开机,即未在共育 + Date lastRecordDate = EnergyDBManager.queryLastRecordDate(); + if (lastRecordDate == null || + lastRecordDate.getTime() < System.currentTimeMillis() - Variable.LONG_TIME_NOT_START) { + MyLog.app("长时间未启动,设置启动时间:" + new Date()); + MMKVUtil.put(RxTag.IS_LONG_TIME_NOT_START, System.currentTimeMillis()); + } + + // 初始化MQTT网络 + processLiveData.postValue(new ProcessInfo(10, "正在连接服务器...")); + currentActivity.startService(new Intent(currentActivity, MQTTService.class)); + // 检查是否注册 + PollingTask.getInstance("checkRegister").startPollingTaskOnIOThread(RxTag.CHECK_REGISTER, 3, () -> { + if (HardwareSetting.isRegistered()) { + PollingTask.getInstance("checkRegister").stopAllPollingTasks(); + needHideRegisterDialog.postValue(true); + processLiveData.postValue(new ProcessInfo(20, "正在下载最新配置...")); + downloadLocalSet(); + } + }); + return null; + } + }); + } + + /** + * 检查过往版本向后兼容性 + */ + private void checkOldVersion() { + // 改变温控策略 + switch (AutoModelSet.getAutoTemperatureMode()) { + case "分挡位控制" -> { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, SpinnerList.TEMPERATURE_MODE[1]); + double bias = Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL; + double target = AutoModelSet.getTargetTemperature(); + double min = AutoModelSet.getMinTemperature(); + double max = AutoModelSet.getMaxTemperature(); + // 检查是否符合普通模式设定-最大最小值是否为目标温度上下bias度 + if (target - min < bias) { + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, target - bias); + } + if (max - target < bias) { + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, target + bias); + } + } + case "全功率控制" -> { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, SpinnerList.TEMPERATURE_MODE[0]); + } + } + // 转固定温湿度传感器为动态温湿度传感器 + THSensor indoorTHSensor1 = new THSensor(RoomSensor.SENSOR_NAME[7]); + if (!IndoorTHSensorUtil.hasSensor(indoorTHSensor1.getName())) { + IndoorTHSensorUtil.insert(indoorTHSensor1); + } + THSensor indoorTHSensor2 = new THSensor(RoomSensor.SENSOR_NAME[8]); + if (!IndoorTHSensorUtil.hasSensor(indoorTHSensor2.getName())) { + IndoorTHSensorUtil.insert(indoorTHSensor2); + } + GasSensor indoorCO2Sensor = new GasSensor(RoomSensor.SENSOR_NAME[3]); + if (!IndoorCO2SensorUtil.hasSensor(indoorCO2Sensor.getName())) { + IndoorCO2SensorUtil.insert(indoorCO2Sensor); + } + GasSensor ammoniaSensor = new GasSensor(RoomSensor.SENSOR_NAME[4]); + if (!IndoorCO2SensorUtil.hasSensor(ammoniaSensor.getName())) { + IndoorAmmoniaSensorUtil.insert(ammoniaSensor); + } + FloorTSensor floorTSensor = new FloorTSensor(RoomSensor.SENSOR_NAME[5]); + if (!IndoorFloorTSensorUtil.hasSensor(floorTSensor.getName())) { + IndoorFloorTSensorUtil.insert(floorTSensor); + } + // 120版本:高精度模式下,最大最小温度不可小于等于目标温度0.2度偏差 + if (AutoModelSet.getAutoTemperatureMode().equals(SpinnerList.TEMPERATURE_MODE[0])) { + double bias = Variable.AUTO_DO_NOTHING_TEMPERATURE_FULL; + double target = AutoModelSet.getTargetTemperature(); + double min = AutoModelSet.getMinTemperature(); + double max = AutoModelSet.getMaxTemperature(); + if (target - min <= bias) { + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, target - bias); + } + if (max - target <= bias) { + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, target + bias); + } + } + // 130版本:不在使用蚕季数据库,迁移旧数据到新数据库 + SilkwormDBManager.initSK(); + } + + /** + * 下载并更新配置 + */ + public void downloadLocalSet() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getConfig); + params.addParameter("hostCode", HardwareSetting.getHostId()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + String configStr = model.getData().toString(); + WebSet webSet = new Gson().fromJson(configStr, new TypeToken() { + }.getType()); + if (webSet != null) { + if (webSet.getSetUpdateTime() > RoomSetting.getSetTime()) { + //如果服务器配置更新时间大于本地配置更新时间,则更新本地配置 + RoomSetting.setSetTime(webSet.getSetUpdateTime()); + Map config = webSet.getSetJson(); + //将键值对存入MMKV + for (String key : config.keySet()) { + Object value = config.get(key); + //修复部分配置项是整型,但是解析出来是double型导致无法正确导入的bug + if (key.endsWith("PWM") //pwm值为整型 + || key.contains(Floor.MAX_GEAR) //最大档位为整型 + || key.contains(RoomSetting.MAIN_ACTIVITY_OPERATE_LOCK_TIMEOUT) //锁屏时间为整型 + ) { + MMKVUtil.put(key, ((Double) value).intValue()); + } else { + MMKVUtil.put(key, value); + } + } + successTips("配置已更新"); + MyLog.networkError("配置已更新"); + } + } else { + // 云端配置为空,将本地配置上传至云端 + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.uploadConfig); + params.addParameter("ConfigJson", SetActivityPresenter.getCurrentSetJsonString()); + params.addParameter("hostCode", HardwareSetting.getHostId()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.network("配置已上传"); + } + + @Override + public void error(Throwable e) { + } + }); + } + } + + @Override + public void error(Throwable e) { + errorTips("配置下载失败"); + MyLog.networkError("配置下载失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + // 初始化控制器 + processLiveData.postValue(new ProcessInfo(40, "正在初始化控制器...")); + RoomController.init(); + EnergyDBManager.clearLogsOlderThanThreeMonths(); + // 初始化传感器 + processLiveData.postValue(new ProcessInfo(60, "正在初始化传感器...")); + RoomSensor.init(); + // 初始化故障预警 + processLiveData.postValue(new ProcessInfo(80, "正在初始化故障预警...")); + DeviceAnomalyDetection.init(); + // 正在检查权限 + processLiveData.postValue(new ProcessInfo(90, "正在检查权限...")); + checkPermission(); + } + }); + } + + /** + * 检查与请求权限 + */ + void checkPermission() { + //申请读取存储的权限 + String permessions[]; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + permessions = new String[]{Permission.MANAGE_EXTERNAL_STORAGE}; + } else { + permessions = new String[]{Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE}; + } + XXPermissions.with(currentActivity) + .permission(permessions) + .permission(Permission.READ_PHONE_STATE) + .permission(Permission.REQUEST_INSTALL_PACKAGES) + .permission(Permission.CAMERA) + .request(new OnPermissionCallback() { + @Override + public void onGranted(@NonNull List permissions, boolean allGranted) { + if (allGranted) { + processLiveData.postValue(new ProcessInfo(100, "系统启动成功")); + UserLog.operate("系统启动成功"); + } else { + MyLog.appError("获取部分权限成功,但部分权限未正常授予"); + XXPermissions.startPermissionActivity(getApplication(), permissions); + } + } + + @Override + public void onDenied(@NonNull List permissions, boolean doNotAskAgain) { + if (doNotAskAgain) { + MyLog.app("被永久拒绝授权,需手动授予权限"); + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(getApplication(), permissions); + } else { + MyLog.appError("权限请求失败"); + } + } + }); + } + + public MutableLiveData getNeedHideRegisterDialog() { + return needHideRegisterDialog; + } + + public MutableLiveData getProcessLiveData() { + return processLiveData; + } + + public void setActivity(Activity activity) { + this.currentActivity = activity; + } + + /** + * 进度提示类 + */ + public class ProcessInfo { + private int process; + private String tips; + + public ProcessInfo(int process, String tips) { + this.process = process; + this.tips = tips; + } + + public int getProcess() { + return process; + } + + public void setProcess(int process) { + this.process = process; + } + + public String getTips() { + return tips; + } + + public void setTips(String tips) { + this.tips = tips; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/compose/PIDScreen.kt b/app/src/main/java/com/example/iot_controlhost/ui/compose/PIDScreen.kt new file mode 100644 index 0000000..26b19af --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/compose/PIDScreen.kt @@ -0,0 +1,463 @@ +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) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/compose/PIDViewModel.kt b/app/src/main/java/com/example/iot_controlhost/ui/compose/PIDViewModel.kt new file mode 100644 index 0000000..6406cc2 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/compose/PIDViewModel.kt @@ -0,0 +1,332 @@ +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>(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 = ArrayDeque() + val tempList = mutableStateListOf() + + val kpList = mutableStateListOf() + val kiList = mutableStateListOf() + val kdList = mutableStateListOf() + + // 振荡周期记录 + val periods: MutableList = 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// 自整定完成状态 + } + +} + diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirConditionAddDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirConditionAddDialog.java new file mode 100644 index 0000000..20784f9 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirConditionAddDialog.java @@ -0,0 +1,232 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogAirConditionAddBinding; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.ArrayList; +import java.util.List; + +import android_serialport_api.SerialPortUtil; +import es.dmoral.toasty.Toasty; + +/** + * @Description 增加新空调弹窗 + * @Author DuanKaiji + * @CreateTime 2024年04月10日 09:29:35 + */ +public class AirConditionAddDialog extends Dialog { + /** + * 当前设置的空调指令类型 + */ + private int type = 0; + DialogAirConditionAddBinding binding; + Context mContext; + + AirConditionInfrared mAirConditionInfrared; + + public AirConditionAddDialog(@NonNull Context mContext, AirConditionInfrared airConditionInfrared) { + super(mContext); + this.mContext = mContext; + this.mAirConditionInfrared = airConditionInfrared; + binding = DialogAirConditionAddBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + //初始空调指令设置类型为1:开机 + type = 1; + binding.tvTitle.setText("新空调指令学习测试与导出——" + airConditionInfrared.getName()); + //基础模式 + View.OnClickListener baseListener = v -> { + int id = v.getId(); + binding.toggleButton2.clearChecked(); + binding.toggleButton1.check(id); + if (id == R.id.btn_cancel_learn) { + type = 0; + } else if (id == R.id.btn_power_on) { + type = 1; + } else if (id == R.id.btn_power_off) { + type = 2; + } else if (id == R.id.btn_heat) { + type = 3; + } else if (id == R.id.btn_refrigeration) { + type = 4; + } + + }; + + getWindow().setGravity(Gravity.CENTER); + binding.ivDialogClose.setOnClickListener(view -> dismiss()); + binding.btnPowerOn.setOnClickListener(baseListener); + binding.btnPowerOff.setOnClickListener(baseListener); + binding.btnHeat.setOnClickListener(baseListener); + binding.btnRefrigeration.setOnClickListener(baseListener); + binding.btnDehumidify.setOnClickListener(baseListener); + //温度模式 + View.OnClickListener modeListener = v -> { + binding.toggleButton1.clearChecked(); + binding.toggleButton2.check(v.getId()); + int temperature = Integer.parseInt(binding.btnTemperature.getText().toString()); + int id = v.getId(); + if (id == R.id.btn_temperature_heat) { + type = temperature - 12; + } else if (id == R.id.btn_temperature_refrigeration) { + type = temperature + 2; + } else if (id == R.id.btn_temperature_auto) { + type = temperature + 16; + } else if (id == R.id.btn_dehumidify) { + type = temperature + 30; + } + }; + binding.btnTemperatureHeat.setOnClickListener(modeListener); + binding.btnTemperatureAuto.setOnClickListener(modeListener); + binding.btnTemperatureRefrigeration.setOnClickListener(modeListener); + binding.btnDehumidify.setOnClickListener(modeListener); + //加减温度 + View.OnClickListener temperatureListener = v -> { + //获得toggleButtonGroup2选中的项 + int checkedId = binding.toggleButton2.getCheckedButtonId(); + int temperature = Integer.parseInt(binding.btnTemperature.getText().toString()); + if (checkedId == -1) { + Toasty.info(mContext, "请先选择左侧温度模式").show(); + return; + } + int id = v.getId(); + if (id == R.id.btn_plus) { + temperature += 1; + } else if (id == R.id.btn_minus) { + temperature -= 1; + } + + if (temperature <= 30 && temperature >= 17) { + binding.btnTemperature.setText(String.valueOf(temperature)); + } else { + Toasty.info(mContext, mContext.getText(R.string.min_max_error_temperature)).show(); + return; + } + + if (checkedId == R.id.btn_temperature_heat) { + type = temperature - 12; + } else if (checkedId == R.id.btn_temperature_refrigeration) { + type = temperature + 2; + } else if (checkedId == R.id.btn_temperature_auto) { + type = temperature + 16; + } else if (checkedId == R.id.btn_dehumidify) { + type = temperature + 30; + } + + }; + binding.btnPlus.setOnClickListener(temperatureListener); + binding.btnMinus.setOnClickListener(temperatureListener); + //刷新/读取 + binding.btnRefresh.setOnClickListener(view -> binding.tvInstruction.setText(mAirConditionInfrared.readInstruction(type))); + //测试 + binding.btnTest.setOnClickListener(view -> { + if (mAirConditionInfrared.executeInstruction(type)) { + Toasty.success(mContext, "测试成功").show(); + } else { + Toasty.error(mContext, "测试失败").show(); + } + }); + //学习 + binding.btnLearn.setOnClickListener(view -> mAirConditionInfrared.learnInstruction(type)); + binding.btnCancelLearn.setOnClickListener(view -> mAirConditionInfrared.learnInstruction(0)); + //上传服务器 + binding.btnUpload.setOnClickListener(view -> { + if (StringUtils.isEmpty(binding.etAirConditionModel.getText().toString())) { + Toasty.info(mContext, "请输入空调型号").show(); + return; + } + if (StringUtils.isEmpty(binding.etAirConditionBrand.getText().toString())) { + Toasty.info(mContext, "请输入空调品牌").show(); + return; + } + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "确定要上传至服务器吗?", () -> + uploadAirConditionInstruction(binding.etAirConditionBrand.getText().toString(), binding.etAirConditionModel.getText().toString())).show(); + }); + binding.btnYes.setOnClickListener(view -> dismiss()); + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + Toasty.error(mContext, "串口打开失败,请检查设置->控制器配置->串口配置").show(); + } + } + + /** + * 上传空调指令 + */ + public void uploadAirConditionInstruction(String brand, String model) { + DialogUtil.showLoadingDialog(mContext,mContext.getString(R.string.in_read_air_condition_instruction)); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask>() { + @Override + public List doInQueueThread() { + List instructions = new ArrayList<>(); + for (int i = 1; i <= 60; i++) { + DialogUtil.showLoadingDialogInIO(mContext,"正在读取第" + i + "条指令"); + String instruction = mAirConditionInfrared.readInstruction(i); + if (MyUtil.checkInstruction(instruction)) { + instructions.add(instruction); + } else { + instructions.add("0000"); + } + } + return instructions; + } + + @Override + public void doInUIThread(List instructions) { + if (instructions == null) { + //存在指令错误,有全0全F出现 + errorTips("存在指令错误"); + return; + } + DialogUtil.showLoadingDialog(mContext,mContext.getString(R.string.in_upload_air_condition_instruction)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.uploadAirConditioner); + params.addParameter("Brand", brand); + params.addParameter("Model", model); + params.addParameter("Instruct", GsonUtils.toJson(instructions)); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + DialogUtil.hideLoadingDialog(); + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "成功上传空调指令",null).show(); + successTips(model.getMessage()); + } + + @Override + public void error(Throwable e) { + //上传空调指令失败 + errorTips("上传空调指令失败"); + DialogUtil.hideLoadingDialog(); + } + }); + } + }); + } + + protected void successTips(String msg) { + Toasty.success(mContext, msg).show(); + } + + protected void errorTips(String msg) { + Toasty.error(mContext, msg).show(); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java new file mode 100644 index 0000000..c8e7ef2 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/AirDialog.java @@ -0,0 +1,137 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.transition.Fade; +import android.transition.TransitionManager; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.appcompat.content.res.AppCompatResources; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogAirConditionBinding; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import android_serialport_api.SerialPortUtil; + +/** + * 空调遥控器 + */ +public class AirDialog extends BaseDialog { + AirConditionInfrared air; + + public AirDialog(@NonNull Context mContext, AirConditionInfrared air) { + super(mContext); + this.air = air; + refreshAir(); + //打开串口 + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + errorTips("串口打开失败"); + } + binding.tvTitle.setText("空调—" + air.getName()); + binding.modeHeat.setOnClickListener(v -> changeAirCondition(0)); + binding.modeAuto.setOnClickListener(v -> changeAirCondition(1)); + binding.modeCold.setOnClickListener(v -> changeAirCondition(2)); + binding.modeDehumidify.setOnClickListener(v -> changeAirCondition(-1)); + binding.btnAirPower.setOnClickListener(v -> changeAirCondition(3)); + binding.btnAirPlus.setOnClickListener(v -> changeAirCondition(4)); + binding.btnAirReduce.setOnClickListener(v -> changeAirCondition(5)); + } + + public void refreshAir() { + TransitionManager.beginDelayedTransition(binding.ll, new Fade()); + binding.ivSelHeat.setVisibility(air.getMode() == AirConditionInfrared.HEAT ? View.VISIBLE : View.INVISIBLE); + binding.ivSelAuto.setVisibility(air.getMode() == AirConditionInfrared.AUTO ? View.VISIBLE : View.INVISIBLE); + binding.ivSelCold.setVisibility(air.getMode() == AirConditionInfrared.COLD ? View.VISIBLE : View.INVISIBLE); + binding.ivSelDehumidify.setVisibility(air.getMode() == AirConditionInfrared.DEHUMIDIFY ? View.VISIBLE : View.INVISIBLE); + binding.airModeState.setText(air.getModeName()); + binding.ivModeState.setImageDrawable(air.getMode() == AirConditionInfrared.HEAT ? + mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.heat_dark : R.mipmap.heat) : air.getMode() == AirConditionInfrared.AUTO ? + mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.auto_dark : R.mipmap.auto) : + mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.cold_dark : R.mipmap.cold)); + binding.modeHeat.setImageDrawable(AppCompatResources.getDrawable(mContext, RoomSetting.isDarkTheme() ? R.mipmap.heat_dark : R.mipmap.heat)); + binding.modeAuto.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.auto_dark : R.mipmap.auto)); + binding.modeCold.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.cold_dark : R.mipmap.cold)); + binding.modeDehumidify.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.cold_dark : R.mipmap.cold)); + if (air.isPowerSupply()) { + binding.btnAirPower.setImageDrawable(mContext.getDrawable(R.mipmap.open)); + binding.tvAirTemperature.setText(String.valueOf(air.getTemperature())); + } else { + binding.btnAirPower.setImageDrawable(mContext.getDrawable(R.mipmap.close)); + binding.tvAirTemperature.setText("—"); + } + } + + /** + * @param type -1:变更模式-除湿 0:变更模式-制热 1:变更模式-自动 2:变更模式-制冷 3:反转开关 4:温度+ 5:温度- + */ + public void changeAirCondition(int type) { + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_operate_air_condition)); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + switch (type) { + case -1: + return air.setAirConditionMode(AirConditionInfrared.DEHUMIDIFY); + case 0: + case 1: + case 2: + return air.setAirConditionMode(type); + case 3: + boolean res; + if(air.isPowerSupply()){ + res = air.setPowerSupply(false); + }else{ + // 修复手动开电源时,没有设为目标温度和模式。 + res = air.setAirCondition(air.getMode(),air.getTemperature()); + } + return res; + case 4: + return air.setAirConditionTemperature(air.getTemperature() + 1); + case 5: + return air.setAirConditionTemperature(air.getTemperature() - 1); + } + return false; + } + + @Override + public void doInUIThread(Boolean result) { + if (result) { + successTips(air.getState()); + UserLog.operate("手动:操作空调" + air.getState()); + } else { + String errorTips = "手动:操作空调失败"; + if (RoomSetting.isAirConditionForceClose()) { + errorTips += ",在消毒期间无法开启空调"; + } + errorTips(errorTips); + UserLog.operateError(errorTips); + } + refreshAir(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + @Override + public void dismiss() { + super.dismiss(); + if (RoomSetting.getMode() == 0) { + //更新手动模式UI + RxBusUtils.get().post(RxTag.UPDATE_MANUAL, 0); + } else if (RoomSetting.getMode() == 1) { + //更新自动模式UI + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/ConfirmDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/ConfirmDialog.java new file mode 100644 index 0000000..5ca2cf9 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/ConfirmDialog.java @@ -0,0 +1,38 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogCustomBinding; + +/** + * @Description 获得确认通用弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月13日 13:57 + */ +public class ConfirmDialog extends BaseDialog { + Runnable runnable; + + public ConfirmDialog(@NonNull Context context, String title, String message, Runnable runnable) { + super(context); + this.runnable = runnable; + binding.tvTitle.setText(title); + binding.tvMessage.setText(message); + if (runnable != null) { + //询问确定执行的弹窗 + binding.btnYes.setOnClickListener(view -> { + runnable.run(); + dismiss(); + }); + binding.btnNo.setOnClickListener(v -> dismiss()); + } else { + //通知显示的弹窗 + binding.btnYes.setOnClickListener(view -> dismiss()); + binding.btnNo.setVisibility(View.GONE); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/DehumidityAddDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/DehumidityAddDialog.java new file mode 100644 index 0000000..7fa19d5 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/DehumidityAddDialog.java @@ -0,0 +1,167 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.databinding.DialogAirConditionAddBinding; +import com.example.iot_controlhost.databinding.DialogDehumidityAddBinding; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.controller.DehumidifierInfrared; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.ArrayList; +import java.util.List; + +import android_serialport_api.SerialPortUtil; +import es.dmoral.toasty.Toasty; + +/** + * @Description 增加新除湿机弹窗 + * @Author DuanKaiji + * @CreateTime 2024年04月10日 09:29:35 + */ +public class DehumidityAddDialog extends Dialog { + /** + * 当前设置的指令类型 + */ + private int type; + DialogDehumidityAddBinding binding; + Context mContext; + + DehumidifierInfrared mAirConditionInfrared; + + public DehumidityAddDialog(@NonNull Context mContext, DehumidifierInfrared dehumidityAddDialog) { + super(mContext); + this.mContext = mContext; + this.mAirConditionInfrared = dehumidityAddDialog; + binding = DialogDehumidityAddBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + //初始除湿机指令设置类型为200:开机 + type = 200; + binding.tvTitle.setText("除湿机指令学习与测试" + dehumidityAddDialog.getName()); + //基础模式 + View.OnClickListener baseListener = v -> { + int id = v.getId(); + binding.toggleButton1.check(id); + if (id == R.id.btn_cancel_learn) { + type = 0; + } else if (id == R.id.btn_power_on) { + type = 200; + } else if (id == R.id.btn_power_off) { + type = 201; + } + }; + + getWindow().setGravity(Gravity.CENTER); + binding.ivDialogClose.setOnClickListener(view -> dismiss()); + binding.btnPowerOn.setOnClickListener(baseListener); + binding.btnPowerOff.setOnClickListener(baseListener); + //刷新/读取 + binding.btnRefresh.setOnClickListener(view -> binding.tvInstruction.setText(mAirConditionInfrared.readInstruction(type))); + //测试 + binding.btnTest.setOnClickListener(view -> { + if (mAirConditionInfrared.executeInstruction(type)) { + Toasty.success(mContext, "测试成功").show(); + } else { + Toasty.error(mContext, "测试失败").show(); + } + }); + //学习 + binding.btnLearn.setOnClickListener(view -> mAirConditionInfrared.learnInstruction(type)); + binding.btnCancelLearn.setOnClickListener(view -> mAirConditionInfrared.learnInstruction(0)); + binding.btnYes.setOnClickListener(view -> dismiss()); + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + Toasty.error(mContext, "串口打开失败,请检查设置->控制器配置->串口配置").show(); + } + //上传服务器 + binding.btnUpload.setOnClickListener(view -> { + if (StringUtils.isEmpty(binding.etAirConditionModel.getText().toString())) { + Toasty.info(mContext, "请输入除湿机型号").show(); + return; + } + if (StringUtils.isEmpty(binding.etAirConditionBrand.getText().toString())) { + Toasty.info(mContext, "请输入除湿机品牌").show(); + return; + } + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "确定要上传至服务器吗?", () -> + uploadAirConditionInstruction(binding.etAirConditionBrand.getText().toString(), binding.etAirConditionModel.getText().toString())).show(); + }); + } + + /** + * 上传除湿机指令 + */ + public void uploadAirConditionInstruction(String brand, String model) { + DialogUtil.showLoadingDialog(mContext,mContext.getString(R.string.in_read_air_condition_instruction)); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask>() { + @Override + public List doInQueueThread() { + List instructions = new ArrayList<>(); + for (int i = 200; i <= 201; i++) { + DialogUtil.showLoadingDialogInIO(mContext,"正在读取第" + i + "条指令"); + String instruction = mAirConditionInfrared.readInstruction(i); + if (MyUtil.checkInstruction(instruction)) { + instructions.add(instruction); + } else { + instructions.add("0000"); + } + } + return instructions; + } + + @Override + public void doInUIThread(List instructions) { + if (instructions == null) { + //存在指令错误,有全0全F出现 + errorTips("存在指令错误"); + return; + } + DialogUtil.showLoadingDialog(mContext,mContext.getString(R.string.in_upload_air_condition_instruction)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.uploadAirConditioner); + params.addParameter("Brand", brand); + params.addParameter("Model", model); + params.addParameter("Instruct", GsonUtils.toJson(instructions)); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + DialogUtil.hideLoadingDialog(); + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "成功上传除湿机指令",null).show(); + successTips(model.getMessage()); + } + + @Override + public void error(Throwable e) { + //上传除湿机指令失败 + errorTips("上传除湿机指令失败"); + DialogUtil.hideLoadingDialog(); + } + }); + } + }); + } + + protected void successTips(String msg) { + Toasty.success(mContext, msg).show(); + } + + protected void errorTips(String msg) { + Toasty.error(mContext, msg).show(); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/EnergyDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/EnergyDialog.java new file mode 100644 index 0000000..19b4e13 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/EnergyDialog.java @@ -0,0 +1,51 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.adapter.SensorInfoAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogEnergyBinding; +import com.example.iot_controlhost.databinding.ItemSensorInfoBinding; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.sensor.SensorInfo; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RxTag; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * BBIT能耗详情弹窗 + */ +public class EnergyDialog extends BaseDialog { + SensorInfoAdapter adapter; + PollingTask pollingTask; + + public EnergyDialog(Context mContext) { + super(mContext); + pollingTask = PollingTask.getInstance("EnergyDialog"); + adapter = new SensorInfoAdapter(null, ItemSensorInfoBinding.class); + //初始化界面控件 + binding.rvEnergy.setLayoutManager(new LinearLayoutManager(mContext)); + binding.rvEnergy.setAdapter(adapter); + // 初始化温湿度数据 + pollingTask.startPollingTaskOnIOThread(RxTag.GET_RELAY_SENSOR_DATA,5,()->{ + List infoList = new ArrayList<>(); + for (Relay relay : RoomController.getAllRelayDevice()) { + infoList.add(new SensorInfo(relay.getName(), relay.getSensorData())); + } + adapter.setDataList(infoList); + }); + } + + @Override + public void dismiss() { + super.dismiss(); + pollingTask.stopAllPollingTasks(); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/FaceDetectionDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/FaceDetectionDialog.java new file mode 100644 index 0000000..9ec78b1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/FaceDetectionDialog.java @@ -0,0 +1,291 @@ +package com.example.iot_controlhost.ui.dialog; + +import static android.util.Base64.NO_WRAP; + +import android.app.Dialog; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.YuvImage; +import android.hardware.Camera; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraManager; +import android.media.FaceDetector; +import android.util.Base64; +import android.view.Gravity; +import android.view.SurfaceHolder; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.GsonUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.model.net.FaceCheck; +import com.example.iot_controlhost.ui.view.CircleOverlay; +import com.example.iot_controlhost.ui.view.OnPasswordEnteredListener; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.io.ByteArrayOutputStream; + +import es.dmoral.toasty.Toasty; + +/** + * @Author:DuanKaiji + * @Date:2024年1月9日 14:26:39 + * @Declaration:人脸识别弹窗 + */ +public class FaceDetectionDialog extends Dialog { + + /** + * 照相机是否在采集中 + */ + private boolean isCameraPaused = false; + /** + * 是否正在人脸识别中 + */ + private volatile boolean isChecking = false; + /** + * 识别次数(一次弹窗显示中) + */ + public static int times = 0; + /** + * 识别到的图片-Bitmap格式 + */ + Bitmap bitmap; + /** + * 识别到的图片-ByteArrayOutputStream格式 + */ + ByteArrayOutputStream stream; + private Camera camera; + private CircleOverlay surfaceView; + private SurfaceHolder surfaceHolder; + ImageView imageView; + Button button; + FaceDetector faceDetector; + FaceDetector.Face[] face; + YuvImage image; + Context mContext; + TextView tvTips; + OnPasswordEnteredListener onPasswordEnteredListener; + + public FaceDetectionDialog(@NonNull Context context, OnPasswordEnteredListener listener) { + super(context, R.style.MyDialogTheme); + this.mContext = context; + this.onPasswordEnteredListener = listener; + setContentView(R.layout.dialog_password_face); + surfaceView = findViewById(R.id.sv); + imageView = findViewById(R.id.iv_face); + button = findViewById(R.id.btn_face_check); + tvTips = findViewById(R.id.tv_tips); + surfaceHolder = surfaceView.getHolder(); + button.setOnClickListener(view -> { + if (stream != null) { + imageView.setImageBitmap(bitmap); + faceCheck(stream); + } else { + Toasty.error(mContext, "请正视摄像头").show(); + } + }); + findViewById(R.id.iv_dialog_close).setOnClickListener(view -> dismiss()); +// findViewById(R.id.iv_keyboard).setOnClickListener(view -> { +// //切换为键盘输入 +// +// }); + initFaceRecongize(); + getWindow().setAttributes(BaseDialog.getAttributes(this)); + getWindow().setGravity(Gravity.CENTER); + show(); + } + + /** + * 初始化人脸识别 + */ + private void initFaceRecongize() { + try { + times = 0; + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) { + tvTips.setText("设备不支持相机功能"); + return; + } else if (((CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE)).getCameraIdList().length == 0) { + tvTips.setText("未发现有效摄像头"); + tvTips.setTextColor(Color.RED); + return; + } + surfaceHolder.addCallback(new SurfaceHolder.Callback() { + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + // 打开相机 + try { + camera = Camera.open(); + camera.setPreviewDisplay(holder); + camera.setPreviewCallback((bytes, camera) -> myPreviewFrame(bytes, camera)); // 设置预览回调 + camera.startPreview(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { + try { + if (surfaceHolder.getSurface() == null) { + return; + } + camera.stopPreview(); + camera.setPreviewDisplay(surfaceHolder); + camera.setPreviewCallback((bytes, camera) -> myPreviewFrame(bytes, camera)); // 设置预览回调 + camera.startPreview(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + // 在 Surface 销毁时释放相机资源 + if (camera != null) { + camera.stopPreview(); + // 取消预览回调 + camera.setPreviewCallback(null); + camera.release(); + camera = null; + } + } + }); + } catch (CameraAccessException e) { + MyLog.face("摄像头初始化失败"); + } + } + + /** + * 预览帧处理 + */ + private void myPreviewFrame(byte[] data, Camera camera) { + Camera.Size size = camera.getParameters().getPreviewSize(); + // 计算裁剪区域 + int centerX = size.width / 2; + int centerY = size.height / 2; + int radius = Math.min(size.width, size.height) / CircleOverlay.FACE_RADIUS; + int left = Math.max(0, centerX - radius); + int top = Math.max(0, centerY - radius); + int right = Math.min(size.width, centerX + radius); + int bottom = Math.min(size.height, centerY + radius); + stream = new ByteArrayOutputStream(); + face = new FaceDetector.Face[1]; + image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null); + image.compressToJpeg(new Rect(left, top, right, bottom), 10, stream); + bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size()).copy(Bitmap.Config.RGB_565, true); + faceDetector = new FaceDetector(bitmap.getWidth(), bitmap.getHeight(), 1); + if (faceDetector.findFaces(bitmap, face) > 0) { + //需要自动识别,则启动该方法 + faceCheck(stream); + } else { + tvTips.setText("请正视摄像头"); + stream = null; + bitmap = null; + } + } + + /** + * 人脸识别(在线上传) + */ + private boolean faceCheck(ByteArrayOutputStream stream) { + if (isChecking) { + return false; + } + MyLog.face("第" + ++times + "次识别"); + tvTips.setText("正在识别中"); + setChecking(true); + long start = System.currentTimeMillis(); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.faceCheck, true); + String image = "data:image/jpeg;base64," + Base64.encodeToString(stream.toByteArray(), NO_WRAP); + params.addParameter("image", image); + params.addParameter("operation", 0); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(FaceCheck result) { + stopPreview(); + long end = System.currentTimeMillis(); + MyLog.face("耗时:" + ((end - start) / 1000) + "s\t" + result.getMessage()); + if (result.getData() == null) { + Toasty.info(mContext, result.getMessage()).show(); + MMKVUtil.put(RoomSetting.USER, GsonUtils.toJson(result.getData())); + setChecking(false); + startPreview(); + } else { + dismiss(); + Toasty.success(mContext, result.getData().getName() + ",欢迎使用").show(); + onPasswordEnteredListener.listener(null); + } + } + + @Override + public void error(Throwable e) { + e.printStackTrace(); + tvTips.setText("识别失败" + e.getMessage()); + MyLog.face("识别失败:" + e.getMessage()); + setChecking(false); + } + }); + return true; + } + + /** + * 设置是否在人脸识别中 + */ + private void setChecking(boolean isChecking) { + this.isChecking = isChecking; + button.setEnabled(!isChecking); + } + + /** + * 暂停预览 + */ + private void stopPreview() { + if (camera != null && !isCameraPaused) { + isCameraPaused = true; + camera.stopPreview(); + } + } + + /** + * 恢复预览 + */ + private void startPreview(){ + if (camera != null && isCameraPaused) { + isCameraPaused = false; + camera.startPreview(); + } + } + + @Override + public void dismiss() { + super.dismiss(); + //关闭摄像头 + MyLog.face("关闭摄像头,停止识别"); + if (camera != null) { + if (isCameraPaused) { + isCameraPaused = false; + } + camera.stopPreview(); + camera.setPreviewCallback(null); + camera.release(); + camera = null; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/FloorDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/FloorDialog.java new file mode 100644 index 0000000..029bbcf --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/FloorDialog.java @@ -0,0 +1,123 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogFloorBinding; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; +import com.google.android.material.slider.RangeSlider; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import android_serialport_api.SerialPortUtil; +import es.dmoral.toasty.Toasty; + +/** + * 地暖遥控器 + */ +public class FloorDialog extends BaseDialog { + Floor floor; + + public FloorDialog(Context mContext) { + super(mContext); + floor = RoomController.floor; + preGear = floor.getGear(); + binding.tvTitle.setText(RoomSetting.getFloorName()); + //打开串口 + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + Toasty.error(mContext, "串口打开失败").show(); + } + binding.rs.setCustomThumbDrawable(R.mipmap.sel_floor_1); + binding.rs.setThumbRadius(40); + binding.rs.setTickVisible(false); + binding.rs.addOnChangeListener((slider, value, fromUser) -> binding.rs.setCustomThumbDrawable(getThumbView((int) value))); + binding.rs.addOnSliderTouchListener(new RangeSlider.OnSliderTouchListener() { + @Override + public void onStartTrackingTouch(@NonNull RangeSlider slider) { + } + + @Override + public void onStopTrackingTouch(@NonNull RangeSlider slider) { + changeFloor(slider.getValues().get(0).intValue()); + } + }); + binding.rs.setValues(preGear); + binding.rs.setTrackHeight(50); + // 设置导轨为透明 + binding.rs.setTrackInactiveTintList(ColorStateList.valueOf(Color.parseColor("#00000000"))); + binding.rs.setTrackActiveTintList(ColorStateList.valueOf(Color.parseColor("#00000000"))); + } + + /** + * 调节前的档位,用于操作失败时回溯 + */ + private float preGear; + + private int getThumbView(int target) { + int result = R.mipmap.sel_floor_0; + switch (target) { + case 0 -> result = R.mipmap.sel_floor_0; + case 1 -> result = R.mipmap.sel_floor_1; + case 2 -> result = R.mipmap.sel_floor_2; + case 3 -> result = R.mipmap.sel_floor_3; + case 4 -> result = R.mipmap.sel_floor_4; + case 5 -> result = R.mipmap.sel_floor_5; + } + return result; + } + + private void changeFloor(int gear) { + DialogUtil.showLoadingDialog(mContext,"正在操作" + RoomSetting.getFloorName() + "中"); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Boolean doInQueueThread() throws Exception { + if (gear > floor.getMaxGear()) { + return false; + } else { + return floor.setGear(gear); + } + } + + @Override + public void doInUIThread(Boolean aBoolean) { + if (aBoolean) { + preGear = floor.getGear(); + successTips(floor.getState()); + UserLog.operate("手动:切换地暖档位:" + floor.getGear() + "档"); + } else { + binding.rs.setValues(preGear); + if (gear > floor.getMaxGear()) { + errorTips("目标档位大于设定最大档位,切换失败"); + } else { + UserLog.operateError("手动:切换地暖档位:" + gear + "档失败"); + errorTips("切换档位失败"); + } + } + DialogUtil.hideLoadingDialog(); + } + }); + } + + @Override + public void dismiss() { + super.dismiss(); + if (RoomSetting.getMode() == 0) { + //更新手动模式UI + RxBusUtils.get().post(RxTag.UPDATE_MANUAL, 0); + } else if (RoomSetting.getMode() == 1) { + //更新自动模式UI + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java new file mode 100644 index 0000000..0f3edda --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/LogDialog.java @@ -0,0 +1,95 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.LogAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogLogBinding; +import com.example.iot_controlhost.databinding.ItemLogBinding; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.example.iot_controlhost.utils.log.MyLog; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask; + +import java.util.List; + +/** + * 体质弹窗 + */ +public class LogDialog extends BaseDialog { + + /** + * 当前主界面日志所在页数 + */ + private int logPage = 1; + List logs; + LogAdapter adapter; + + public LogDialog(Context mContext, List logs) { + super(mContext); + this.logs = logs; + adapter = new LogAdapter(logs, ItemLogBinding.class); + //初始化界面控件 + binding.rvLog.setLayoutManager(new LinearLayoutManager(mContext)); + binding.rvLog.setAdapter(adapter); + binding.acInput.setHint(mContext.getString(R.string.select_log_tag)); + binding.acInput.setAdapter(new CustomSpinnerAdapter(mContext, MyLog.getUserQueryTag().toArray(new String[0]))); + binding.refreshLayout.setOnRefreshListener(refreshlayout -> RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { + @Override + public List doInIOThread(Object o) { + logPage = 1; + return LogDBManager.queryList(binding.acInput.getText().toString(), logPage); + } + + @Override + public void doInUIThread(List logs) { + adapter.setDataList(logs); + if (logs.size() == 0) { + refreshlayout.finishLoadMoreWithNoMoreData(); + } else { + refreshlayout.finishLoadMore(); + } + refreshlayout.finishRefresh(); + } + })); + binding.refreshLayout.setOnLoadMoreListener(refreshlayout -> RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { + @Override + public List doInIOThread(Object o) { + return LogDBManager.queryList(binding.acInput.getText().toString(), ++logPage); + } + + @Override + public void doInUIThread(List logs) { + adapter.addDataList(logs); + if (logs.size() == 0) { + refreshlayout.finishLoadMoreWithNoMoreData(); + } else { + refreshlayout.finishLoadMore(); + } + } + })); + binding.btnQuery.setOnClickListener(view -> { + logPage = 1; + DialogUtil.showLoadingDialog(mContext, "正在读取日志..."); + RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { + @Override + public List doInIOThread(Object o) { + return LogDBManager.queryList(binding.acInput.getText().toString(), logPage); + } + + @Override + public void doInUIThread(List logs) { + adapter.setDataList(logs); + DialogUtil.hideLoadingDialog(); + } + }); + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/MessageDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/MessageDialog.java new file mode 100644 index 0000000..d658ba1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/MessageDialog.java @@ -0,0 +1,70 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.util.Pair; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.LogAdapter; +import com.example.iot_controlhost.adapter.MessageAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogLogBinding; +import com.example.iot_controlhost.databinding.DialogMessageBinding; +import com.example.iot_controlhost.databinding.ItemLogBinding; +import com.example.iot_controlhost.databinding.ItemMsgTitleBinding; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.model.net.AirConditionList; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.List; + +/** + * 弹窗 + */ +public class MessageDialog extends BaseDialog { + + List logs; + MessageAdapter adapter; + + public MessageDialog(Context mContext) { + super(mContext); + adapter = new MessageAdapter(logs, ItemMsgTitleBinding.class,binding); + binding.rvMsg.setAdapter(adapter); + + refreshData(); + + binding.btnQuery.setOnClickListener(view -> { + refreshData(); + }); + + } + + private void refreshData() { +// DialogUtil.showLoadingDialog(mContext, "正在加载最新消息,请稍等..."); +// RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getAirConditionerModelList); +// x.http().get(params, new XHttpShorthand() { +// @Override +// public void success(AirConditionList model) { +// +// } +// +// @Override +// public void error(Throwable e) { +// errorTips("无法获取消息列表"); +// } +// }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/NumberInputDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/NumberInputDialog.java new file mode 100644 index 0000000..6f9f174 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/NumberInputDialog.java @@ -0,0 +1,120 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogNumberInputBinding; + +/** + * @Description 数字输入弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月12日 17:46 + */ +public class NumberInputDialog extends BaseDialog { + TextView tv; + private int lastAction; + private float startX, startY; + + + public NumberInputDialog(@NonNull Context context, TextView tv) { + super(context); + this.tv = tv; + // 初始化界面控件 tv文本末尾闪烁“|” + tv.setText(getAfterText(tv.getText().toString())); + binding.btnPoint.setOnClickListener(getNumberOnClickListener(tv, -1)); + binding.btn0.setOnClickListener(getNumberOnClickListener(tv, 0)); + binding.btn1.setOnClickListener(getNumberOnClickListener(tv, 1)); + binding.btn2.setOnClickListener(getNumberOnClickListener(tv, 2)); + binding.btn3.setOnClickListener(getNumberOnClickListener(tv, 3)); + binding.btn4.setOnClickListener(getNumberOnClickListener(tv, 4)); + binding.btn5.setOnClickListener(getNumberOnClickListener(tv, 5)); + binding.btn6.setOnClickListener(getNumberOnClickListener(tv, 6)); + binding.btn7.setOnClickListener(getNumberOnClickListener(tv, 7)); + binding.btn8.setOnClickListener(getNumberOnClickListener(tv, 8)); + binding.btn9.setOnClickListener(getNumberOnClickListener(tv, 9)); + binding.btnDelete.setOnClickListener(view -> { + String text = tv.getText().toString(); + if (text.length() > 1){ + tv.setText(getAfterText(text.substring(0, text.length() - 2))); + } + }); + binding.clClose.setOnClickListener(view -> dismiss()); + // Make the dialog draggable + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.x = 0; + params.y = 0; + getWindow().setAttributes(params); + binding.cardRoot.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + lastAction = MotionEvent.ACTION_DOWN; + startX = event.getRawX() - params.x; + startY = event.getRawY() - params.y; + break; + case MotionEvent.ACTION_MOVE: + lastAction = MotionEvent.ACTION_MOVE; + params.x = (int) (event.getRawX() - startX); + params.y = (int) (event.getRawY() - startY); + getWindow().setAttributes(params); + break; + case MotionEvent.ACTION_UP: + if (lastAction == MotionEvent.ACTION_DOWN) { + // Handle click event if necessary + } + break; + default: + return false; + } + return true; + } + }); + } + + /** + * 获取密码输入弹窗的 + * 数字事件 + * + * @param ti 输入框 + * @param number 目标数字 0-10 + * @return 点击事件 + */ + private View.OnClickListener getNumberOnClickListener(TextView ti, int number) { + if (number == -1) { + return view -> { + if (!ti.getText().toString().contains(".")) { + ti.setText(getAfterText(getTrulyText() + ".")); + } + }; + } else { + return view -> ti.setText(getAfterText(getTrulyText() + number)); + } + } + + /** + * 获取真实的文本 + */ + private String getTrulyText() { + return tv.getText().toString().substring(0, tv.getText().length() - 1); + } + + /** + * 获取处理后的文本 + */ + private String getAfterText(String text) { + return text + "|"; + } + + @Override + public void dismiss() { + super.dismiss(); + tv.setText(getTrulyText()); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/PasswordDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/PasswordDialog.java new file mode 100644 index 0000000..e2cfee1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/PasswordDialog.java @@ -0,0 +1,126 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogPasswordBinding; +import com.example.iot_controlhost.ui.view.OnPasswordEnteredListener; +import com.google.android.material.textfield.TextInputEditText; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 密码输入弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月12日 17:46 + */ +public class PasswordDialog extends BaseDialog { + + public PasswordDialog(@NonNull Context context, String title, OnPasswordEnteredListener listener) { + this(context, title, null, listener); + } + + public PasswordDialog(@NonNull Context context, String title, String target, OnPasswordEnteredListener listener) { + this(context, title, target, false, listener,false); + } + + /** + * @param context 上下文 + * @param title 题目 + * @param target 密码 + * @param showFace 是否可以通过人脸识别代替密码 + * @param listener 输入后的事件 + */ + public PasswordDialog(@NonNull Context context, String title, String target, boolean showFace, OnPasswordEnteredListener listener,boolean showPwd) { + super(context); + binding.tvTitle.setText(title); + TextInputEditText inputEditText = binding.tiInput; + if (showFace) { + binding.ivFaceRecognize.setVisibility(View.VISIBLE); + binding.ivFaceRecognize.setOnClickListener(view -> { + new FaceDetectionDialog(context, listener).show(); + dismiss(); + }); + } + if(showPwd){ + inputEditText.setInputType(InputType.TYPE_CLASS_TEXT); + }else{ + inputEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD); + } + inputEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + + @Override + public void afterTextChanged(Editable editable) { + //大于六位才开始验证 + if (editable.toString().length() < 6) { + return; + } + if (!StringUtils.isEmpty(target) && target.equals(editable.toString())) { + Toasty.success(context, "密码验证通过 ").show(); + listener.listener(inputEditText); + dismiss(); + } + } + }); + binding.btnYes.setOnClickListener(view -> { + if (target != null) { + if (StringUtils.isEmpty(inputEditText.getText().toString())) { + Toasty.info(context, "密码不可为空,请输入").show(); + return; + } else if (!inputEditText.getText().toString().equals(target)) { + Toasty.error(context, "密码错误").show(); + inputEditText.setText(""); + return; + } else if (inputEditText.getText().toString().equals(target)) { + Toasty.success(context, "密码正确").show(); + } + } + //密码正确 || 并非验证密码 || 有其他的验证方式(不用target) + listener.listener(inputEditText); + dismiss(); + }); + binding.btn0.setOnClickListener(getNumberOnClickListener(inputEditText, 0)); + binding.btn1.setOnClickListener(getNumberOnClickListener(inputEditText, 1)); + binding.btn2.setOnClickListener(getNumberOnClickListener(inputEditText, 2)); + binding.btn3.setOnClickListener(getNumberOnClickListener(inputEditText, 3)); + binding.btn4.setOnClickListener(getNumberOnClickListener(inputEditText, 4)); + binding.btn5.setOnClickListener(getNumberOnClickListener(inputEditText, 5)); + binding.btn6.setOnClickListener(getNumberOnClickListener(inputEditText, 6)); + binding.btn7.setOnClickListener(getNumberOnClickListener(inputEditText, 7)); + binding.btn8.setOnClickListener(getNumberOnClickListener(inputEditText, 8)); + binding.btn9.setOnClickListener(getNumberOnClickListener(inputEditText, 9)); + binding.btnDelete.setOnClickListener(view -> { + String password = inputEditText.getText().toString(); + if (!password.isEmpty()) { + password = password.substring(0, password.length() - 1); + inputEditText.setText(password); + } + }); + } + + /** + * 获取密码输入弹窗的 + * 数字事件 + * + * @param ti 输入框 + * @param number 目标数字 0-10 + * @return 点击事件 + */ + private View.OnClickListener getNumberOnClickListener(TextInputEditText ti, int number) { + return view -> ti.setText(ti.getText().toString() + number); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/RegisterDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/RegisterDialog.java new file mode 100644 index 0000000..d970e06 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/RegisterDialog.java @@ -0,0 +1,30 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.graphics.BitmapFactory; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogRegisterBinding; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.QRCodeUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; + +/** + * @Description 第一次启动,注册弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月18日 09:17 + */ +public class RegisterDialog extends BaseDialog { + public RegisterDialog(@NonNull Context context) { + super(context); + binding.ivImei.setImageBitmap(QRCodeUtil.createQRCodeBitmap(HardwareSetting.getHostId(), 300, 300, + BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.logo_blue), 30)); + binding.tvDeviceId.setText("设备ID:" + MMKVUtil.get(HardwareSetting.HOST_ID)); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/RenewDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/RenewDialog.java new file mode 100644 index 0000000..a8d1c14 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/RenewDialog.java @@ -0,0 +1,133 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogRenewBinding; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.Random; + +/** + * 续费弹窗 + */ +public class RenewDialog extends BaseDialog { + + private final String[] packages = new String[]{"测试1", "测试2", "测试3", "测试4"}; + private final String[] times = new String[]{"测试1", "测试2", "测试3", "测试4"}; + + /** + * 选择的套餐类型 + */ + String selPackage = null; + /** + * 选择的续费时长 + */ + String selTime = null; + /** + * 选择的支付类型 + * 0微信 1支付宝 + */ + int selPayType = 0; + + public RenewDialog(Context context) { + super(context); + binding.acInputPackage.setHint("流量套餐"); + binding.acInputTime.setHint("续费时长"); + binding.acInputPackage.setAdapter(new CustomSpinnerAdapter(mContext, packages)); + binding.acInputTime.setAdapter(new CustomSpinnerAdapter(mContext, times)); + binding.acInputTime.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + selTime = s.toString(); + loadImage(); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + binding.acInputPackage.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + selPackage = s.toString(); + loadImage(); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + } + + /** + * 读取支付码-网络请求 + * 根据套餐类型、续费时长与支付类型获取支付码` + */ + private void loadImage() { + if (selPackage == null) { + infoTips("请选择套餐类型"); + } else if (selTime == null) { + infoTips("请选择续费时长"); + } else { + renew(); + } + binding.ivAli.setOnClickListener(v -> { + selPayType = 1; + binding.ivAli.setImageDrawable(mContext.getDrawable(R.mipmap.sel)); + binding.ivWe.setImageDrawable(mContext.getDrawable(R.mipmap.unsel)); + loadImage(); + }); + binding.ivWe.setOnClickListener(v -> { + selPayType = 2; + binding.ivWe.setImageDrawable(mContext.getDrawable(R.mipmap.sel)); + binding.ivAli.setImageDrawable(mContext.getDrawable(R.mipmap.unsel)); + loadImage(); + }); + } + + private void renew() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.renew); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.app("续费图像信息获取成功"); + binding.ivPay.setImageDrawable(mContext.getDrawable(R.mipmap.logo_gys)); + binding.tvPrice.setText(String.valueOf(new Random().nextInt(2000))); + binding.tvOriginPrice.setText(Html.fromHtml("" + (2000 + new Random().nextInt(50000)) + "")); + } + + @Override + public void error(Throwable e) { + MyLog.appError("续费图像信息获取失败"); + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java new file mode 100644 index 0000000..62d0f00 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetAutoParamDialog.java @@ -0,0 +1,382 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogSetAutoBinding; +import com.example.iot_controlhost.model.AITHMode; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.database.MyAIModeUtil; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import java.util.List; + +/** + * @Description 自动模式设置弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月06日 14:05 + */ +public class SetAutoParamDialog extends BaseDialog { + + /** + * 构造函数 + * + * @param context 上下文 + * @param setType 设置类型 0温度 1湿度 2换气 + * 3智能模式-温湿度 4智能模式-换气扇 + * @param defaultValue 默认值 + */ + public SetAutoParamDialog(@NonNull Context context, int setType, String[] defaultValue) { + super(context); + binding.tiSet1.setText(defaultValue[0]); + binding.tiSet2.setText(defaultValue[1]); + if (setType == 0 || setType == 1 || setType == 2) { + binding.tiSet3.setText(defaultValue[2]); + binding.set0.setVisibility(View.VISIBLE); + } + String strSet1 = null; + String strSet2 = null; + String strSet3 = null; + switch (setType) { + case 0: + //设置温度 + binding.tvSetTitle.setText(R.string.auto_set_temperature); + strSet1 = context.getString(R.string.target_temperature); + strSet2 = context.getString(R.string.temperature_max); + strSet3 = context.getString(R.string.temperature_min); + binding.tvSet1Symbol.setText(R.string.T_symbol); + binding.tvSet2Symbol.setText(R.string.T_symbol); + binding.tvSet3Symbol.setText(R.string.T_symbol); + MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoTemperature(), binding.llAutoSetParam, binding.llAutoSetParamNone); + binding.set0.setOnClickListener(v -> MyUtil.changeIcon(context, binding.ivAuto, binding.llAutoSetParam, binding.llAutoSetParamNone)); + break; + case 1: + //设置湿度 + binding.tvSetTitle.setText(R.string.auto_set_humidity); + strSet1 = context.getString(R.string.target_humidity); + strSet2 = context.getString(R.string.humidity_max); + strSet3 = context.getString(R.string.humidity_min); + binding.tvSet1Symbol.setText(R.string.H_symbol); + binding.tvSet2Symbol.setText(R.string.H_symbol); + binding.set3.setVisibility(View.VISIBLE); + binding.tvSet3Symbol.setText(R.string.H_symbol); + MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoHumidity(), binding.llAutoSetParam, binding.llAutoSetParamNone); + binding.set0.setOnClickListener(v -> MyUtil.changeIcon(context, binding.ivAuto, binding.llAutoSetParam, binding.llAutoSetParamNone)); + break; + case 2: + default: + //设置换气 + binding.tvSetTitle.setText(R.string.auto_set_ventilation); + strSet1 = context.getString(R.string.target_start_time); + strSet2 = context.getString(R.string.target_stop_time); + strSet3 = context.getString(R.string.target_co2); + binding.tvSet1Symbol.setText(R.string.minute); + binding.tvSet2Symbol.setText(R.string.minute); + binding.set3.setVisibility(View.VISIBLE); + binding.tvSet3Symbol.setText(R.string.ppm); + MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoVentilator(), binding.llAutoSetParam, binding.llAutoSetParamNone); + binding.set0.setOnClickListener(v -> MyUtil.changeIcon(context, binding.ivAuto, binding.llAutoSetParam, binding.llAutoSetParamNone)); + break; + case 3: + //设置智能模式-温湿度 + binding.tvSetTitle.setText(R.string.ai_set_th); + binding.tvSet1.setText(R.string.target_temperature); + binding.tvSet1Symbol.setText(R.string.T_symbol); + binding.tvSet2.setText(R.string.target_humidity); + binding.tvSet2Symbol.setText(R.string.H_symbol); + binding.set3.setVisibility(View.GONE); + binding.llAutoSetParamNone.setVisibility(View.GONE); + break; + case 4: + //设置智能模式-换气扇 + binding.tvSetTitle.setText(R.string.ai_set_ventilation); + binding.tvSet1.setText(R.string.target_start_time); + binding.tvSet1Symbol.setText(R.string.minute); + binding.tvSet2.setText(R.string.target_stop_time); + binding.tvSet2Symbol.setText(R.string.minute); + binding.set3.setVisibility(View.GONE); + binding.llAutoSetParamNone.setVisibility(View.GONE); + break; + } + binding.tvSet1.setText(strSet1); + binding.tvSet2.setText(strSet2); + binding.tvSet3.setText(strSet3); + binding.tiSet1.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet1).show()); + binding.tvSet1Symbol.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet1).show()); + binding.tiSet2.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet2).show()); + binding.tvSet2Symbol.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet2).show()); + binding.tiSet3.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet3).show()); + binding.tvSet3Symbol.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet3).show()); + binding.btnAdd2.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet2, setType, true)); + binding.btnAdd3.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet3, setType, true)); + binding.btnMinus2.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet2, setType, false)); + binding.btnMinus3.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet3, setType, false)); + if (setType == 0 || setType == 1) { + // 新需求:在设置目标温湿度时,最大值和最小值的范围随着目标值的变化而变化 + binding.btnAdd1.setOnClickListener(getTargetListener(binding.tiSet1, binding.tiSet2, binding.tiSet3, setType, true)); + binding.btnMinus1.setOnClickListener(getTargetListener(binding.tiSet1, binding.tiSet2, binding.tiSet3, setType, false)); + } else { + binding.btnAdd1.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet1, setType, true)); + binding.btnMinus1.setOnClickListener(getAutoSetAddAndMinusOnClickListener(binding.tiSet1, setType, false)); + } + binding.btnYes.setOnClickListener(view -> { + double min = Double.parseDouble(binding.tiSet3.getText().toString()); + double max = Double.parseDouble(binding.tiSet2.getText().toString()); + double target = Double.parseDouble(binding.tiSet1.getText().toString()); + boolean needRefresh = false; + switch (setType) { + case 0: + //设置温度 + if (min > target || max < target || min > max) { + new ConfirmDialog(context, context.getString(R.string.warning), "预警区间设置不合理,请重新调整", null).show(); + return; + } + if (!checkInputInTemperatureMode(target, min, max)) { + return; + } + //保存自动模式-温度设置 + MMKVUtil.put(AutoModelSet.TARGET_TEMPERATURE, target); + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, max); + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, min); + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, binding.ivAuto.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState())); + dismiss(); + needRefresh = true; + break; + case 1: + //设置湿度 + if (checkInput(context, binding.tvSet1, binding.tiSet1, 1, 100) + && checkInput(context, binding.tvSet2, binding.tiSet2, 1, 100) + && checkInput(context, binding.tvSet3, binding.tiSet3, 1, 100)) { + if (min > target || max < target || min > max) { + new ConfirmDialog(context, context.getString(R.string.warning), "预警区间设置不合理,请重新调整", null).show(); + return; + } + //保存自动模式-湿度设置 + MMKVUtil.put(AutoModelSet.TARGET_HUMIDITY, target); + MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, min); + MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, max); + MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, binding.ivAuto.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState())); + dismiss(); + needRefresh = true; + } + break; + case 2: + //设置换气 + if (checkInput(context, binding.tvSet1, binding.tiSet1, 1, 1440) + && checkInput(context, binding.tvSet2, binding.tiSet2, 1, 1440) + && checkInput(context, binding.tvSet3, binding.tiSet3, 500, 3000)) { + //保存自动模式-换气设置 + try { + MMKVUtil.put(AutoModelSet.CYCLE_START, Integer.parseInt(binding.tiSet1.getText().toString())); + MMKVUtil.put(AutoModelSet.CYCLE_STOP, Integer.parseInt(binding.tiSet2.getText().toString())); + MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, Integer.parseInt(binding.tiSet3.getText().toString())); + MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, binding.ivAuto.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState())); + dismiss(); + needRefresh = true; + } catch (NumberFormatException e) { + infoTips("请输入整数"); + } + } + break; + case 3: + //设置智能模式-温湿度 + if (checkInput(context, binding.tvSet2, binding.tiSet2, 1, 100)) { + //保存智能模式-温湿度设置 + double temperature = Double.parseDouble(binding.tiSet1.getText().toString()); + double humidity = Double.parseDouble(binding.tiSet2.getText().toString()); + int hour = AIModelSet.getStartToNowHours(); + List modes = MyAIModeUtil.getMyAiMode(); + int age = 0; + for (int i = 0; i < modes.size(); i++) { + hour -= modes.get(i).getExpectedHours(); + if (hour < 0) { + age = i; + break; + } + } + AITHMode mode = MyAIModeUtil.getMyAiMode().get(age); + //修改当前龄期的湿度 + mode.setTargetTemp(temperature); + mode.setTargetHumi(humidity); + MyAIModeUtil.update(mode); + RxBusUtils.get().post(RxTag.AI_INFO, 0); + dismiss(); + needRefresh = true; + } + break; + case 4: + //设置智能模式-换气扇 + if (checkInput(context, binding.tvSet1, binding.tiSet1, 1, 1440) && checkInput(context, binding.tvSet2, binding.tiSet2, 1, 1440)) { + //保存智能模式-换气扇设置 + int start = Integer.parseInt(binding.tiSet1.getText().toString()); + int stop = Integer.parseInt(binding.tiSet2.getText().toString()); + int hour = AIModelSet.getStartToNowHours(); + List modes = MyAIModeUtil.getMyAiMode(); + int age = 0; + for (int i = 0; i < modes.size(); i++) { + hour -= modes.get(i).getExpectedHours(); + if (hour < 0) { + age = i; + break; + } + } + AITHMode mode = MyAIModeUtil.getMyAiMode().get(age); + mode.setTargetVentOpen(start); + mode.setTargetVentClose(stop); + MyAIModeUtil.update(mode); + RxBusUtils.get().post(RxTag.AI_INFO, 0); + dismiss(); + needRefresh = true; + } + break; + default: + } + if (needRefresh) { + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + } + }); + + } + + /** + * 输入框中数字增大减小事件 + * + * @param ti 输入框 + * @param type 设置类型 0温度 1湿度 2换气 3智能模式-温湿度 4智能模式-换气扇 + * @param isAdd true增加 false减少 + * @return 事件 + */ + private View.OnClickListener getAutoSetAddAndMinusOnClickListener(TextView ti, int type, boolean isAdd) { + double value; + if (type == 0 || type == 3) { + value = .1; + } else { + //(type == 1 || type == 2 || type == 4) + value = 1; + } + return view -> { + double d = Double.parseDouble(ti.getText().toString()); + if (isAdd) { + d += value; + } else { + d -= value; + } + if (type == 0 || type == 3) { + ti.setText(String.format("%.1f", d)); + } else { + ti.setText(String.format("%.0f", d)); + } + }; + } + + /** + * 输入框中数字增大减小事件 + * + * @param t1 输入框 + * @param t2 输入框 + * @param t3 输入框 + * @param type 设置类型 0温度 1湿度 + * @param isAdd true增加 false减少 + * @return 事件 + */ + private View.OnClickListener getTargetListener(TextView t1, TextView t2, TextView t3, int type, boolean isAdd) { + double value; + if (type == 0 || type == 3) { + value = .1; + } else { + //(type == 1 || type == 2 || type == 4) + value = 1; + } + return view -> { + double d1 = Double.parseDouble(format(t1.getText().toString())); + double d2 = Double.parseDouble(format(t2.getText().toString())); + double d3 = Double.parseDouble(format(t3.getText().toString())); + double[] minTargetMax = MyUtil.getTHMinMax(d1, isAdd ? value : -value, d2, d3, type); + t1.setText(String.format("%.1f", minTargetMax[1])); + t2.setText(String.format("%.1f", minTargetMax[2])); + t3.setText(String.format("%.1f", minTargetMax[0])); + }; + } + + private String format(String str) { + if (str == null) { + return "0.0"; + } + if (str.contains("|")) { + // 删除| + str = str.replace("|", ""); + } + return str; + } + + /** + * 检查输入 + * 是否超过最小值或最大值 + * + * @param tvSet 被设置的内容,如“湿度”、“换气值” + * @param inputEditText 用户输入的文本框 + * @param min 最小值 + * @param max 最大值 + * @return 是否符合规定要求 + */ + private boolean checkInput(Context context, TextView tvSet, TextView inputEditText, double min, double max) { + String input = inputEditText.getText().toString(); + try { + double value = Double.parseDouble(input); + if (value > max) { + // 检查是否超过最大值 + new ConfirmDialog(context, context.getString(R.string.warning), tvSet.getText().toString() + "最大值为" + max + ",请重新调整", null).show(); + return false; + } else if (value < min) { + // 检查是否低于最小值 + new ConfirmDialog(context, context.getString(R.string.warning), tvSet.getText().toString() + "最小值为" + min + ",请重新调整", null).show(); + return false; + } else { + return true; + } + } catch (Exception e) { + new ConfirmDialog(context, context.getString(R.string.error), context.getString(R.string.input_error), null).show(); + return false; + } + } + private boolean checkInputInTemperatureMode(double target, double min, double max) { + String mode = AutoModelSet.getAutoTemperatureMode(); + double minBias; + String warningMessage; + // 根据不同模式设置相应的最小偏差和提示信息 + if (SpinnerList.TEMPERATURE_MODE[0].equals(mode)) { + minBias = Variable.AUTO_DO_NOTHING_TEMPERATURE_FULL; + warningMessage = "高精度模式下"; + } else if (SpinnerList.TEMPERATURE_MODE[1].equals(mode)) { + minBias = Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL; + warningMessage = "普通模式下"; + } else if (SpinnerList.TEMPERATURE_MODE[2].equals(mode)) { + minBias = Variable.AUTO_DO_NOTHING_TEMPERATURE_SAVING; + warningMessage = "节能模式下"; + } else { + return true; // 未知模式,直接返回 true + } + // 检查最低温度和最高温度是否符合条件 + if (target - min < minBias) { + new ConfirmDialog(mContext, mContext.getString(R.string.warning), warningMessage + ",最低温度需小于目标温度至少" + + minBias + "℃,请重新设置最小值", null).show(); + return false; + } else if (max - target < minBias) { + new ConfirmDialog(mContext, mContext.getString(R.string.warning), warningMessage + ",最高温度需大于目标温度至少" + + minBias + "℃,请重新设置最大值", null).show(); + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java new file mode 100644 index 0000000..04f9f21 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetBaseDialog.java @@ -0,0 +1,263 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.SetAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogSetBinding; +import com.example.iot_controlhost.databinding.ItemSetBinding; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.model.Set; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.temperature.TemperatureController; +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.timer.ThemeTaskUtil; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxAsyncTask; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import es.dmoral.toasty.Toasty; + +/** + * + */ +public class SetBaseDialog extends BaseDialog { + List setList; + SetAdapter setAdapter; + + public SetBaseDialog(@NonNull Context context) { + super(context); + setList = new ArrayList<>(); + setAdapter = new SetAdapter(setList, ItemSetBinding.class); + binding.btnAppClose.setOnClickListener(v1 -> new ConfirmDialog(mContext, mContext.getString(R.string.warning), "确定要关闭程序吗?", + () -> MyUtil.exitApp()).show()); + binding.btnAppRestart.setOnClickListener(v1 -> new ConfirmDialog(mContext, mContext.getString(R.string.warning), "确定要重启程序吗?", + () -> MyUtil.relaunchApp()).show()); + binding.btnDeviceShutdown.setOnClickListener(v1 -> new ConfirmDialog(mContext, mContext.getString(R.string.warning), "确定要关闭主机吗?", + () -> MyUtil.shutDown()).show()); + binding.btnDeviceRestart.setOnClickListener(v1 -> new ConfirmDialog(mContext, mContext.getString(R.string.warning), "确定要重启主机吗?", + () -> MyUtil.reboot()).show()); + binding.rvSet.setLayoutManager(new LinearLayoutManager(mContext)); + binding.rvSet.setAdapter(setAdapter); + binding.ivReboot1.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.refresh : R.mipmap.reboot)); + binding.ivReboot2.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.refresh : R.mipmap.reboot)); + binding.ivClose1.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.power : R.mipmap.shutdown)); + binding.ivClose2.setImageDrawable(mContext.getDrawable(RoomSetting.isDarkTheme() ? R.mipmap.power : R.mipmap.shutdown)); + setMain(); + } + + private void setMain() { + setList.clear(); + setList.add(new Set("自动模式设置", view -> setAutoParam())); + setList.add(new Set(mContext.getString(R.string.set_password_main_lock_time), RoomSetting.getMainActivityOperateLockTimeOut() + "秒", view -> + new TextViewDialog(mContext, mContext.getString(R.string.set_password_main_lock_time), null, str -> { + try { + long time = Long.parseLong(str); + if (time < 5) { + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "时间不可小于5s,请重新输入", null).show(); + return; + } + MMKVUtil.put(RoomSetting.MAIN_ACTIVITY_OPERATE_LOCK_TIMEOUT, time); + setMain(); + } catch (Exception e) { + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "输入格式有误,设置失败", null).show(); + } + }, true).show())); + setList.add(new Set(mContext.getString(R.string.notice_type), RoomSetting.getNoticeType(), view -> + new SpinnerDialog(mContext, mContext.getString(R.string.notice_type), "请选择通知类型", RoomSetting.NOTICE_TYPE_SPINNER, sel -> { + MMKVUtil.put(RoomSetting.NOTICE_TYPE, sel); + setMain(); + }).show())); + setList.add(new Set(mContext.getString(R.string.light_night), + RoomSetting.getAutoLightNight() ? "根据日出日落时间自动设置" : RoomSetting.isDarkTheme() ? "总是暗色调" : "总是亮色调", + v2 -> new SpinnerDialog(mContext, mContext.getString(R.string.light_night), "请选择色调调节方式", new String[]{"总是亮色调", "总是暗色调", "根据日出日落时间自动设置"}, sel -> { + boolean isDark; + if ("根据日出日落时间自动设置".equals(sel)) { + MMKVUtil.put(RoomSetting.AUTO_LIGHT_NIGHT, true); + isDark = ThemeTaskUtil.resetTask(); + } else { + MMKVUtil.put(RoomSetting.AUTO_LIGHT_NIGHT, false); + isDark = "总是暗色调".equals(sel); + } + dismiss(); + this.dismiss(); + MyUtil.switchDarkMode(isDark); + }).show())); + setList.add(new Set(getString(R.string.set_password_main_lock), "******", view -> new PasswordDialog(mContext, "请先输入原锁屏密码", + null, ti -> { + if (!ti.getText().toString().equals(RoomSetting.getMainActivityLock())) { + new ConfirmDialog(mContext, "提示", "设置失败,原锁屏密码错误", null).show(); + return; + } + new PasswordDialog(mContext, "请输入六位新锁屏密码", + null, ti2 -> { + if (ti2.getText().toString().length() != 6) { + new ConfirmDialog(mContext, "提示", "设置失败,锁屏密码长度为6位,请重新设置", null).show(); + return; + } + MMKVUtil.put(RoomSetting.MAIN_ACTIVITY_LOCK, ti2.getText().toString()); + Toasty.success(mContext, "锁屏新密码设置成功").show(); + }).show(); + }).show())); + setList.add(new Set("消毒作业期间自动关闭空调", RoomSetting.isAirConditionForceCloseEnable(), (compoundButton, b) -> { + MMKVUtil.put(RoomSetting.IS_AIR_CONDITION_FORCE_CLOSE_ENABLE, b); + setMain(); + })); + setList.add(new Set(mContext.getString(R.string.log), view -> showLogDialog())); + setAdapter.setDataList(setList); + } + + /** + * 设置自动模式参数 + */ + private void setAutoParam() { + setList.clear(); + setList.add(new Set("返回上一级", view -> setMain())); + setList.add(new Set("升温设备", AutoModelSet.getAutoTemperatureHeat(), view -> + new SpinnerDialog(mContext, mContext.getString(R.string.temperature_control_heat), "请选择升温设备", SpinnerList.TEMPERATURE_HEAT, selection -> { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, selection); + updateTemperatureControl(); + }).show())); + setList.add(new Set("降温设备", AutoModelSet.getAutoTemperatureCool(), view -> + new SpinnerDialog(mContext, mContext.getString(R.string.temperature_control_heat), "请选择降温设备", SpinnerList.TEMPERATURE_COOL, selection -> { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_COOL, selection); + updateTemperatureControl(); + }).show())); + setList.add(new Set("温度控制模式", AutoModelSet.getAutoTemperatureMode(), view -> + new SpinnerDialog(mContext, mContext.getString(R.string.temperature_control_mode), "请选择温控模式", SpinnerList.TEMPERATURE_MODE, selection -> { + double target = AutoModelSet.getTargetTemperature(); + double min = AutoModelSet.getMinTemperature(); + double max = AutoModelSet.getMaxTemperature(); + // 获取当前选择模式的偏差 + double bias ; + String heatMode; + if (selection.equals(SpinnerList.TEMPERATURE_MODE[1])) { + bias = Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL; + heatMode = SpinnerList.TEMPERATURE_HEAT[1]; + } else if (selection.equals(SpinnerList.TEMPERATURE_MODE[2])) { + bias = Variable.AUTO_DO_NOTHING_TEMPERATURE_SAVING; + heatMode = SpinnerList.TEMPERATURE_HEAT[2]; + } else { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, selection); + updateTemperatureControl(); + return; + } + if (target - min < bias || max - target < bias) { + new ConfirmDialog(mContext, mContext.getString(R.string.warning), "切换模式后自动最高最低与目标值偏差为" + bias + "℃,是否继续", () -> { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, selection); + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, heatMode); + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, target - bias); + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, target + bias); + updateTemperatureControl(); + }).show(); + } else { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, selection); + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, heatMode); + updateTemperatureControl(); + } + }).show() + )); + setList.add(new Set("消毒灯计划", AutoModelSet.isAutoDisinfect() ? UVTaskUtil.getUvTask().size() + mContext.getString(R.string.plans) : "手动控制", view -> { + //设置自动模式消毒 + List myTaskList = UVTaskUtil.getUvTask(); + Collections.sort(myTaskList); + new UvTaskDialog(mContext, myTaskList).show(); + })); + setList.add(new Set("匀风扇控制-启动时机", AutoModelSet.getAutoEvenMode(), view -> + new SpinnerDialog(mContext, "匀风扇控制", "请选择匀风扇启动时机", SpinnerList.AUTO_EVEN_MODES, selection -> { + MMKVUtil.put(AutoModelSet.AUTO_EVEN_MODE, selection); + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + setAutoParam(); + }).show())); + setList.add(new Set("匀风扇控制-在加湿期间不运行", AutoModelSet.isAutoEvenModeDuringHumidify()?"是" : "否", view -> + new SpinnerDialog(mContext, "匀风扇在加湿期间不运行", "请选择", SpinnerList.YES_OR_NO, selection -> { + MMKVUtil.put(AutoModelSet.AUTO_EVEN_MODE_DURING_HUMIDIFY, selection.equals("是")); + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + setAutoParam(); + }).show())); + setList.add(new Set("换气扇控制", AutoModelSet.getVentilatorMode(), view -> + new SpinnerDialog(mContext, "换气扇控制", "请选择换气扇控制方法", SpinnerList.VENTILATION_MODES, selection -> { + MMKVUtil.put(AutoModelSet.VENTILATOR_MODE, selection); + setAutoParam(); + }).show())); + setList.add(new Set("新风控制", AutoModelSet.getAirExchangeMode(), view -> + new SpinnerDialog(mContext, "新风控制", "请选择新风控制方法", SpinnerList.AIR_EXCHANGE_MODES, selection -> { + MMKVUtil.put(AutoModelSet.AIR_EXCHANGE_MODE, selection); + setAutoParam(); + }).show())); + setList.add(new Set("地暖最大温度限制", AutoModelSet.getAutoFlorMaxTemp() + "℃", view -> + new TextViewDialog(mContext, "自动模式地暖最大温度限制(35~45℃)", String.valueOf(AutoModelSet.getAutoFlorMaxTemp()), str -> { + try { + double temp = Double.parseDouble(str); + if (temp > 45 || temp < 35) { + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "地暖温度区间需为35~45℃,请重新设置", null).show(); + return; + } + MMKVUtil.put(AutoModelSet.AUTO_FLOOR_MAX_TEMP, temp); + setAutoParam(); + } catch (Exception e) { + new ConfirmDialog(mContext, mContext.getString(R.string.tip), "输入格式有误,设置失败", null).show(); + } + }, true).show())); + setAdapter.setDataList(setList); + } + + /** + * 更新温度控制算法及加热降温方式 + */ + public void updateTemperatureControl() { + DialogUtil.showLoadingDialog(mContext, "正在更新温度控制模式,请稍等..."); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Object doInQueueThread() throws Exception { + TemperatureController.getInstance().refreshStrategy(); + return null; + } + + @Override + public void doInUIThread(Object o) { + DialogUtil.hideLoadingDialog(); + setAutoParam(); + } + }); + } + + /** + * 日志显示弹窗 + */ + public void showLogDialog() { + DialogUtil.showLoadingDialog(mContext, "正在读取日志中..."); + RxJavaUtils.executeAsyncTask(new RxAsyncTask>(null) { + @Override + public List doInIOThread(Object o) { + return LogDBManager.queryList(null, 1); + } + + @Override + public void doInUIThread(List logs) { + DialogUtil.hideLoadingDialog(); + new LogDialog(mContext, logs).show(); + } + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java new file mode 100644 index 0000000..a87c0bf --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetDeviceBaseDialog.java @@ -0,0 +1,65 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.view.View; +import androidx.annotation.NonNull; +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogDeviceCustomSetBinding; +import com.example.iot_controlhost.ui.activity.SetActivity; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 设备基础设置弹窗 + * @Author DuanKaiji + * @CreateTime 2024年04月10日 11:34:10 + */ +public class SetDeviceBaseDialog extends BaseDialog { + + /** + * 显示基础配置弹窗 + * + * @param context 上下文 + * @param title 标题 + * @param ac1List 下拉框1的内容 + * @param ac1Hint 下拉框1的提示 + * @param ac2List 下拉框2的内容 + * @param ac2Hint 下拉框2的提示 + * @param listener 点击确定事件 + */ + public SetDeviceBaseDialog(@NonNull Context context, String title, String[] ac1List, String ac1Hint, String[] ac2List, String ac2Hint, OnCommonSetListener listener) { + super(context,true); + binding.tilSpinner1.setVisibility(View.VISIBLE); + binding.tilSpinner2.setVisibility(View.VISIBLE); + binding.tilSpinner1.setHint(ac1Hint); + binding.tilSpinner2.setHint(ac2Hint); + binding.acInput1.setAdapter(new CustomSpinnerAdapter(context, ac1List)); + binding.acInput2.setAdapter(new CustomSpinnerAdapter(context, ac2List)); + binding.tvTitle.setText(title); + binding.btnYes.setText(context.getText(R.string.yes)); + binding.btnYes.setOnClickListener(view -> { + if (StringUtils.isEmpty(binding.acInput1.getText().toString())) { + Toasty.info(context, ac1Hint).show(); + return; + } + if (StringUtils.isEmpty(binding.acInput2.getText().toString())) { + Toasty.info(context, ac2Hint).show(); + return; + } + listener.btnSet(binding.acInput1.getText().toString(), binding.acInput2.getText().toString()); + dismiss(); + }); + } + + public interface OnCommonSetListener { + /** + * 设置按钮事件 + */ + void btnSet(String ac1, String ac2); + } + + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPWMDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPWMDialog.java new file mode 100644 index 0000000..a4a7c9e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPWMDialog.java @@ -0,0 +1,56 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.text.InputType; +import android.view.View; +import android.widget.Button; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogDeviceCustomSetBinding; +import com.example.iot_controlhost.utils.global.RoomController; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 获得手动配置地暖PWM弹窗 + * @Author DuanKaiji + * @CreateTime 2024年04月11日 10:50:40 + */ +public class SetPWMDialog extends BaseDialog { + public SetPWMDialog(@NonNull Context context,int gear,Runnable runnable) { + super(context, true); + binding.tvTitle.setText(gear + "档" + getString(R.string.set_pwm)); + binding.tilInput.setVisibility(View.VISIBLE); + binding.tilInput.setHint(getString(R.string.pwm_param)); + binding.tiInput.setInputType(InputType.TYPE_CLASS_NUMBER); + binding.btnYes.setText(getString(R.string.save)); + binding.btnYes.setOnClickListener(view -> { + try { + if (binding.tiInput.getText().toString().isEmpty()) { + Toasty.info(mContext, "请先输入PWM值").show(); + return; + } + int pwm = Integer.parseInt(binding.tiInput.getText().toString()); + if (pwm <= 0) { + Toasty.error(mContext, "PWM不能小于等于0").show(); + return; + } + if (pwm > RoomController.floor.getMaxPower()) { + Toasty.error(mContext, "PWM大于当前地暖型号支持的最大PWM").show(); + return; + } + RoomController.floor.getPwm(gear).setPwm(pwm); + } catch (Exception e) { + Toasty.error(mContext, "输入格式有误,请检查").show(); + return; + } + dismiss(); + runnable.run(); + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java new file mode 100644 index 0000000..3fb175c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetPortDialog.java @@ -0,0 +1,90 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.view.View; +import android.widget.Button; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogDeviceCustomSetBinding; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.RoomController; + +import java.util.Arrays; +import java.util.HashSet; + +import android_serialport_api.SerialPortUtil; +import es.dmoral.toasty.Toasty; + +/** + * @Description 设置端口设备 + * @Author DuanKaiji + * @CreateTime 2024年04月10日 11:45:19 + */ +public class SetPortDialog extends BaseDialog { + public SetPortDialog(@NonNull Context context, int port,Runnable afterSetRelay) { + super(context,true); + binding.ivDialogClose.setOnClickListener(view -> { + RoomController.relay.setPowerSupply(false, port); + dismiss(); + }); + binding.tvTitle.setText(port + mContext.getString(R.string.set)); + binding.tilSpinner1.setVisibility(View.VISIBLE); + binding.acInput1.setHint(mContext.getString(R.string.no_select)); + // 删除部分设备 + java.util.Set set = new HashSet<>(Arrays.asList(RoomController.DEVICE_NAME)); + set.remove("空调红外控制器"); + set.remove("继电器"); + set.remove("换气扇"); + binding.acInput1.setAdapter(new CustomSpinnerAdapter(mContext, set.toArray(new String[0]))); + binding.btn1.setVisibility(View.VISIBLE); + binding.btn1.setText(mContext.getText(R.string.test_power_on)); + binding.btn1.setOnClickListener(view -> { + Button button = (Button) view; + if (SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + boolean powerSupply; + if (button.getText().toString().equals(getString(R.string.test_power_on))) { + //测试开 + powerSupply = true; + button.setText(R.string.test_power_off); + } else { + //测试关 + powerSupply = false; + button.setText(R.string.test_power_on); + } + RoomController.relay.setPowerSupply(powerSupply, port); + } else { + Toasty.error(mContext, "串口未开启").show(); + } + }); + binding.btnYes.setText(mContext.getText(R.string.set)); + binding.btnYes.setOnClickListener(view -> { + //移除之前设备的端口设置 + Relay relay = RoomController.getRelay(MyUtil.getPortToDeviceName(port)); + if (relay != null) { + relay.removePort(port); + } else { + //当前端口之前未配置 + } + String deviceName = binding.acInput1.getText().toString(); + if (!deviceName.isEmpty()) { + //设置新设备的端口设置 + Relay r = RoomController.getRelay(deviceName); + if (r != null) { + r.addPort(port); + } else { + Toasty.error(mContext, "当前设备不支持").show(); + } + } + //确定后关闭弹窗 + RoomController.relay.setPowerSupply(false, port); + dismiss(); + afterSetRelay.run(); + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java new file mode 100644 index 0000000..8008b83 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SetSensorDialog.java @@ -0,0 +1,98 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogSetSensorBinding; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.global.SpinnerList; + +import android_serialport_api.SerialPortUtil; + +/** + * @Description 配置传感器弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月13日 13:46 + */ +public class SetSensorDialog extends BaseDialog { + Sensor sensor; + + boolean isEnable; + + public SetSensorDialog(@NonNull Context context, Sensor sensor, setSensor listener) { + super(context, true); + this.sensor = sensor; + binding.tvTitle.setText(sensor.getName() + context.getString(R.string.set)); + binding.acSensorModel.setHint(context.getString(R.string.select_model)); + binding.acSensorModel.setAdapter(new CustomSpinnerAdapter(context, sensor.getModels())); + binding.acAddress.setHint(context.getString(R.string.select_address)); + binding.acAddress.setAdapter(new CustomSpinnerAdapter(context, SpinnerList.ADDRESS)); + isEnable = sensor.isEnable(); + binding.switcher.setChecked(isEnable); + binding.switcher.setOnCheckedChangeListener((compoundButton, b) -> isEnable = b); + binding.btnLoadData.setOnClickListener(view -> { + String address = binding.acAddress.getText().toString(); + if (StringUtils.isEmpty(address)) { + infoTips("请先选择地址"); + return; + } + //读数据事件 + MyQueue.start(MyQueue.TYPE_SENSOR, new QueueIOUITask() { + @Override + public String doInQueueThread() throws Exception { + //先通过指定波特率打开串口 + if (SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR).openSerialPort()) { + //尝试读取数据 + if (sensor.collectSensorData(binding.acSensorModel.getText().toString(), address)) { + return sensor.toString(); + } else { + return mContext.getString(R.string.operate_fail); + } + } else { + return "串口打开失败"; + } + } + + @Override + public void doInUIThread(String s) { + binding.tvSensorInfo.setText(s); + } + }); + }); + binding.btnYes.setOnClickListener(button -> { + //设置事件 + boolean enable = binding.switcher.isChecked(); + if (enable) { + String address = binding.acAddress.getText().toString(); + String model = binding.acSensorModel.getText().toString(); + if (StringUtils.isEmpty(address)) { + infoTips("请先选择型号"); + return; + } + if (StringUtils.isEmpty(model)) { + infoTips("请先选择地址"); + return; + } + sensor.setEnable(isEnable); + sensor.setModel(binding.acSensorModel.getText().toString()); + sensor.setAddress(binding.acAddress.getText().toString()); + } + sensor.setEnable(enable); + //刷新页面 + listener.run(sensor); + dismiss(); + }); + } + + public interface setSensor{ + void run(Sensor sensor); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/SpinnerDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SpinnerDialog.java new file mode 100644 index 0000000..3ea741c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/SpinnerDialog.java @@ -0,0 +1,59 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogCustomBinding; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 下拉框弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月13日 14:08 + */ +public class SpinnerDialog extends BaseDialog { + /** + * 获取下拉框弹窗 + * + * @param context 上下文 + * @param title 标题 + * @param hint 提示信息 会在默认时、未选择点击确定时显示 + * @param list 下拉框列表内容 + * @param listener 点击确定后的事件 + */ + public SpinnerDialog(@NonNull Context context, String title, String hint, String[] list, CustomString listener) { + super(context); + binding.tilSpinner.setVisibility(View.VISIBLE); + binding.tvMessage.setVisibility(View.GONE); + binding.btnNo.setVisibility(View.GONE); + binding.tvTitle.setText(title); + ArrayAdapter adapter = new CustomSpinnerAdapter(context, list); + binding.acInput.setAdapter(adapter); + binding.acInput.setHint(hint); + binding.acInput.setDropDownHeight( + (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 350, + context.getResources().getDisplayMetrics() + ) + ); + //需要默认显示已设置的项,还能同时显示所有项 + binding.btnYes.setOnClickListener(view -> { + if (binding.acInput.getText().toString().isEmpty()) { + Toasty.info(context, hint).show(); + return; + } + listener.listener(binding.acInput.getText().toString()); + dismiss(); + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java new file mode 100644 index 0000000..80310b6 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/StartCultivateDialog.java @@ -0,0 +1,170 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.text.Editable; +import android.text.TextWatcher; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogStartCoeducationBinding; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.model.net.Batch; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.ui.view.CustomSpinnerAdapter; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @Description 开始共育弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月06日 09:39 + */ +public class StartCultivateDialog extends BaseDialog { + + public StartCultivateDialog(@NonNull Context context, List batchs) { + super(context, true); + List seasons = new ArrayList<>(); + for (Batch.DataDTO season : batchs) { + seasons.add(season.getBatchName()); + } + //初始化界面控件 + binding.textView3.setText("开始" + (RoomSetting.getType() == 4 ? "催青" : "共育")); + binding.btnYes.setText("开始" + (RoomSetting.getType() == 4 ? "催青" : "共育")); + binding.tvSeason.setAdapter(new CustomSpinnerAdapter(mContext, seasons.toArray(new String[0]))); + binding.tiDays.setText("1"); + binding.tvSeason.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // 文本改变时执行的操作 + binding.tvVariety.setText(null); + for (Batch.DataDTO dto : batchs) { + if (s.toString().equals(dto.getBatchName())) { + List variety = new ArrayList<>(); + for (Batch.DataDTO.ItemsDTO v : dto.getItems()) { + variety.add(v.getSilkwormEggName()); + } + binding.tvVariety.setAdapter(new CustomSpinnerAdapter(mContext, variety.toArray(new String[0]))); + return; + } + } + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + binding.btnYes.setOnClickListener(view -> { + //记录开始养蚕的所有信息 + if (binding.tvSeason.getText().toString().isEmpty()) { + infoTips("请选择蚕季"); + return; + } + if (binding.tvVariety.getText().toString().isEmpty()) { + infoTips("请选择品种"); + return; + } + int age; + double number; + try { + age = Integer.parseInt(binding.tiDays.getText().toString()); + } catch (Exception e) { + infoTips("请正确填写蚕龄"); + return; + } + try { + number = Double.parseDouble(binding.tiNumber.getText().toString()); + } catch (Exception e) { + infoTips("请正确填写数量"); + return; + } + for (Batch.DataDTO dto : batchs) { + if (binding.tvSeason.getText().toString().equals(dto.getBatchName())) { + for (Batch.DataDTO.ItemsDTO v : dto.getItems()) { + if (binding.tvVariety.getText().toString().equals(v.getSilkwormEggName())) { + startCultivate(v.getSilkwormEggId(), dto.getBatchName(), binding.tvVariety.getText().toString(), age, number); + MMKVUtil.put(AIModelSet.AI_CONTROL_PROGRAM_ID, v.getIntelligentControlProgramID()); +// MMKVUtil.put(AIModelSet.AI_CONTROL_PROGRAM_TOTAL_DAYS, days);//todo 需要共育总天数 + dismiss(); + return; + } + } + } + } + }); + binding.tiNumber.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiNumber).show()); + binding.tiDays.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiDays).show()); + } + + /** + * 开始共育-网络请求 + * + * @param season 蚕季/批次 + * @param variety 蚕种 + * @param age 蚕龄 + * @param number 数量 + */ + private void startCultivate(String SilkwormEggId, String season, String variety, int age, double number) { + //此次蚕期 + Date startTime = TimeUtils.getDateByNow(-(age - 1), TimeConstants.DAY); + //网络请求 + DialogUtil.showLoadingDialog(mContext, "正在记录中,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.startCultivate); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + params.addParameter("SilkwormEggId", SilkwormEggId); + params.addParameter("FeedingNum", String.valueOf(number)); + params.addParameter("StartTime", MyUtil.getFormatDateTime(startTime)); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + //记录此次蚕期 + SilkwormDBManager.INSTANCE.init(new SilkwormNew(model.getData().toString(), season, startTime, null, number, variety)); + //开始共育后切换为自动模式 + dismiss(); + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 7); +// MyUtil.relaunchApp(); + } + + @Override + public void onSuccessError(CommonResponse data) { + errorTips("服务器-开始"+(RoomSetting.getType() == 4 ? "催青" : "共育")+"失败:" + data.getMessage()); + } + + @Override + public void error(Throwable e) { + errorTips("开始"+(RoomSetting.getType() == 4 ? "催青" : "共育")+"网络请求失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java new file mode 100644 index 0000000..2462821 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/THBiasDialog.java @@ -0,0 +1,65 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.text.InputType; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogBiasThBinding; +import com.example.iot_controlhost.databinding.DialogCustomBinding; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomSetting; + +/** + * @Description 文本输入弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月12日 15:54 + */ +public class THBiasDialog extends BaseDialog { + public THBiasDialog(@NonNull Context context, boolean isTemp) { + super(context, true); + binding.tvTitle.setText(isTemp ? "校准温度" : "校准湿度"); + String value = String.valueOf(isTemp ? RoomSetting.getBiasTemp() : RoomSetting.getBiasHumidity()); + binding.tvSet1.setText(isTemp?"温度偏差":"湿度偏差"); + binding.tvTitle.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tvTitle).show()); + binding.tvSet1Symbol.setText(isTemp ? R.string.T_symbol : R.string.H_symbol); + binding.btnAdd1.setOnClickListener(getTargetListener(binding.tiSet1, isTemp, true)); + binding.btnMinus1.setOnClickListener(getTargetListener(binding.tiSet1, isTemp, false)); + binding.tiSet1.setText(value); + binding.tiSet1.setOnClickListener(v -> new NumberInputDialog(mContext, binding.tiSet1).show()); + binding.btnYes.setOnClickListener(view -> { + InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + MMKVUtil.put(isTemp ? RoomSetting.BIAS_TEMP : RoomSetting.BIAS_HUMIDITY, Double.parseDouble(binding.tiSet1.getText().toString())); + dismiss(); + }); + } + + private View.OnClickListener getTargetListener(TextView t1, boolean isTemp, boolean isAdd) { + double value = isTemp ? 0.1 : 1; + return view -> { + double d1 = Double.parseDouble(format(t1.getText().toString())); + double result = isAdd ? d1 + value : d1 - value; + t1.setText(String.format("%.1f", result)); + }; + } + + private String format(String str) { + if (str == null) { + return "0.0"; + } + if (str.contains("|")) { + // 删除| + str = str.replace("|", ""); + } + return str; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/TextViewDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/TextViewDialog.java new file mode 100644 index 0000000..c57a4e9 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/TextViewDialog.java @@ -0,0 +1,39 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.text.InputType; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogCustomBinding; +import com.example.iot_controlhost.databinding.DialogSetBinding; +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +/** + * @Description 文本输入弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月12日 15:54 + */ +public class TextViewDialog extends BaseDialog { + public TextViewDialog(@NonNull Context context, String title, String inputText, CustomString listener,boolean isNumber) { + super(context,true); + binding.tilInput.setVisibility(View.VISIBLE); + binding.tvMessage.setVisibility(View.GONE); + binding.btnNo.setVisibility(View.GONE); + binding.tvTitle.setText(title); + binding.tiInput.setText(inputText); + binding.tiInput.setInputType(isNumber?InputType.TYPE_CLASS_NUMBER:InputType.TYPE_CLASS_TEXT); + binding.btnYes.setOnClickListener(view -> { + InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + listener.listener(binding.tiInput.getText().toString()); + dismiss(); + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java new file mode 100644 index 0000000..ecad012 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskDialog.java @@ -0,0 +1,42 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.adapter.UvTaskAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogUvtaskBinding; +import com.example.iot_controlhost.databinding.ItemUvTaskBinding; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RxTag; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import java.util.List; + +/** + * @Description 消毒计划设置 + * @Author DuanKaiji + * @CreateTime 2024年03月07日 17:33 + */ +public class UvTaskDialog extends BaseDialog { + public UvTaskDialog(@NonNull Context context, List data) { + super(context); + UvTaskAdapter uvTaskAdapter = new UvTaskAdapter(data, ItemUvTaskBinding.class, binding.tvTipNone); + uvTaskAdapter.setContext(context); + binding.rvUvTask.setLayoutManager(new LinearLayoutManager(context)); + binding.rvUvTask.setAdapter(uvTaskAdapter); + binding.btnInsert.setOnClickListener(view -> new UvTaskInsertDialog(context, uvTaskAdapter).show()); + MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoDisinfect(), binding.llAutoSetParam,binding.llAutoSetParamNone); + binding.ivAuto.setOnClickListener(v -> { + //修改自动模式-消毒灯计划 + MMKVUtil.put(AutoModelSet.AUTO_DISINFECT, !AutoModelSet.isAutoDisinfect()); + MyUtil.changeIcon(context, binding.ivAuto, AutoModelSet.isAutoDisinfect(), binding.llAutoSetParam,binding.llAutoSetParamNone); + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java new file mode 100644 index 0000000..fa8915b --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/UvTaskInsertDialog.java @@ -0,0 +1,69 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.bigkoo.pickerview.builder.TimePickerBuilder; +import com.bigkoo.pickerview.view.TimePickerView; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.UvTaskAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogUvtaskInsertBinding; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.disinfect.DisinfectController; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; + +import java.util.Date; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 新增消毒计划 + * @Author DuanKaiji + * @CreateTime 2024年03月07日 17:26 + */ +public class UvTaskInsertDialog extends BaseDialog { + UvTaskAdapter adapter; + public UvTaskInsertDialog(@NonNull Context context, UvTaskAdapter adapter) { + super(context); + this.adapter = adapter; + //时间选择器 + TimePickerView picker1 = new TimePickerBuilder(mContext, (date, v) -> binding.tvStartTime.setText(TimeUtils.date2String(date, "HH:mm"))).isDialog(true) + .setContentTextSize(35).setTitleSize(35).isCyclic(false).setType(new boolean[]{false, false, false, true, true, false}).build(); + TimePickerView picker2 = new TimePickerBuilder(mContext, (date, v) -> binding.tvEndTime.setText(TimeUtils.date2String(date, "HH:mm"))).isDialog(true) + .setContentTextSize(35).setTitleSize(35).isCyclic(false).setType(new boolean[]{false, false, false, true, true, false}).build(); + binding.tvStartTime.setOnClickListener(view -> picker1.show()); + binding.tvEndTime.setOnClickListener(view -> picker2.show()); + binding.btnYes.setOnClickListener(view -> { + String startTimeStr = binding.tvStartTime.getText().toString(); + String endTimeStr = binding.tvEndTime.getText().toString(); + Date startTime = TimeUtils.string2Date(startTimeStr, "HH:mm"); + Date endTime = TimeUtils.string2Date(endTimeStr, "HH:mm"); + // 检查结束时间是否小于开始时间 + if (startTime == null || endTime == null) { + new ConfirmDialog(mContext, mContext.getString(R.string.error), mContext.getString(R.string.select_end_time_tip2), null).show(); + } else if (endTime.compareTo(startTime) <= 0) { + new ConfirmDialog(mContext, mContext.getString(R.string.error), mContext.getString(R.string.select_end_time_tip), null).show(); + } else { + //新增消毒计划 + int[] startTimes = MyUtil.getHourAndMinuteUsingLocalTime(startTime); + int[] endTimes = MyUtil.getHourAndMinuteUsingLocalTime(endTime); + if (!UVTaskUtil.addUvTask(new MyTask(startTimes[0], startTimes[1], endTimes[0], endTimes[1]))) { + Toasty.error(mContext, "当前时间与已有计划重叠,请重新设置").show(); + return; + } + boolean target = UVTaskUtil.getUvTask().stream().anyMatch(task -> + DisinfectController.isBetween(task.getStartTimeHour(), task.getStartTimeMinute(), task.getEndTimeHour(), task.getEndTimeMinute())); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> MyUtil.autoControlOperateThird(RoomController.uvLight, target))); + adapter.setDataList(UVTaskUtil.getUvTask()); + dismiss(); + } + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WeatherDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WeatherDialog.java new file mode 100644 index 0000000..634eccb --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WeatherDialog.java @@ -0,0 +1,40 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.example.iot_controlhost.adapter.WeatherAdapter; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogWeatherBinding; +import com.example.iot_controlhost.databinding.ItemWeatherBinding; +import com.example.iot_controlhost.model.net.Weather; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * 天气弹窗 + */ +public class WeatherDialog extends BaseDialog { + + public WeatherDialog(Context mContext, Weather infos) { + super(mContext); + WeatherAdapter adapter = new WeatherAdapter(infos.getData().getCasts(), ItemWeatherBinding.class); + //初始化界面控件 + binding.rvWeather.setLayoutManager(new LinearLayoutManager(mContext)); + binding.rvWeather.setAdapter(adapter); + binding.tvAddress.setText(infos.getData().getProvince() + "-" + infos.getData().getCity()); + + Weather.DataDTO.CastsDTO today = infos.getData().getCasts().get(0); + binding.tvDetails.setText(today.getDayweather() + "\t" + today.getDaytemp() + "℃\n" + + today.getDaypower() + " " + today.getDaywind() + " "); + binding.tvUpdateTime.setText("更新时间:" + infos.getData().getReporttime()); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java new file mode 100644 index 0000000..f018905 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkDialog.java @@ -0,0 +1,320 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.content.Context; +import android.graphics.BitmapFactory; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogWorkBinding; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.model.net.Batch; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.net.WorkList; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.QRCodeUtil; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.google.android.flexbox.AlignContent; +import com.google.android.flexbox.AlignItems; +import com.google.android.flexbox.JustifyContent; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +import org.xutils.http.HttpMethod; +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * @Description 登记工作/开始共育弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月06日 09:04 + */ +public class WorkDialog extends BaseDialog { + /** + * 作业开始时间 + */ + public static String START_WORK_TIME = "START_WORK_TIME"; + + PollingTask pollingTask; + + public WorkDialog(@NonNull Context context, WorkList model) { + super(context); + pollingTask = PollingTask.getInstance(); + binding.ivSignUp.setImageBitmap(QRCodeUtil.createQRCodeBitmap(RoomSetting.getAreaCode(), 300, 300, + BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.logo_blue), 30)); + if (model != null) { + //开始工作 + start(model); + } else { + stop(); + } + } + + /** + * 开始工作 + */ + private void start(WorkList model) { + List tvs = new ArrayList<>(); + Collections.addAll(tvs, binding.btnWork1, binding.btnWork2, binding.btnWork3, binding.btnWork4, binding.btnWork5, binding.btnWork6); + View.OnClickListener work = view -> { + String workName = ((TextView) view).getText().toString(); + //上传作业操作 + String tips = "确定要进行<" + workName + ">操作登记吗?"; + if ("消毒".equals(workName) && RoomSetting.isAirConditionForceCloseEnable()) { + tips = tips + "消毒期间将自动关闭空调"; + } + new ConfirmDialog(mContext, mContext.getString(R.string.warning), tips, () -> { + startWork(workName); + dismiss(); + }).show(); + }; + List list = Optional.ofNullable(model.getData()) + .orElse(Collections.emptyList()) + .stream() + .map(WorkList.Data::getName) + .collect(Collectors.toList()); +// List list = model.getData().stream().map(WorkList.Data::getName).collect(Collectors.toList()); + if (!list.isEmpty()) { + for (int i = 0; i < list.size() && i < tvs.size(); i++) { + tvs.get(i).setText(list.get(i)); + tvs.get(i).setVisibility(View.VISIBLE); + tvs.get(i).setOnClickListener(work); + } + } else { + for (TextView tv : tvs) { + tv.setVisibility(View.VISIBLE); + tv.setOnClickListener(work); + } + } + String state = (SilkwormDBManager.isStarting() ? "结束" : "开始") + (RoomSetting.getType() == 4 ? "催青" : "共育"); + binding.btnCultivateStart.setText(state); + binding.btnCultivateStart.setBackground(mContext.getDrawable(SilkwormDBManager.isStarting() ? R.drawable.bg_work_orange : R.drawable.bg_work_blue)); + binding.btnCultivateStart.setOnClickListener(view -> { + if (SilkwormDBManager.isStarting()) { + //结束共育 + new ConfirmDialog(mContext, mContext.getString(R.string.warning), "您确定要结束" + (RoomSetting.getType() == 4 ? "催青" : "共育") + "吗?", () -> stopCultivate()).show(); + } else { + //开始共育 + loadSeason(); + } + }); + } + + /** + * 结束工作 + */ + private void stop() { + String workName = RoomSetting.getWorkState().get("name"); + binding.clTime.setVisibility(View.VISIBLE); + binding.tvWorking.setText("正在进行" + workName + "作业"); + binding.flWork.setAlignContent(AlignContent.CENTER); + binding.flWork.setAlignItems(AlignItems.CENTER); + binding.flWork.setJustifyContent(JustifyContent.SPACE_AROUND); + binding.btnCultivateStart.setText("结束" + workName + "工作"); + binding.btnCultivateStart.setBackground(mContext.getDrawable(R.drawable.bg_work_orange)); + binding.btnCultivateStart.setOnClickListener(view -> { + //完成作业操作 + new ConfirmDialog(mContext, mContext.getString(R.string.warning), "确定要进行结束<" + workName + ">工作登记吗?", () -> { + stopWork(workName); + dismiss(); + }).show(); + }); + pollingTask.startPollingTaskOnUIThread(RxTag.WORK_TIME, 1, () -> { + Date startWork = getStartWorkTime(); + binding.tvHour.setText(String.valueOf(Math.abs(TimeUtils.getTimeSpanByNow(startWork, TimeConstants.HOUR)))); + binding.tvMinute.setText(String.valueOf(Math.abs(TimeUtils.getTimeSpanByNow(startWork, TimeConstants.MIN) % 60))); + binding.tvSecond.setText(String.valueOf(Math.abs(TimeUtils.getTimeSpanByNow(startWork, TimeConstants.SEC) % 60))); + }); + } + + /** + * 开始作业-网络请求 + */ + public void startWork(String workName) { + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_process)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.startWork); + params.addParameter("AreaCode", RoomSetting.getAreaCode()); + params.addParameter("Content", workName); + params.addParameter("JobUserName", RoomSetting.getUserName()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + successTips("作业开始-登记成功"); + Map map = new HashMap<>(); + if ("消毒".equals(workName) && RoomSetting.isAirConditionForceCloseEnable()) { + //消毒期间将自动关闭空调 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.airConditionInfrared.setPowerSupply(false))); + MMKVUtil.put(RoomSetting.IS_AIR_CONDITION_FORCE_CLOSE, true); + } + map.put("name", workName); + map.put("data", model.getData().toString()); + MMKVUtil.put(RoomSetting.WORK_STATE, GsonUtils.toJson(map)); + UserLog.task(workName + "作业开始-已登记"); + setStartWorkTime(new Date()); + new WorkDialog(mContext, null).show(); +// MyLog.test(getString(R.string.work_sign_success) + "获取作业码:" + model.getData()); + } + + @Override + public void error(Throwable e) { + errorTips(workName + "作业开始-登记失败"); + UserLog.taskError(mContext.getString(R.string.work_sign_failed)); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 结束工作-网络请求 + */ + public void stopWork(String workName) { + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_process)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.stopWork); + params.addParameter("JobRecordUniqueCode", RoomSetting.getWorkState().get("data")); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + successTips(workName + "作业结束-登记成功"); + MMKVUtil.put(RoomSetting.WORK_STATE, null); + UserLog.task(workName + "作业结束-已登记"); + // 如果是消毒作业且开启了空调强制关闭,则恢复空调自由状态 + MMKVUtil.put(RoomSetting.IS_AIR_CONDITION_FORCE_CLOSE, false); +// MyLog.test(workName + "作业结束-登记成功,请求结果:" + model.getData()); + } + + @Override + public void error(Throwable e) { + errorTips(workName + "作业结束-登记失败"); + UserLog.taskError(mContext.getString(R.string.work_sign_failed)); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 加载蚕季信息 + */ + public void loadSeason() { + DialogUtil.showLoadingDialog(mContext, "正在加载蚕季信息,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getBatchPlan); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + params.addParameter("datetime", MyUtil.getDateDay()); +// params.addParameter("datetime", "2023-5-06"); //有数据 + x.http().get(params, new XHttpShorthand() { + @Override + public void success(Batch model) { + if (model.getData() == null || model.getData().size() == 0) { + infoTips("暂无蚕季信息"); + } else { + new StartCultivateDialog(mContext, model.getData()).show(); + } + } + + @Override + public void error(Throwable e) { + errorTips("获取蚕季信息失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 结束共育-网络请求 + */ + public void stopCultivate() { + //网络请求 + DialogUtil.showLoadingDialog(mContext, "正在结束" + (RoomSetting.getType() == 4 ? "催青" : "共育") + "中,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.stopCultivate); + params.addParameter("UniqueCode", SilkwormDBManager.INSTANCE.getData().getId()); + params.addParameter("EndTime", MyUtil.getDateTime()); + x.http().request(HttpMethod.PUT, params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + //返回200即结束成功 + DialogUtil.hideLoadingDialog(); + //记录此次蚕期 + SilkwormDBManager.updateCurDate(); + //停止共育后刷新共育状态 + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 7); +// MyUtil.relaunchApp(); + } + + @Override + public void error(Throwable e) { + errorTips("服务器-结束失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + public static Date getStartWorkTime() { + try { + return DateFormat.getDateTimeInstance().parse(MMKVUtil.get(START_WORK_TIME, "2024-03-06 10:45:49")); + } catch (ParseException e) { + e.printStackTrace(); + return new Date(); + } + } + + public static void setStartWorkTime(Date startWorkTime) { + MMKVUtil.put(START_WORK_TIME, DateFormat.getDateTimeInstance().format(startWorkTime)); + } + + @Override + public void dismiss() { + super.dismiss(); + pollingTask.stopPollingTask(RxTag.WORK_TIME); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java new file mode 100644 index 0000000..24a6a41 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/dialog/WorkNewDialog.java @@ -0,0 +1,481 @@ +package com.example.iot_controlhost.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.StageAdapter; +import com.example.iot_controlhost.adapter.WorkAdapter; +import com.example.iot_controlhost.adapter.WorkAdapterInterface; +import com.example.iot_controlhost.base.BaseDialog; +import com.example.iot_controlhost.databinding.DialogNewWorkBinding; +import com.example.iot_controlhost.databinding.ItemBtnBinding; +import com.example.iot_controlhost.model.net.Batch; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.net.ControlMode; +import com.example.iot_controlhost.model.net.StageList; +import com.example.iot_controlhost.model.net.WorkList; +import com.example.iot_controlhost.model.net.WorkStage; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.QRCodeUtil; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.database.TempTHVDBManager; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import org.xutils.http.HttpMethod; +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 登记工作/开始共育弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月06日 09:04 + */ +public class WorkNewDialog extends BaseDialog { + /** + * 作业开始时间 + */ + public static String START_WORK_TIME = "START_WORK_TIME"; + + PollingTask pollingTask; + WorkList workList; + StageList stageList; + WorkAdapter workAdapter; + StageAdapter stageAdapter; + + public WorkNewDialog(@NonNull Context context) { + super(context); + pollingTask = PollingTask.getInstance(); + binding.btnScan.setOnClickListener(v -> { + showImageDialog(mContext, QRCodeUtil.createQRCodeBitmap(RoomSetting.getAreaCode(), 300, 300, + BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.logo_blue), 30)); + }); + binding.btnStopStage.setOnClickListener(v -> { + // 结束阶段 + binding.tvStageCurrent.setText(""); + binding.tvStageContent.setText(""); + if (RoomSetting.getMode() != 0) { + // 进入手动模式 + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3); + } + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 5); + TempTHVDBManager.INSTANCE.setStage(null, null); + }); + binding.btnStopWork.setOnClickListener(v -> { + // 结束作业 + binding.tvWorkCurrent.setText(""); + binding.tvWorkContent.setText(""); + stopWork(); + // 恢复之前的设置 + TempTHVDBManager.INSTANCE.appSet(); + if (RoomSetting.getMode() == 1) { + // 如果是自动模式,则刷新自动模式参数 + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + } + }); + String state = (SilkwormDBManager.isStarting() ? "结束" : "开始") + (RoomSetting.getType() == 4 ? "催青" : "共育"); + binding.btnCultivateStart.setText(state); + binding.btnCultivateStart.setBackground(mContext.getDrawable(SilkwormDBManager.isStarting() ? R.drawable.bg_work_orange : R.drawable.bg_work_blue)); + binding.btnCultivateStart.setOnClickListener(view -> { + if (SilkwormDBManager.isStarting()) { + //结束共育 + new ConfirmDialog(mContext, mContext.getString(R.string.warning), "您确定要结束" + (RoomSetting.getType() == 4 ? "催青" : "共育") + "吗?", () -> stopCultivate()).show(); + } else { + //开始共育 + loadSeason(); + } + }); + workAdapter = new WorkAdapter(new ArrayList<>(), ItemBtnBinding.class, binding.tvWorkCurrent, binding.tvWorkContent, content -> startWork(content)); + binding.rvWork.setAdapter(workAdapter); + binding.rvWork.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)); + stageAdapter = new StageAdapter(new ArrayList<>(), ItemBtnBinding.class, + binding.tvStageCurrent, binding.tvStageContent); + binding.rvStage.setAdapter(stageAdapter); + binding.rvStage.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false)); + refreshNewWorkList(); + // 初始化界面 + if (RoomSetting.getWorkState() != null) { + binding.tvWorkCurrent.setText(RoomSetting.getWorkState().get("name")); + binding.tvWorkContent.setText("正在作业中"); + binding.btnStopWork.setVisibility(View.VISIBLE); + } else { + binding.tvWorkCurrent.setText(""); + binding.tvWorkContent.setText(""); + binding.btnStopWork.setVisibility(View.GONE); + } + if (TempTHVDBManager.INSTANCE.getStageName() != null) { + binding.tvStageCurrent.setText(TempTHVDBManager.INSTANCE.getStageName()); + binding.tvStageContent.setText(TempTHVDBManager.INSTANCE.getStageContent()); + } else { + binding.tvStageCurrent.setText(""); + binding.tvStageContent.setText(""); + } + } + + public void showImageDialog(Context context, Bitmap bitmap) { + Dialog dialog = new Dialog(context); + ImageView imageView = new ImageView(context); + imageView.setImageBitmap(bitmap); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + imageView.setAdjustViewBounds(true); + imageView.setOnClickListener(v -> dialog.dismiss()); + dialog.setContentView(imageView); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + dialog.show(); + } + + public void refreshNewWorkList() { + // 获取新作业、阶段信息 + DialogUtil.showLoadingDialog(mContext, "正在读取作业列表中..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getWorkList); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(WorkList model) { + workList = model; + workAdapter.setDataList(workList.getData()); + } + + @Override + public void error(Throwable e) { + errorTips("作业列表获取失败,请检查网络"); + UserLog.taskError("作业列表获取失败" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + + }); + DialogUtil.showLoadingDialog(mContext, "正在读取阶段列表中..."); + RequestParams params2 = XHttpManager.getInstance().getRequestParams(Url.getStageList); + params2.addParameter("areaCode", RoomSetting.getAreaCode()); + x.http().get(params2, new XHttpShorthand() { + @Override + public void success(StageList model) { + stageList = model; + stageAdapter.setDataList(stageList.getData()); + } + + @Override + public void error(Throwable e) { + errorTips("阶段列表获取失败,请检查网络"); + UserLog.taskError("阶段列表获取失败" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 开始作业-网络请求 + */ + public void startWork(String workName) { + if (RoomSetting.getWorkState() != null) { + Toasty.info(mContext, "当前已在进行" + RoomSetting.getWorkState().get("name") + "作业,请先结束当前作业").show(); + return; + } + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_process)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.startWork); + params.addParameter("AreaCode", RoomSetting.getAreaCode()); + params.addParameter("Content", workName); + params.addParameter("JobUserName", RoomSetting.getUserName()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + successTips("作业开始-登记成功"); + Map map = new HashMap<>(); + if ("消毒".equals(workName) && RoomSetting.isAirConditionForceCloseEnable()) { + //消毒期间将自动关闭空调 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.airConditionInfrared.setPowerSupply(false))); + MMKVUtil.put(RoomSetting.IS_AIR_CONDITION_FORCE_CLOSE, true); + } + map.put("name", workName); + map.put("data", model.getData().toString()); + MMKVUtil.put(RoomSetting.WORK_STATE, GsonUtils.toJson(map)); + UserLog.task(workName + "作业开始-已登记"); + setStartWorkTime(new Date()); + binding.btnStopWork.setVisibility(View.VISIBLE); +// MyLog.test(getString(R.string.work_sign_success) + "获取作业码:" + model.getData()); + } + + @Override + public void error(Throwable e) { + errorTips(workName + "作业开始-登记失败"); + UserLog.taskError(mContext.getString(R.string.work_sign_failed)); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 结束工作-网络请求 + */ + public void stopWork() { + String workName = RoomSetting.getWorkState().get("name"); + DialogUtil.showLoadingDialog(mContext, mContext.getString(R.string.in_process)); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.stopWork); + params.addParameter("JobRecordUniqueCode", RoomSetting.getWorkState().get("data")); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + successTips(workName + "作业结束-登记成功"); + MMKVUtil.put(RoomSetting.WORK_STATE, null); + UserLog.task(workName + "作业结束-已登记"); + MMKVUtil.put(RoomSetting.IS_AIR_CONDITION_FORCE_CLOSE, false); +// MyLog.test(workName + "作业结束-登记成功,请求结果:" + model.getData()); + binding.btnStopWork.setVisibility(View.INVISIBLE); + } + + @Override + public void error(Throwable e) { + errorTips(workName + "作业结束-登记失败"); + UserLog.taskError(mContext.getString(R.string.work_sign_failed)); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 加载蚕季信息 + */ + public void loadSeason() { + DialogUtil.showLoadingDialog(mContext, "正在加载蚕季信息,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getBatchPlan); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + params.addParameter("datetime", MyUtil.getDateDay()); +// params.addParameter("datetime", "2023-5-06"); //有数据 + x.http().get(params, new XHttpShorthand() { + @Override + public void success(Batch model) { + if (model.getData() == null || model.getData().size() == 0) { + infoTips("暂无蚕季信息"); + } else { + new StartCultivateDialog(mContext, model.getData()).show(); + } + } + + @Override + public void error(Throwable e) { + errorTips("获取蚕季信息失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 结束共育-网络请求 + */ + public void stopCultivate() { + //网络请求 + DialogUtil.showLoadingDialog(mContext, "正在结束" + (RoomSetting.getType() == 4 ? "催青" : "共育") + "中,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.stopCultivate); + params.addParameter("UniqueCode", SilkwormDBManager.INSTANCE.getData().getId()); + params.addParameter("EndTime", MyUtil.getDateTime()); + x.http().request(HttpMethod.PUT, params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + //返回200即结束成功 + DialogUtil.hideLoadingDialog(); + //记录此次蚕期 + SilkwormDBManager.updateCurDate(); + //停止共育后刷新共育状态 + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 7); +// MyUtil.relaunchApp(); + } + + @Override + public void error(Throwable e) { + errorTips("服务器-结束失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + public static Date getStartWorkTime() { + try { + return DateFormat.getDateTimeInstance().parse(MMKVUtil.get(START_WORK_TIME, "2024-03-06 10:45:49")); + } catch (ParseException e) { + e.printStackTrace(); + return new Date(); + } + } + + public static void setStartWorkTime(Date startWorkTime) { + MMKVUtil.put(START_WORK_TIME, DateFormat.getDateTimeInstance().format(startWorkTime)); + } + + @Override + public void dismiss() { + super.dismiss(); + pollingTask.stopPollingTask(RxTag.WORK_TIME); + } + + public static String getInfo(WorkStage model, boolean isWork) { + StringBuffer content = new StringBuffer(); + Map map = new HashMap<>(); + for (WorkStage.Data data : model.getData()) { + ControlMode info = GsonUtils.fromJson(data.getConfigJson(), ControlMode.class); + content.append(data.getEnvTypeName()).append(":"); + if (info.getTargetValue() != 0.0) { + content.append("目标值:").append(info.getTargetValue()).append(","); + } + if (info.getWarningMaxValue() != 0.0) { + content.append("最大值:").append(info.getWarningMaxValue()).append(","); + } + if (info.getWarningMinValue() != 0.0) { + content.append("最小值:").append(info.getWarningMinValue()).append(","); + } + if (info.getOpenTime() != 0) { + content.append("开启时长:").append(info.getOpenTime()).append("分钟,"); + } + if (info.getCloseTime() != 0) { + content.append("关闭时长:").append(info.getCloseTime()).append("分钟,"); + } + // 删除末尾可能多余的“,” + int len = content.length(); + if (content.charAt(len - 1) == ',') { + content.deleteCharAt(len - 1); + } + content.append("\n"); + if ("温度".equals(data.getEnvTypeName())) { + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, true); + if (isWork) { + // 作业,保存现在的设置 在作业结束后需要恢复 + map.put(AutoModelSet.TARGET_TEMPERATURE, AutoModelSet.getTargetTemperature()); + map.put(AutoModelSet.MIN_TEMPERATURE, AutoModelSet.getMinTemperature()); + map.put(AutoModelSet.MAX_TEMPERATURE, AutoModelSet.getMaxTemperature()); + } + if (info.getTargetValue() != 0.0) { + MMKVUtil.put(AutoModelSet.TARGET_TEMPERATURE, info.getTargetValue()); + } else { + info.setTargetValue(AutoModelSet.getTargetTemperature()); + } + if (info.getWarningMinValue() != 0.0) { + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, info.getWarningMinValue()); + } else { + info.setWarningMinValue(AutoModelSet.getMinTemperature()); + } + if (info.getWarningMaxValue() != 0.0) { + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, info.getWarningMaxValue()); + } else { + info.setWarningMaxValue(AutoModelSet.getMaxTemperature()); + } + } else if ("湿度".equals(data.getEnvTypeName())) { + if (isWork) { + // 作业,保存现在的设置 在作业结束后需要恢复 + map.put(AutoModelSet.TARGET_HUMIDITY, AutoModelSet.getTargetHumidity()); + map.put(AutoModelSet.MIN_HUMIDITY, AutoModelSet.getMinHumidity()); + map.put(AutoModelSet.MAX_HUMIDITY, AutoModelSet.getMaxHumidity()); + } + MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, true); + if (info.getTargetValue() != 0.0) { + MMKVUtil.put(AutoModelSet.TARGET_HUMIDITY, info.getTargetValue()); + } else { + info.setTargetValue(AutoModelSet.getTargetHumidity()); + } + if (info.getWarningMinValue() != 0.0) { + MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, info.getWarningMinValue()); + } else { + info.setWarningMinValue(AutoModelSet.getMinHumidity()); + } + if (info.getWarningMaxValue() != 0.0) { + MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, info.getWarningMaxValue()); + } else { + info.setWarningMaxValue(AutoModelSet.getMaxHumidity()); + } + } else if ("通风".equals(data.getEnvTypeName())) { + if (isWork) { + // 作业,保存现在的设置 在作业结束后需要恢复 + map.put(AutoModelSet.CYCLE_START, AutoModelSet.getCycleStart()); + map.put(AutoModelSet.CYCLE_STOP, AutoModelSet.getCycleStop()); + } + MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, true); + if (info.getOpenTime() != 0) { + MMKVUtil.put(AutoModelSet.CYCLE_START, info.getOpenTime()); + } else { + info.setOpenTime(AutoModelSet.getCycleStart()); + } + if (info.getCloseTime() != 0) { + MMKVUtil.put(AutoModelSet.CYCLE_STOP, info.getCloseTime()); + } else { + info.setCloseTime(AutoModelSet.getCycleStop()); + } + } + // 保存当前的设置 + if (isWork) { + // 作业,保存现在的设置 在作业结束后需要恢复 + TempTHVDBManager.INSTANCE.init(map); + } + } + // 去掉最后一个换行符 + if (content.length() > 0 && content.charAt(content.length() - 1) == '\n') { + content.deleteCharAt(content.length() - 1); + } + return content.toString(); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/AutoFragment.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/AutoFragment.java new file mode 100644 index 0000000..d9274f3 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/AutoFragment.java @@ -0,0 +1,202 @@ +package com.example.iot_controlhost.ui.fragment; + +import android.graphics.Color; +import android.transition.Fade; +import android.transition.TransitionManager; +import android.view.View; + +import androidx.recyclerview.widget.GridLayoutManager; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.AutoControllerAdapter; +import com.example.iot_controlhost.base.BaseFragment; +import com.example.iot_controlhost.base.BaseMode; +import com.example.iot_controlhost.databinding.FragmentModelAutoBinding; +import com.example.iot_controlhost.databinding.ItemControllerAutoBinding; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.ui.dialog.AirDialog; +import com.example.iot_controlhost.ui.dialog.SetAutoParamDialog; +import com.example.iot_controlhost.ui.dialog.FloorDialog; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; + +import java.util.ArrayList; +import java.util.List; + +import es.dmoral.toasty.Toasty; + +/** + * 自动模式 + */ +public class AutoFragment extends BaseFragment implements BaseMode { + private static AutoControllerAdapter manualControllerAdapter; + private static AutoControllerAdapter manualControllerAdapterTwo; +// private static AutoFragment INSTANCE = new AutoFragment(); + + public AutoFragment() { + } + +// public static AutoFragment getInstance() { +// return INSTANCE; +// } + + @Override + public void init() { + binding.setVm(presenter); + presenter.getSetAutoParamLiveData().observe(this, integer -> { + switch (integer) { + case 0 -> //设置自动模式温度 + new SetAutoParamDialog(mContext, 0, new String[]{String.valueOf(AutoModelSet.getTargetTemperature()), + String.valueOf(AutoModelSet.getMaxTemperature()), String.valueOf(AutoModelSet.getMinTemperature())}).show(); + case 1 -> //设置自动模式湿度 + new SetAutoParamDialog(mContext, 1, new String[]{String.valueOf(AutoModelSet.getTargetHumidity()), + String.valueOf(AutoModelSet.getMaxHumidity()), String.valueOf(AutoModelSet.getMinHumidity())}).show(); + case 2 -> //设置自动模式换气 + new SetAutoParamDialog(mContext, 2, new String[]{String.valueOf(AutoModelSet.getCycleStart()), + String.valueOf(AutoModelSet.getCycleStop()), String.valueOf(AutoModelSet.getMaxDensityCO2())}).show(); + case 6 ->//刷新手动温度控制状态 + refreshAutoView(); + } + }); + manualControllerAdapter = new AutoControllerAdapter(mContext, null, ItemControllerAutoBinding.class); + manualControllerAdapterTwo = new AutoControllerAdapter(mContext, null, ItemControllerAutoBinding.class); + binding.recycleView.setAdapter(manualControllerAdapter); + binding.recycleView.setLayoutManager(new GridLayoutManager(mContext, 2)); + binding.recycleView2.setAdapter(manualControllerAdapterTwo); + binding.recycleView2.setLayoutManager(new GridLayoutManager(mContext, 4)); + List devices = RoomController.getAllControlRelays(); + // 第一排显示2个设备 + if (devices.size() <= 2) { + int missingCount = 2 - devices.size(); + for (int i = 0; i < missingCount; i++) { + devices.add(new Relay("未配置")); + } + } + manualControllerAdapter.setDataList(devices); + // 第二排显示剩下的设备 + List remainingDevices = new ArrayList<>(); + if (devices.size() > 2) { + List tempDevices = devices.subList(2, devices.size()); + remainingDevices.addAll(tempDevices); + } + if (remainingDevices.size() < 4) { + int missingCount = 4 - remainingDevices.size(); + for (int i = 0; i < missingCount; i++) { + remainingDevices.add(new Relay("未配置")); + } + } + manualControllerAdapterTwo.setDataList(remainingDevices); + refreshAutoView(); + if (RoomSetting.getMode() == 1) { + initMode(); + } + } + + @Override + public void initListener() { + super.initListener(); + binding.cardFloor.setOnClickListener(v -> { + if (AutoModelSet.isAutoTemperature()) { + Toasty.info(mContext, "该设备由自动控制系统接管中").show(); + } else { + new FloorDialog(mContext).show(); + } + }); + binding.cardAir.setOnClickListener(v -> { + if (AutoModelSet.isAutoTemperature()) { + Toasty.info(mContext, "该设备由自动控制系统接管中").show(); + } else { + new AirDialog(mContext,MyInfraredUtils.getInstances()).show(); + } + }); + } + + @Override + public void initMode() { + presenter.startAutoControl(); + presenter.updateUI(); + presenter.updateParamUI(); + } + + @Override + public void stopMode() { + presenter.stopAutoControl(); + RoomController.closeAllControllers(); + } + + /** + * 刷新自动视图 + */ + private void refreshAutoView() { + TransitionManager.beginDelayedTransition(binding.ll, new Fade()); + //初始化自动模式参数显示 + binding.llTemAuto.setVisibility(AutoModelSet.isAutoTemperature() ? View.VISIBLE : View.GONE); + binding.llTemManual.setVisibility(AutoModelSet.isAutoTemperature() ? View.GONE : View.VISIBLE); + binding.llHumAuto.setVisibility(AutoModelSet.isAutoHumidity() ? View.VISIBLE : View.GONE); + binding.llHumManual.setVisibility(AutoModelSet.isAutoHumidity() ? View.GONE : View.VISIBLE); + binding.llVenAuto.setVisibility(AutoModelSet.isAutoVentilator() ? View.VISIBLE : View.GONE); + binding.llVenManual.setVisibility(AutoModelSet.isAutoVentilator() ? View.GONE : View.VISIBLE); + //初始化空调地暖视图 + changeFloorState(); + changeAirState(); + //初始化手动控制器显示 + manualControllerAdapter.notifyDataSetChanged(); + manualControllerAdapterTwo.notifyDataSetChanged(); + } + + private void changeFloorState() { + Floor floor = RoomController.floor; + binding.tvStateFloor.setText(floor.getState()); + if (floor.isPowerSupply()) { + binding.ivControllerFloor.setImageResource(R.mipmap.floor_open); + binding.cardFloor.setBackgroundColor(Color.parseColor("#FF9C00")); + binding.tvStateFloor.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvFloor.setTextColor(Color.parseColor("#FFFFFF")); + binding.ivPowerFloor.setBackground(mContext.getDrawable(R.drawable.bg_controller_open)); + binding.ivPowerFloor.setImageDrawable(mContext.getDrawable(R.mipmap.switch_on)); + binding.tvPowerFloor.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvFloorT.setTextColor(Color.parseColor("#FFFFFF")); + } else { + binding.ivControllerFloor.setImageResource(R.mipmap.floor_close); + binding.cardFloor.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33DCE3E3" : "#F2F0F1")); + binding.tvStateFloor.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.ivPowerFloor.setBackground(mContext.getDrawable(R.drawable.bg_controller_close)); + binding.ivPowerFloor.setImageDrawable(mContext.getDrawable(R.mipmap.switch_off)); + binding.tvFloor.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvPowerFloor.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvFloorT.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + } + binding.tvPowerFloor.setVisibility(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) ? View.VISIBLE : View.GONE); + binding.tvPowerFloor.setText("实时功率:" + RoomController.relayFloor.getPower() + "w"); + } + + private void changeAirState() { + AirConditionInfrared air = MyInfraredUtils.getInstances(); + binding.tvStateAir.setText(air.getState()); + if (air.isPowerSupply()) { + binding.ivControllerAir.setImageResource(R.mipmap.air_open); + binding.cardAir.setBackgroundColor(Color.parseColor(air.getMode() == AirConditionInfrared.HEAT ? "#FF9C00" : + air.getMode() == AirConditionInfrared.COLD?"#00A1FF":"#7DFA5B")); + binding.tvStateAir.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvAir.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvPowerAir.setTextColor(Color.parseColor("#FFFFFF")); + binding.ivPowerAir.setBackground(mContext.getDrawable(R.drawable.bg_controller_open)); + binding.ivPowerAir.setImageDrawable(mContext.getDrawable(R.mipmap.switch_on)); + } else { + binding.ivControllerAir.setImageResource(R.mipmap.air_close); + binding.cardAir.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33DCE3E3" : "#F2F0F1")); + binding.tvStateAir.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvAir.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvPowerAir.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.ivPowerAir.setBackground(mContext.getDrawable(R.drawable.bg_controller_close)); + binding.ivPowerAir.setImageDrawable(mContext.getDrawable(R.mipmap.switch_off)); + } + binding.tvPowerAir.setVisibility(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) ? View.VISIBLE : View.GONE); + binding.tvPowerAir.setText("实时功率:" + RoomController.airCondition.getPower() + "w"); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/AutoFragmentPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/AutoFragmentPresenter.java new file mode 100644 index 0000000..dc20fae --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/AutoFragmentPresenter.java @@ -0,0 +1,130 @@ +package com.example.iot_controlhost.ui.fragment; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.SingleLiveEvent; +import com.example.iot_controlhost.utils.control.disinfect.DisinfectController; +import com.example.iot_controlhost.utils.control.even.EvenController; +import com.example.iot_controlhost.utils.control.humidity.HumidityController; +import com.example.iot_controlhost.utils.control.temperature.TemperatureController; +import com.example.iot_controlhost.utils.control.ventilator.VentilatorController; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.TopicClass; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +public class AutoFragmentPresenter extends BasePresenter { + + public MutableLiveData floorT = new MutableLiveData<>(""); + public MutableLiveData targetTemperature = new MutableLiveData<>(); + public MutableLiveData targetHumidity = new MutableLiveData<>(); + public MutableLiveData targetStart = new MutableLiveData<>(); + public MutableLiveData targetStop = new MutableLiveData<>(); + public MutableLiveData setAutoParamLiveData = new SingleLiveEvent<>(); + + public AutoFragmentPresenter(@NonNull Application application) { + super(application); + //接收消息 + RxJavaUtils.doInIOThread(new RxIOTask(null) { + @Override + public Void doInIOThread(Object o) { + //更新自动控制相关参数显示 + RxBusUtils.get().onMainThread(RxTag.UPDATE_AUTO, Integer.class, i -> { + switch (i) { + case 2 -> updateUI(); + case 3 -> { + startAutoControl(); + updateParamUI(); + updateUI(); + } + } + }); + return null; + } + }); + pollingTask.startPollingTaskOnIOThread(RxTag.AUTO_MODE_UI_REFRESH, 5, ()->{ + floorT.postValue(RoomSensor.getFloorTemperature() + "℃"); + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + setAutoParamLiveData.postValue(6); + } + }); + } + + /** + * 更新UI + */ + public void updateUI() { + setAutoParamLiveData.setValue(6); + } + + /** + * 更新自动模式 + */ + public void startAutoControl() { + if (AutoModelSet.isAutoTemperature()) { + TemperatureController.getInstance().start(); + } else { + TemperatureController.getInstance().stop(); + } + if (AutoModelSet.isAutoHumidity()) { + HumidityController.getInstance().start(); + } else { + HumidityController.getInstance().stop(); + } + if (AutoModelSet.isAutoVentilator()) { + VentilatorController.getInstance().start(); + } else { + VentilatorController.getInstance().stop(); + } + if (AutoModelSet.isAutoDisinfect()) { + DisinfectController.getInstance().start(); + } else { + DisinfectController.getInstance().stop(); + } + if (AutoModelSet.isAutoEven()) { + EvenController.getInstance().start(); + } else { + EvenController.getInstance().stop(); + } + } + + /** + * 更新自动模式参数显示 + */ + public void updateParamUI() { + targetTemperature.setValue(String.valueOf(AutoModelSet.getTargetTemperature())); + targetHumidity.setValue(String.valueOf(AutoModelSet.getTargetHumidity())); + targetStart.setValue(String.valueOf(AutoModelSet.getCycleStart())); + targetStop.setValue(String.valueOf(AutoModelSet.getCycleStop())); + } + + public MutableLiveData getSetAutoParamLiveData() { + return setAutoParamLiveData; + } + + /** + * 停止自动控制 + */ + public void stopAutoControl() { + TemperatureController.getInstance().stop(); + HumidityController.getInstance().stop(); + VentilatorController.getInstance().stop(); + DisinfectController.getInstance().stop(); + EvenController.getInstance().stop(); + } + + public void setAutoParam(int setType) { + setAutoParamLiveData.setValue(setType); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java new file mode 100644 index 0000000..edc928b --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragment.java @@ -0,0 +1,362 @@ +package com.example.iot_controlhost.ui.fragment; + +import android.graphics.Color; +import android.graphics.DashPathEffect; +import android.graphics.Typeface; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BaseFragment; +import com.example.iot_controlhost.base.BaseMode; +import com.example.iot_controlhost.databinding.FragmentModelIntelligenceBinding; +import com.example.iot_controlhost.model.AITHMode; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.ui.dialog.SetAutoParamDialog; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.ui.dialog.UvTaskDialog; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.database.MyAIModeUtil; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; +import com.github.mikephil.charting.charts.LineChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.components.LimitLine; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.data.LineData; +import com.github.mikephil.charting.data.LineDataSet; +import com.github.mikephil.charting.formatter.DefaultAxisValueFormatter; +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 智能模式 + */ +public class IntelligenceFragment extends BaseFragment implements BaseMode { + /** + * 当前蚕季所有信息 + */ + List modes; + /** + * 今日信息 + */ + AITHMode mode; + /** + * 图表折线两边的空白边距 + */ + private static final int MARGIN = 20; + /** + * 当前龄期,亦为当前模板下标 + */ + int age = 0; + /** + * 当前龄期的第几天 + */ + int day; +// private static IntelligenceFragment INSTANCE = new IntelligenceFragment(); + + public IntelligenceFragment() { + } + +// public static IntelligenceFragment getInstance() { +// return INSTANCE; +// } + + @Override + public void init() { + binding.setVm(presenter); + modes = MyAIModeUtil.getMyAiMode(); + //禁止Y轴缩放 + binding.chart.setScaleYEnabled(false); + binding.chart.getDescription().setEnabled(false); + binding.chart.setDrawGridBackground(false); + binding.chart.setNoDataText("智能模式已暂停,设备已停止"); + binding.chart.setNoDataTextTypeface(Typeface.create("sans-serif", Typeface.BOLD)); + //图例 + Legend legend = binding.chart.getLegend(); + legend.setWordWrapEnabled(false); + legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); + legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); + legend.setOrientation(Legend.LegendOrientation.HORIZONTAL); + legend.setDrawInside(true); + legend.setTextColor(Color.parseColor("#424242")); + legend.setTextSize(20f); + //Y轴-左侧 + YAxis leftAxis = binding.chart.getAxisLeft(); + leftAxis.setDrawGridLines(true); + leftAxis.setDrawTopYLabelEntry(true); + leftAxis.setTextSize(20f); + leftAxis.setDrawZeroLine(true); + leftAxis.setAxisMaximum(32); + leftAxis.setTextColor(Color.parseColor("#424242")); + leftAxis.setGridDashedLine(new DashPathEffect(new float[]{5, 5}, 0)); + leftAxis.setGridColor(Color.GRAY); + //Y轴-右侧 + YAxis rightAxis = binding.chart.getAxisRight(); + rightAxis.setTextSize(20f); + rightAxis.setDrawGridLines(false); + rightAxis.setDrawTopYLabelEntry(true); + rightAxis.setAxisMaximum(100); + rightAxis.setTextColor(Color.parseColor("#424242")); + rightAxis.setAxisMinimum(50); + //X轴 + XAxis xAxis = binding.chart.getXAxis(); + xAxis.setTextSize(20f); + xAxis.setTextColor(Color.parseColor("#424242")); + xAxis.setGranularity(24f); + binding.chart.setExtraBottomOffset(5); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); + xAxis.setDrawGridLines(false); + xAxis.setAvoidFirstLastClipping(true); + xAxis.setValueFormatter(new DefaultAxisValueFormatter(0) { + @Override + public String getFormattedValue(float value) { + int i = getI((int) value - MARGIN); + if (i < 0) { + return ""; + } + return modes.get(i).getName(); + } + }); + if (RoomSetting.isDarkTheme()) { + setGlobalTextColor(binding.chart, Color.WHITE); + } + String name = (RoomSetting.getType() == 4 ? "催青" : "共育"); + presenter.getNoticeLiveData().observe(this, integer -> { + switch (integer) { + case 0 -> //刷新模板 + new ConfirmDialog(mContext, getString(R.string.warning), "确定要重新获取温湿度模板吗?\n注:此操作将清除自定义模板参数及"+name+"时间前后移量", + () -> presenter.refreshTemplate()).show(); + case 1 -> //暂停 + new ConfirmDialog(mContext, getString(R.string.warning), "确定要关闭所有设备,暂停智能模式吗?", () -> { + DialogUtil.showLoadingDialog(mContext, "正在停止中..."); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask<>() { + @Override + public Object doInQueueThread() { + stopMode(); + return null; + } + + @Override + public void doInUIThread(Object o) { + hideLoadingDialog(); + } + }); + }).show(); + case 2 -> //退回半天 + new ConfirmDialog(mContext, getString(R.string.warning), "确定将"+name+"计划返回至12小时前吗", () -> { + MMKVUtil.put(AIModelSet.AI_BIAS_TIME, AIModelSet.getAiBiasTime() - 12); + refreshInfo(); + }).show(); + case 3 -> //提前半天 + new ConfirmDialog(mContext, getString(R.string.warning), "确定将"+name+"计划提前到12小时后吗", () -> { + MMKVUtil.put(AIModelSet.AI_BIAS_TIME, AIModelSet.getAiBiasTime() + 12); + refreshInfo(); + }).show(); + case 4 -> {//消毒 + List myTaskList = UVTaskUtil.getUvTask(); + Collections.sort(myTaskList); + new UvTaskDialog(mContext,myTaskList).show(); + } + case 5 -> //自定义智能模式-温湿度 + new SetAutoParamDialog(mContext, 3, new String[]{String.valueOf(AIModelSet.getTargetTemperature()), + String.valueOf(AIModelSet.getTargetHumidity())}).show(); + case 6 -> //修改当前龄期的关闭通风时间 + new SetAutoParamDialog(mContext, 4, new String[]{String.valueOf(AIModelSet.getAiCycleStart()), + String.valueOf(AIModelSet.getAiCycleStop())}).show(); + case 7 ->// + refreshInfo(); + case 8 ->//更新开始暂停图标显示 + binding.btnStartStop.setImageDrawable(mContext.getDrawable(AIModelSet.isAiProcessing() ? R.mipmap.pause : R.mipmap.play)); + } + }); + if (RoomSetting.getMode() == 2) { + initMode(); + } + } + + @Override + public void initMode() { + //开始硬件控制 + if (AIModelSet.isAiProcessing()) { + presenter.startAICultivate(); + } + binding.btnStartStop.setImageDrawable(mContext.getDrawable(AIModelSet.isAiProcessing() ? R.mipmap.pause : R.mipmap.play)); + } + + @Override + public void stopMode() { + presenter.stopIntelligenceControl(); + RoomController.closeAllControllers(); + binding.chart.setData(null); + RxJavaUtils.doInUIThread(new RxUITask(null) { + @Override + public void doInUIThread(Object o) { + binding.chart.invalidate(); + } + }); + } + + private void setGlobalTextColor(LineChart lineChart, int textColor) { + // 设置 X 轴文字颜色 + XAxis xAxis = lineChart.getXAxis(); + xAxis.setTextColor(textColor); + // 设置左侧 Y 轴文字颜色 + YAxis leftYAxis = lineChart.getAxisLeft(); + leftYAxis.setTextColor(textColor); + // 设置右侧 Y 轴文字颜色 + YAxis rightYAxis = lineChart.getAxisRight(); + rightYAxis.setTextColor(textColor); + // 设置图例文字颜色 + Legend legend = lineChart.getLegend(); + legend.setTextColor(textColor); + // 更新图表 + lineChart.invalidate(); + } + + /** + * 根据当前所处时间获取当前模板下标 + */ + private int getI(int hour) { + for (int i = 0; i < modes.size(); i++) { + hour -= modes.get(i).getExpectedHours(); + if (hour < 0) { + return i; + } + } + //时间大于全部时间 + return -1; + } + + /** + * 根据当前所处时间获取当前龄期的第几天 + */ + private int getDay() { + int hour = AIModelSet.getStartToNowHours(); + for (int i = 0; i < modes.size(); i++) { + hour -= modes.get(i).getExpectedHours(); + if (hour < 0) { + return (int) (modes.get(i).getExpectedHours() + hour) / 24 + 1; + } + } + //时间大于全部时间 + return -1; + } + + /** + * 刷新全部显示信息 + */ + public void refreshInfo() { + modes = MyAIModeUtil.getMyAiMode(); + int hourTotal = AIModelSet.getStartToNowHours(); + age = getI(hourTotal); + day = getDay(); + if (age < 0) { + // 当前在模板中的时间已经超出模板范围 + return; + } + modes = MyAIModeUtil.getMyAiMode(); + mode = modes.get(age); + SilkwormNew silkworm = SilkwormDBManager.getLatestSilkworm(); + binding.tvRoomName.setText((RoomSetting.getType() == 4 ? "催青" : "共育") + "第"); + binding.tvInstar.setText(mode.getName() + "第" + day + "天"); + binding.tvDay.setText("" + SilkwormDBManager.getStartToNowDays()); + binding.tvSeason.setText(silkworm.getName()); + binding.tvVariety.setText(silkworm.getVariety()); + binding.tvMode.setText(AIModelSet.getAiSchemeName()); + binding.tvTemperature.setText(mode.getTargetTemp() + "\t℃"); + binding.tvHumidity.setText(mode.getTargetHumi() + "\t%"); + binding.tvVentilator.setText("启" + mode.getTargetVentOpen() + "停" + mode.getTargetVentClose()); + AIModelSet.setTargetTemperature(mode.getTargetTemp()); + AIModelSet.setAiTargetHumidity(mode.getTargetHumi()); + AIModelSet.setAiCycleStart(mode.getTargetVentOpen()); + AIModelSet.setAiCycleStop(mode.getTargetVentClose()); + LimitLine limitLine = new LimitLine(AIModelSet.getStartToNowHours() + MARGIN, "★"); + limitLine.setLineColor(Color.parseColor("#FF8D28")); + limitLine.setLineWidth(2f); + limitLine.setTextSize(50f); + limitLine.setTextColor(Color.parseColor("#FF8D28")); + List dataSets = new ArrayList<>(); + LineDataSet temperatureData = generateTemperatureData(modes); + LineDataSet humidityData = generateHumidityData(modes); + temperatureData.setValueTextColor(RoomSetting.isDarkTheme() ? Color.WHITE : Color.BLACK); + humidityData.setValueTextColor(RoomSetting.isDarkTheme() ? Color.WHITE : Color.BLACK); + dataSets.add(temperatureData); + dataSets.add(humidityData); + XAxis xAxis = binding.chart.getXAxis(); + xAxis.removeAllLimitLines(); + xAxis.addLimitLine(limitLine); + xAxis.setAxisMaximum(Math.max(temperatureData.getXMax(), humidityData.getXMax()) + MARGIN); + xAxis.setAxisMinimum(Math.min(temperatureData.getXMin(), humidityData.getXMin()) - MARGIN); + binding.chart.setData(new LineData(dataSets)); + binding.chart.invalidate(); + } + + /** + * 获取温度曲线数据 + */ + private LineDataSet generateTemperatureData(List modes) { + ArrayList entries = new ArrayList<>(); + float x = MARGIN; + float y = 0; + for (AITHMode mode : modes) { + y = (float) mode.getTargetTemp(); + entries.add(new Entry(x, y)); + x += mode.getExpectedHours(); + } + entries.add(new Entry(x, y)); + LineDataSet set = new LineDataSet(entries, "温度(°C) "); + set.setColor(Color.parseColor("#00D262")); + set.setLineWidth(2f); + set.setCircleColor(Color.parseColor("#00D262")); + set.setCircleRadius(5f); + set.setFillColor(Color.parseColor("#00D262")); + set.setMode(LineDataSet.Mode.STEPPED); + set.setDrawValues(true); + set.setAxisDependency(YAxis.AxisDependency.LEFT); + set.setValueTextSize(22); + set.setValueTextColor(Color.parseColor("#424242")); + return set; + } + + /** + * 获取湿度曲线数据 + */ + private LineDataSet generateHumidityData(List modes) { + ArrayList entries = new ArrayList<>(); + float x = MARGIN; + float y = 0; + for (AITHMode mode : modes) { + y = (float) mode.getTargetHumi(); + entries.add(new Entry(x, y)); + x += mode.getExpectedHours(); + } + entries.add(new Entry(x, y)); + LineDataSet set = new LineDataSet(entries, "湿度(%)"); + set.setColor(Color.parseColor("#00A1FF")); + set.setLineWidth(2f); + set.setCircleColor(Color.parseColor("#00A1FF")); + set.setCircleRadius(5f); + set.setFillColor(Color.parseColor("#00A1FF")); + set.setMode(LineDataSet.Mode.STEPPED); + set.setDrawValues(true); + set.setAxisDependency(YAxis.AxisDependency.RIGHT); + set.setValueTextSize(22); + set.setValueTextColor(Color.parseColor("#424242")); + return set; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragmentPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragmentPresenter.java new file mode 100644 index 0000000..eeb8886 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/IntelligenceFragmentPresenter.java @@ -0,0 +1,184 @@ +package com.example.iot_controlhost.ui.fragment; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.AITHMode; +import com.example.iot_controlhost.model.SingleLiveEvent; +import com.example.iot_controlhost.model.net.AIMode; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.control.disinfect.DisinfectController; +import com.example.iot_controlhost.utils.control.humidity.HumidityController; +import com.example.iot_controlhost.utils.control.temperature.TemperatureController; +import com.example.iot_controlhost.utils.control.temperature.strategy.GearControl; +import com.example.iot_controlhost.utils.control.ventilator.VentilatorController; +import com.example.iot_controlhost.utils.database.AITHModeDBManager; +import com.example.iot_controlhost.utils.database.MyAIModeUtil; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.List; + +public class IntelligenceFragmentPresenter extends BasePresenter { + + private MutableLiveData noticeLiveData = new SingleLiveEvent<>(); + + public MutableLiveData getNoticeLiveData() { + return noticeLiveData; + } + + public IntelligenceFragmentPresenter(@NonNull Application application) { + super(application); + RxBusUtils.get().onMainThread(RxTag.AI_INFO, Integer.class, i -> notice(7)); + } + + /** + * 开始硬件操作 + * 能调用这个方法说明已经在共育中,所以不需要判断 + */ + public void startAICultivate() { + showLoadingDialog("正在刷新模式中,请稍后..."); + if (MyAIModeUtil.getMyAiMode().size() > 0) { + //有模板 + initAIMode(); + } else { + //正在共育,但是没有模板,重新获取 + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getSilkwormTHTemplate); + params.addParameter("intelligentControlProgramId", AIModelSet.getAiControlProgramId()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(AIMode model) { + AIMode.DataDTO data = model.getData(); + MMKVUtil.put(AIModelSet.AI_SCHEME_NAME, data.getProgramName()); + List modes = data.getItems(); + if (modes.size() > 0) { + AITHModeDBManager.insert(modes); + MyAIModeUtil.initMyAiMode(AITHModeDBManager.getAllSilkworms()); + initAIMode(); + } else { + successTips("该批次该蚕种暂无"+(RoomSetting.getType() == 4 ? "催青" : "共育")+"计划"); + } + } + + @Override + public void error(Throwable e) { + e.printStackTrace(); + errorTips("获取模板失败,请检查网络:" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + hideLoadingDialog(); + } + }); + } + } + + /** + * 在有模板的前提下初始化 + */ + private void initAIMode() { + showLoadingDialog("正在加载模板中"); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask() { + @Override + public Object doInQueueThread() throws Exception { + //智能模式温控使用“分档位控制”算法 + TemperatureController.getInstance(new GearControl()).start(); + HumidityController.getInstance().start(); + VentilatorController.getInstance().start(); + DisinfectController.getInstance().start(); + // 刷新当前显示信息 + pollingTask.startPollingTaskOnIOThread(RxTag.AI_CULTIVATE, 2 * 60 * 60, () -> { + notice(7); + }); +// DisposablePool.get().remove(RxTag.AI_CULTIVATE); +// DisposablePool.get().add(RxTag.AI_CULTIVATE, RxJavaUtils.polling(2 * 60 * 60, aLong -> notice(7))); + return null; + } + + @Override + public void doInUIThread(Object o) { + hideLoadingDialog(); + } + }); + } + + /** + * 停止智能控制 + */ + public void stopIntelligenceControl() { + TemperatureController.getInstance().stop(); + HumidityController.getInstance().stop(); + VentilatorController.getInstance().stop(); + DisinfectController.getInstance().stop(); + } + + /** + * 开始/暂停共育 + */ + public void startStopCultivate() { + if (AIModelSet.isAiProcessing()) { + //要停止 + noticeLiveData.setValue(1); + } else { + //要进行 + startAICultivate(); + } + MMKVUtil.put(AIModelSet.AI_PROCESSING, !AIModelSet.isAiProcessing()); + noticeLiveData.setValue(8); + } + + public void notice(int type) { + noticeLiveData.postValue(type); + } + + /** + * 刷新模板 + */ + public void refreshTemplate() { + showLoadingDialog("正在下载最新模板,请稍等..."); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getSilkwormTHTemplate); + params.addParameter("intelligentControlProgramId", AIModelSet.getAiControlProgramId()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(AIMode model) { + AIMode.DataDTO data = model.getData(); + MMKVUtil.put(AIModelSet.AI_SCHEME_NAME, data.getProgramName()); + List modes = data.getItems(); + AITHModeDBManager.insert(modes); + MyAIModeUtil.initMyAiMode(AITHModeDBManager.getAllSilkworms()); + MMKVUtil.put(AIModelSet.AI_BIAS_TIME, 0); + notice(7); + } + + @Override + public void error(Throwable e) { + e.printStackTrace(); + errorTips("模板获取失败,错误信息:\n" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + hideLoadingDialog(); + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/ManualFragment.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/ManualFragment.java new file mode 100644 index 0000000..6521759 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/ManualFragment.java @@ -0,0 +1,154 @@ +package com.example.iot_controlhost.ui.fragment; + +import android.graphics.Color; +import android.view.View; + +import androidx.recyclerview.widget.GridLayoutManager; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.ManualControllerAdapter; +import com.example.iot_controlhost.base.BaseFragment; +import com.example.iot_controlhost.base.BaseMode; +import com.example.iot_controlhost.databinding.FragmentModelManualBinding; +import com.example.iot_controlhost.databinding.ItemControllerBinding; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.ui.dialog.AirDialog; +import com.example.iot_controlhost.ui.dialog.FloorDialog; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.UserLog; + +import java.util.List; + +import es.dmoral.toasty.Toasty; + +/** + * 手动模式 + */ +public class ManualFragment extends BaseFragment implements BaseMode { + private static ManualControllerAdapter manualControllerAdapter; +// private static ManualFragment INSTANCE = new ManualFragment(); + Floor floor; + +// public ManualFragment() { +// } + +// public static ManualFragment getInstance() { +// return INSTANCE; +// } + + @Override + public void init() { + floor = RoomController.floor; + binding.setVm(presenter); + presenter.getRefreshRelay().observe(this, unused -> refreshManualView()); + // 初始化继电器开关设备 + manualControllerAdapter = new ManualControllerAdapter(mContext, null, ItemControllerBinding.class); + binding.recycleView.setAdapter(manualControllerAdapter); + binding.recycleView.setLayoutManager(new GridLayoutManager(mContext, 3)); + List devices = RoomController.getAllControlRelays(); + int size = devices.size(); + if (size < 6) { + for (int i = 0; i < 6 - size; i++) { + devices.add(new Relay("未配置")); + } + } + //初始化控制器显示 + manualControllerAdapter.setDataList(devices); + if (RoomSetting.getMode() == 0) { + initMode(); + } + } + + @Override + public void initListener() { + super.initListener(); + binding.cardFloor.setOnClickListener(v -> { + if (!RoomSensor.dynamicFloorTSensorList.isEmpty() && RoomSensor.getFloorTemperature() > AutoModelSet.getAutoFlorMaxTemp()) { + Toasty.info(mContext, "当前地面温度已超38℃,请注意使用").show(); + } + new FloorDialog(mContext).show(); + }); + binding.cardAir.setOnClickListener(v -> new AirDialog(mContext,MyInfraredUtils.getInstances()).show()); + } + + @Override + public void initMode() { + UserLog.operate("进入手动模式"); + refreshManualView(); + } + + @Override + public void stopMode() { + UserLog.operate("退出手动模式:关闭所有设备"); + RoomController.closeAllControllers(); + } + + /** + * 刷新手动视图 + */ + private void refreshManualView() { + //初始化空调地暖视图 + changeFloorState(); + changeAirState(); + //初始化手动控制器显示 + manualControllerAdapter.notifyDataSetChanged(); + } + + private void changeFloorState() { + binding.tvFloor.setText(RoomSetting.getFloorName()); + binding.tvStateFloor.setText(floor.getState()); + if (floor.isPowerSupply()) { + binding.ivControllerFloor.setImageResource(R.mipmap.floor_open); + binding.cardFloor.setBackgroundColor(Color.parseColor("#FF9C00")); + binding.tvStateFloor.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvFloor.setTextColor(Color.parseColor("#FFFFFF")); + binding.ivPowerFloor.setBackground(mContext.getDrawable(R.drawable.bg_controller_open)); + binding.ivPowerFloor.setImageDrawable(mContext.getDrawable(R.mipmap.switch_on)); + binding.tvPowerFloor.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvFloorT.setTextColor(Color.parseColor("#FFFFFF")); + } else { + binding.ivControllerFloor.setImageResource(R.mipmap.floor_close); + binding.cardFloor.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33DCE3E3" : "#F2F0F1")); + binding.tvStateFloor.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.ivPowerFloor.setBackground(mContext.getDrawable(R.drawable.bg_controller_close)); + binding.ivPowerFloor.setImageDrawable(mContext.getDrawable(R.mipmap.switch_off)); + binding.tvFloor.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvPowerFloor.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvFloorT.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + } + binding.tvPowerFloor.setVisibility(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) ? View.VISIBLE : View.GONE); + binding.tvPowerFloor.setText("实时功率:" + RoomController.relayFloor.getPower() + "w"); + } + + private void changeAirState() { + AirConditionInfrared air = MyInfraredUtils.getInstances(); + binding.tvStateAir.setText(air.getState()); + if (air.isPowerSupply()) { + binding.ivControllerAir.setImageResource(R.mipmap.air_open); + binding.cardAir.setBackgroundColor(Color.parseColor(air.getMode() == AirConditionInfrared.HEAT ? "#FF9C00" : + air.getMode() == AirConditionInfrared.COLD ? "#00A1FF" : "#7DFA5B")); + binding.tvStateAir.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvAir.setTextColor(Color.parseColor("#FFFFFF")); + binding.tvPowerAir.setTextColor(Color.parseColor("#FFFFFF")); + binding.ivPowerAir.setBackground(mContext.getDrawable(R.drawable.bg_controller_open)); + binding.ivPowerAir.setImageDrawable(mContext.getDrawable(R.mipmap.switch_on)); + } else { + binding.ivControllerAir.setImageResource(R.mipmap.air_close); + binding.cardAir.setBackgroundColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#33DCE3E3" : "#F2F0F1")); + binding.tvStateAir.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvAir.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.tvPowerAir.setTextColor(Color.parseColor(RoomSetting.isDarkTheme() ? "#FFFFFF" : "#3A3A3A")); + binding.ivPowerAir.setBackground(mContext.getDrawable(R.drawable.bg_controller_close)); + binding.ivPowerAir.setImageDrawable(mContext.getDrawable(R.mipmap.switch_off)); + } + binding.tvPowerAir.setVisibility(RoomController.relay.getModel().equals(RoomController.relay.getModels()[2]) ? View.VISIBLE : View.GONE); + binding.tvPowerAir.setText("实时功率:" + RoomController.airCondition.getPower() + "w"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/fragment/ManualFragmentPresenter.java b/app/src/main/java/com/example/iot_controlhost/ui/fragment/ManualFragmentPresenter.java new file mode 100644 index 0000000..ecbef9d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/fragment/ManualFragmentPresenter.java @@ -0,0 +1,43 @@ +package com.example.iot_controlhost.ui.fragment; + + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RxTag; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +public class ManualFragmentPresenter extends BasePresenter { + public MutableLiveData refreshRelay = new MutableLiveData<>(); + public MutableLiveData floorT = new MutableLiveData<>(); + + public MutableLiveData getRefreshRelay() { + return refreshRelay; + } + + public ManualFragmentPresenter(@NonNull Application application) { + super(application); + //更新界面UI + RxBusUtils.get().onMainThread(RxTag.UPDATE_MANUAL, Integer.class, controlCommand -> refreshRelay.setValue(null)); + //更新数据 + if (!RoomSensor.dynamicFloorTSensorList.isEmpty() || RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + pollingTask.startPollingTaskOnIOThread(RxTag.MANUAL_UI, 5, () -> { + if (!RoomSensor.dynamicFloorTSensorList.isEmpty()) { + floorT.postValue(RoomSensor.getFloorTemperature() + "℃"); + } + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + refreshRelay.postValue(null); + } + }); + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/view/CircleOverlay.java b/app/src/main/java/com/example/iot_controlhost/ui/view/CircleOverlay.java new file mode 100644 index 0000000..b95e7f8 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/view/CircleOverlay.java @@ -0,0 +1,59 @@ +package com.example.iot_controlhost.ui.view; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.util.AttributeSet; +import android.view.SurfaceView; + +/** + * 带圆形遮罩的相机预览框 + */ +public class CircleOverlay extends SurfaceView { + /** + * 半价范围 + * (1-3为合适范围) + * 数字越大显示的范围越小 + */ + public static final int FACE_RADIUS = 3; + private Paint mPaint; + + public CircleOverlay(Context context) { + super(context); + init(); + } + + public CircleOverlay(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CircleOverlay(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setWillNotDraw(false); // 确保可以绘制内容 + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAlpha(0); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int width = getWidth(); + int height = getHeight(); + int radius = Math.min(width, height) / CircleOverlay.FACE_RADIUS; + + // 绘制白色遮罩层 + canvas.drawARGB(250, 255, 255, 255); + // 绘制透明圆形 + canvas.drawCircle(width / 2f, height / 2f, radius, mPaint); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/view/CustomSpinnerAdapter.java b/app/src/main/java/com/example/iot_controlhost/ui/view/CustomSpinnerAdapter.java new file mode 100644 index 0000000..dfe5d2d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/view/CustomSpinnerAdapter.java @@ -0,0 +1,41 @@ +package com.example.iot_controlhost.ui.view; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + + +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.R; + +/** + * 自定义下拉框适配器 + */ +public class CustomSpinnerAdapter extends ArrayAdapter { + + public CustomSpinnerAdapter(Context context, String [] items) { + super(context, R.layout.item_list, items); + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + View view = convertView; + if (view == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(R.layout.item_list, parent, false); + } + + // 获取布局中的TextView和ImageView + TextView textView = view.findViewById(R.id.tv); + + // 设置TextView和ImageView的内容 + textView.setText(getItem(position)); + + return view; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/ui/view/OnPasswordEnteredListener.java b/app/src/main/java/com/example/iot_controlhost/ui/view/OnPasswordEnteredListener.java new file mode 100644 index 0000000..b86501d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/view/OnPasswordEnteredListener.java @@ -0,0 +1,11 @@ +package com.example.iot_controlhost.ui.view; + +import com.google.android.material.textfield.TextInputEditText; + +/** + * 密码输入框用 + * 在输入密码校对通过后的事件执行 + */ +public interface OnPasswordEnteredListener { + void listener(TextInputEditText inputEditText); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/ui/viewmodel/AppBarViewModel.java b/app/src/main/java/com/example/iot_controlhost/ui/viewmodel/AppBarViewModel.java new file mode 100644 index 0000000..178a0c4 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/ui/viewmodel/AppBarViewModel.java @@ -0,0 +1,365 @@ +package com.example.iot_controlhost.ui.viewmodel; + +import android.app.Application; +import android.app.Dialog; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.net.wifi.WifiManager; +import android.telephony.PhoneStateListener; +import android.telephony.SignalStrength; +import android.telephony.TelephonyManager; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.core.content.res.ResourcesCompat; +import androidx.lifecycle.MutableLiveData; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.base.BasePresenter; +import com.example.iot_controlhost.model.DeviceErrorInfo; +import com.example.iot_controlhost.model.SingleLiveEvent; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.net.RoomInfo; +import com.example.iot_controlhost.model.net.Weather; +import com.example.iot_controlhost.service.MQTTService; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.FrpUpdateUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.NetState; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xhinliang.lunarcalendar.LunarCalendar; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * @Description 顶部信息栏 + * @Author DuanKanji + * @CreateTime 2024年01月19日 14:41 + */ +public class AppBarViewModel extends BasePresenter { + public MutableLiveData isLight = new MutableLiveData<>(); + public MutableLiveData weatherStr = new MutableLiveData<>(); + public MutableLiveData dateStr = new MutableLiveData<>(); + public MutableLiveData logoType = new MutableLiveData<>(ResourcesCompat.getDrawable(getApplication().getResources(), R.mipmap.logo_gys, null)); + public MutableLiveData ivServer = new MutableLiveData<>(); + public MutableLiveData ivNetwork = new MutableLiveData<>(); + private Boolean pingBaiduAccess = true; + private NetworkStateListener networkStateListener; + private TelephonyManager mTelephonyManager; + + public MutableLiveData getIvServer() { + return ivServer; + } + + public MutableLiveData getIvNetwork() { + return ivNetwork; + } + + public MutableLiveData noticeLiveData = new SingleLiveEvent<>(); + public MutableLiveData weatherLiveData = new MutableLiveData<>(); + + public MutableLiveData getNoticeLiveData() { + return noticeLiveData; + } + + public MutableLiveData getWeatherLiveData() { + return weatherLiveData; + } + + public AppBarViewModel(@NonNull Application application) { + super(application); + refreshRoomInfo(); + //循环更新TopBar UI + pollingTask.startPollingTaskOnIOThread(RxTag.BAR_UI, 5, () -> { + Calendar calendar = Calendar.getInstance(); + LunarCalendar lunarCalender = LunarCalendar.obtainCalendar(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH)); + dateStr.postValue(lunarCalender.getLunarMonth() + "月" + lunarCalender.getLunarDay() + new SimpleDateFormat("\tMM/dd HH:mm").format(new Date())); + }); + //循环更新天气 6小时一更新 + pollingTask.startPollingTaskOnIOThread(RxTag.BAR_UI_WEATHER, 60 * 60, this::refreshWeather); + //监听更新TouBar UI + RxBusUtils.get().onMainThread(RxTag.UPDATE_MAIN, Integer.class, i -> { + switch (i) { + case 4 ->//刷新网络状态 + initNetIcon(); + } + }); + // 轮询Ping Baidu以检测网络真实可用性 2024年8月22日 15:12:06需求取消 +// pollingTask.startPollingTaskOnIOThread(RxTag.PING_BAIDU, 3 * 60, () -> { +// pingBaiduAccess = pingBaidu(); +// initNetIcon(); +// }); + networkStateListener = new NetworkStateListener(); + mTelephonyManager = (TelephonyManager) getApplication().getSystemService(Context.TELEPHONY_SERVICE); + mTelephonyManager.listen(networkStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); + isLight.setValue(!RoomSetting.isDarkTheme()); + RxBusUtils.get().onMainThread(RxTag.MAIN_WARNING, DeviceErrorInfo.class, info -> { + //弹窗 + Dialog dialog = errorInfoMap.get(info.getName()); + if (dialog != null) { + dialog.dismiss(); + } + if (!StringUtils.isEmpty(info.getErrorMessage())) { + //显示新弹窗 + dialog = new ConfirmDialog(activityContext, info.getName(), info.getErrorMessage(), null); + errorInfoMap.put(info.getName(), dialog); + } + //警示图标 + ImageView imageView = errorImageView.get(info.getName()); + if (imageView != null) { + linearLayout.removeView(imageView); + } + if (!StringUtils.isEmpty(info.getErrorMessage())) { + //显示 + imageView = getImageView(info); + linearLayout.addView(imageView); + errorImageView.put(info.getName(), imageView); + } + }); + } + + private boolean pingBaidu() { + try { + Process process = Runtime.getRuntime().exec("/system/bin/ping -c 1 www.baidu.com"); + int exitCode = process.waitFor(); + return exitCode == 0; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + private ImageView getImageView(DeviceErrorInfo info) { + ImageView iv = new ImageView(activityContext); + // 设置 ImageView 的布局参数,比如宽度、高度等 + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + // 设置 margin + layoutParams.setMargins(5, 0, 5, 0); + iv.setLayoutParams(layoutParams); + iv.setImageResource(info.getResId()); + iv.setOnClickListener(v -> new ConfirmDialog(activityContext, info.getName(), info.getErrorMessage(), null).show()); + return iv; + } + + private LinearLayout linearLayout; + + private Context activityContext; + + public void setMainActivityContext(Context mainActivityContext, LinearLayout linearLayout) { + this.activityContext = mainActivityContext; + this.linearLayout = linearLayout; + } + + public Map errorInfoMap = new HashMap<>(); + public Map errorImageView = new HashMap<>(); + + /** + * 刷新房间信息 + */ + public void refreshRoomInfo() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getInfo); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(RoomInfo model) { + MyLog.app("房间信息获取成功"); + RoomInfo.DataDTO data = model.getData(); + MMKVUtil.put(RoomSetting.USER_NAME, data.getOwnerName()); + MMKVUtil.put(RoomSetting.TYPE, data.getTypeCode()); + //更新TouBar logo + int id = R.mipmap.logo_blue; + switch (RoomSetting.getType()) { + case 1 -> id = R.mipmap.logo_gys;//共育室 + case 3 -> id = R.mipmap.logo_dcf;//大蚕房 + case 4 -> id = R.mipmap.logo_cqs;//催青室 + } + logoType.setValue(ResourcesCompat.getDrawable(getApplication().getResources(), id, null)); + //获取坐标 + if (!StringUtils.isEmpty(data.getLat()) && !StringUtils.isEmpty(data.getLon())) { + MMKVUtil.put(RoomSetting.ROOM_LATITUDE_LATITUDE, Double.parseDouble(data.getLat())); + MMKVUtil.put(RoomSetting.ROOM_COORDINATE_LONGITUDE, Double.parseDouble(data.getLon())); + } + } + + @Override + public void error(Throwable e) { + MyLog.appError("房间信息获取失败"); + } + + }); + } + + /** + * 双击最大时间间隔 + * 单位:毫秒 + */ + private static final long DOUBLE_CLICK_TIME_DELTA = 1000; + private long lastClickTime = 0; + + public void enterSetActivity() { + //进入配置界面 + long clickTime = System.currentTimeMillis(); + if (clickTime - lastClickTime < DOUBLE_CLICK_TIME_DELTA) { + // 双击事件 + lastClickTime = 0; + noticeLiveData.setValue(0); + } else { + lastClickTime = clickTime; + } + } + + public void showWeatherTips() { + weatherLiveData.setValue(weather); + } + + public void sosConform() { + noticeLiveData.setValue(2); + } + + /** + * SOS功能 + */ + public void sos(Context mContext) { + FrpUpdateUtil.check(mContext, true); + } + + /** + * 信号强度监听器 + */ + private class NetworkStateListener extends PhoneStateListener { + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + super.onSignalStrengthsChanged(signalStrength); + //获取网络信号强度 + int level = signalStrength.getLevel(); + if (level != NetState.Semaphore) { + MyLog.network("网络信号级别更新为:" + level); + } + NetState.Semaphore = level; + initNetIcon(); + } + } + + private void initNetIcon() { + int resId = R.mipmap.network_0; + int ivNetServerId = MQTTService.getMQTTConnect() ? R.mipmap.server_1 : R.mipmap.server_0; + int netType = NetState.getNetworkType(getApplication()); + switch (netType) { + case 0 -> { + resId = R.mipmap.wifi_0; + ivNetServerId = R.mipmap.server_0; + } + case 1 -> resId = R.mipmap.rj45; + case 2 -> { + WifiManager wifiManager = (WifiManager) getApplication().getSystemService(Context.WIFI_SERVICE); + NetState.Semaphore = WifiManager.calculateSignalLevel(wifiManager.getConnectionInfo().getRssi(), 5); + switch (NetState.Semaphore) { + case 0 -> resId = R.mipmap.wifi_0; +// case 0 -> resId = R.drawable.wifi_1; + case 1 -> resId = R.mipmap.wifi_2; + case 2 -> resId = R.mipmap.wifi_3; + case 3 -> resId = R.mipmap.wifi_4; + case 4 -> resId = R.mipmap.wifi_5; + } + } + case 3 -> { + switch (NetState.Semaphore) { + case 0 -> resId = R.mipmap._4g_0; + case 1 -> resId = R.mipmap._4g_1; + case 2 -> resId = R.mipmap._4g_2; + case 3 -> resId = R.mipmap._4g_3; + case 4 -> resId = R.mipmap._4g_4; + } + } + } + ivServer.setValue(ivNetServerId); + ivNetwork.setValue(pingBaiduAccess ? resId : R.mipmap.network_0); + if (lastNetState != netType) { + //网络状态发生变化 + UploadHostInfo(); + lastNetState = netType; + } + } + + int lastNetState = -1; + + Weather weather; + + /** + * 获取天气信息 + */ + private void refreshWeather() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.weather); + params.addParameter("areacode", RoomSetting.getAreaCode()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(Weather model) { + MyLog.app("天气信息获取成功"); + weather = model; + weatherStr.postValue(model.getData().getCasts().get(0).getDayweather() + " " + model.getData().getCasts().get(0).getDaytemp() + "℃"); + } + + @Override + public void error(Throwable e) { + MyLog.appError("天气信息获取失败"); + } + + @Override + public void onFinish() { + super.onFinish(); + } + }); + } + + + /** + * 上传主机信息 + */ + private void UploadHostInfo() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.uploadHostInfo); + params.addParameter("HostCode", HardwareSetting.getHostId()); + params.addParameter("VersionNumber", MyUtil.getVersionId(getApplication())); + params.addParameter("VersionName", MyUtil.getVersionName(getApplication())); + params.addParameter("PlatformVersion", HardwareSetting.getDeviceModel()); + int type = 0; + //0:未定义,1:物联网卡4G,2:物联网卡5G,3:WIFI,4:RJ45 + switch (NetState.getNetworkType(getApplication())) { + case 0 -> type = 0; + case 1 -> type = 4; + case 2 -> type = 3; + case 3 -> type = 1; + } + params.addParameter("Network", type); + params.addParameter("IotCardCode", HardwareSetting.getImsi()); + params.addParameter("MAC", HardwareSetting.getImei()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.app("主机信息上传成功"); + } + + @Override + public void error(Throwable e) { + MyLog.appError("主机信息上传失败"); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/AppUpdateUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/AppUpdateUtil.java new file mode 100644 index 0000000..bf031f3 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/AppUpdateUtil.java @@ -0,0 +1,106 @@ +package com.example.iot_controlhost.utils; + +import android.content.Context; +import android.os.Environment; +import android.os.StrictMode; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.net.AppVersion; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.network.PublicNetRequest; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import es.dmoral.toasty.Toasty; + +/** + * App更新工具类 + * + * @author DuanKaiji + */ +public class AppUpdateUtil { + + /** + * 检查是否需要升级APP,以及进行升级 + * + * @param mContext 上下文 + * @param needSilentTips 是否需要静默提示 + * @param needForceDownLoad 是否需要直接下载(不询问用户) + */ + public static void updateAPP(Context mContext, boolean needSilentTips, boolean needForceDownLoad) { + if (!needSilentTips) { + DialogUtil.showLoadingDialog(mContext, "正在检测更新中"); + } + int curVersion = MyUtil.getVersion(mContext); + String curVersionName = MyUtil.getVersionName(mContext); + if (android.os.Build.VERSION.SDK_INT > 9) { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + } + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.checkAppNewVersion); + params.addParameter("HostCode", HardwareSetting.getHostId()); + params.addParameter("VersionNumber", curVersion); + params.addParameter("VersionName", curVersionName); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(AppVersion model) { + if (model.getData() != null) { + int server = model.getData().getVersionNumber(); + if (server > curVersion) { + AppVersion.DataDTO data = model.getData(); + if (needForceDownLoad) { + PublicNetRequest.downFile(mContext, data.getUrl()); + } else { + new ConfirmDialog(mContext, "检测到新版本", "新版本内容:\n" + data.getVersionDescription(), () -> { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + PublicNetRequest.downFile(mContext, data.getUrl()); + } else { + Toasty.error(mContext, "SD卡不可用,请插入SD卡").show(); + } + }).show(); + } + if (!needSilentTips) { + Toasty.success(mContext, "检测到新版本").show(); + } + } else { + if (!needSilentTips) { + Toasty.success(mContext, "当前已是最新版本").show(); + } + } + } else if (!StringUtils.isEmpty(model.getMessage())) { + if (!needSilentTips) { + Toasty.success(mContext, model.getMessage()).show(); + } + } + } + + @Override + public void onSuccessError(CommonResponse data) { + if (!needSilentTips) { + Toasty.success(mContext, data.getMessage()).show(); + } + } + + @Override + public void error(Throwable e) { + if (!needSilentTips) { + Toasty.error(mContext, "检测失败:" + e.getMessage()).show(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + if (!needSilentTips) { + DialogUtil.hideLoadingDialog(); + } + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/CsvConverter.java b/app/src/main/java/com/example/iot_controlhost/utils/CsvConverter.java new file mode 100644 index 0000000..dc4c067 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/CsvConverter.java @@ -0,0 +1,180 @@ +package com.example.iot_controlhost.utils; + +import android.os.Environment; + +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomSensor; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * 将传感器数据保存为csv文件 + * + * @Author: DuanKaiji + * @Date: 2023年12月1日 17:18:43 + */ +public class CsvConverter { + public static void saveSensorDataToCsv() { + List sensorList = RoomSensor.getAllAvailableSensors(); + + for (Sensor sensor : sensorList) { +// writeToCsvFile(sensor.getHistoryData()); + } + } + + private static void writeToCsvFile(List sensors) { + try { + if (sensors.isEmpty()) { + return; + } + Sensor sensor = sensors.get(0); + //将通用信息作为文件名 + String fileName = sensor.getName() + "型号:" + sensor.getModel() + + "_截至:" + MyUtil.getDateDay() + ".csv"; + File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + File file = new File(downloadDir, fileName); + FileWriter writer = new FileWriter(file, false); + + List allFields = new ArrayList<>(); + // 1.获取自己类的属性 + Field[] fields = sensor.getClass().getDeclaredFields(); + allFields.addAll(Arrays.asList(fields)); + + // 手动选择要保留的传感器父类属性 + List sensorFieldsToKeep = Arrays.asList("lastRecordTime"); + // 2.获取父类的属性 + Field[] superClassFields = sensor.getClass().getSuperclass().getDeclaredFields(); + for (Field field : superClassFields) { + if (sensorFieldsToKeep.contains(field.getName())) { + allFields.add(field); + } + } + // 手动选择要保留的设备祖父类属性 + List deviceFieldsToKeep = Arrays.asList("failTimes"); + // 3.获取祖父类的属性 + Class grandParentClass = sensor.getClass().getSuperclass().getSuperclass(); + Field[] grandParentFields = grandParentClass.getDeclaredFields(); + for (Field field : grandParentFields) { + if (deviceFieldsToKeep.contains(field.getName())) { + allFields.add(field); + } + } + // 写入表头 + StringBuilder header = new StringBuilder(); + for (Field field : allFields) { + header.append(field.getName()).append(", "); + } + header.delete(header.length() - 2, header.length()); + header.append("\n"); + writer.write(header.toString()); + + // 写入数据 + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + for (Sensor s : sensors) { + StringBuilder csvRow = new StringBuilder(); + for (Field field : allFields) { + field.setAccessible(true); + Object value = field.get(s); + if (value != null) { + // 如果是 lastRecordTime 属性,则将时间戳转换为时间字符串 + if (field.getName().equals("lastRecordTime") && value instanceof Long) { + long timestamp = (Long) value; + String formattedDate = dateFormat.format(new Date(timestamp)); + csvRow.append(formattedDate).append(", "); + } else { + csvRow.append(value).append(", "); + } + } else { + csvRow.append("null, "); + } + } + csvRow.delete(csvRow.length() - 2, csvRow.length()); + csvRow.append("\n"); + + writer.write(csvRow.toString()); + } + writer.flush(); + writer.close(); + } catch (IOException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + public static File writeLogToCsvFile(List logs) { + try { + if (logs.isEmpty()) { + return null; + } + Log log = logs.get(0); + //将通用信息作为文件名 + String fileName = HardwareSetting.getHostId() + "_log_截至" + MyUtil.getDateDay() + ".txt"; + File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + File file = new File(downloadDir, fileName); +// FileWriter writer = new FileWriter(file, false); + + FileOutputStream fos = new FileOutputStream(file); + OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8); + + List allFields = new ArrayList<>(); + // 获取自己类的属性 + Field[] fields = log.getClass().getDeclaredFields(); + allFields.addAll(Arrays.asList(fields)); + // 写入表头 + StringBuilder header = new StringBuilder(); + for (Field field : allFields) { + header.append(field.getName()).append(", "); + } + header.delete(header.length() - 2, header.length()); + header.append("\n"); + writer.write(header.toString()); + + // 写入数据 + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + for (Log s : logs) { + StringBuilder csvRow = new StringBuilder(); + for (Field field : allFields) { + field.setAccessible(true); + Object value = field.get(s); + if (value != null) { + // 如果是 datetime 属性,则将时间戳转换为时间字符串 + if (field.getName().equals("datetime") && value instanceof Long) { + long timestamp = (Long) value; + String formattedDate = dateFormat.format(new Date(timestamp)); + csvRow.append(formattedDate).append(", "); + } else { + csvRow.append(value).append(", "); + } + } else { + csvRow.append("null, "); + } + } + csvRow.delete(csvRow.length() - 2, csvRow.length()); + csvRow.append("\n"); + + writer.write(csvRow.toString()); + } + writer.flush(); + writer.close(); + return file; + } catch (IOException | IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/DataUtils.java b/app/src/main/java/com/example/iot_controlhost/utils/DataUtils.java new file mode 100644 index 0000000..e6e6173 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/DataUtils.java @@ -0,0 +1,75 @@ +package com.example.iot_controlhost.utils; + +import com.example.iot_controlhost.model.Device; + +public class DataUtils { + //------------------------------------------------------- + // 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数 + public static int isOdd(int num) { + return num & 1; + } + + //------------------------------------------------------- + //Hex字符串转byte + public static byte HexToByte(String inHex) { + try{ + return (byte) Integer.parseInt(inHex, 16); + } catch (Exception e) { + return 0; + } + } + + //------------------------------------------------------- + //1字节转2个Hex字符 + public static String Byte2Hex(Byte inByte) { + return String.format("%02x", new Object[]{inByte}).toUpperCase(); + } + + //------------------------------------------------------- + //转hex字符串转字节数组 + public static byte[] HexToByteArr(String inHex) { + byte[] result; + int hexlen = inHex.length(); + if (isOdd(hexlen) == 1) { + hexlen++; + result = new byte[(hexlen / 2)]; + inHex = "0" + inHex; + } else { + result = new byte[(hexlen / 2)]; + } + int j = 0; + for (int i = 0; i < hexlen; i += 2) { + result[j] = HexToByte(inHex.substring(i, i + 2)); + j++; + } + return result; + } + + //字节数组转转hex字符串 + public static String ByteArrToHex(byte[] inBytArr) { + StringBuilder strBuilder = new StringBuilder(); + for (byte valueOf : inBytArr) { + strBuilder.append(Byte2Hex(Byte.valueOf(valueOf))); + } + return strBuilder.toString(); + } + + + /** + * CRC校验 + */ + public static boolean CRC16Check(String[] strings) { + if (strings == null) { + return false; + } + String str = ""; + for (int i = 0; i < strings.length - 2; i++) { + str += strings[i]; + } + String crc = strings[strings.length - 2] + strings[strings.length - 1]; + String crc_ = Device.getCRC(str); + if (crc.equals(crc_)) return true; + else return false; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/DateUtils.java b/app/src/main/java/com/example/iot_controlhost/utils/DateUtils.java new file mode 100644 index 0000000..782f745 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/DateUtils.java @@ -0,0 +1,47 @@ +package com.example.iot_controlhost.utils; + +import java.util.Calendar; +import java.util.Date; + +public class DateUtils { + + /** + * 计算从给定日期到今天的天数,包含当天 + * + * @param date + * @return + */ + public static int daysFromTodayInclusive(Date date) { + if (date == null) { + throw new IllegalArgumentException("date cannot be null"); + } + + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + + cal1.setTime(date); + cal2.setTime(new Date()); + + resetTime(cal1); + resetTime(cal2); + + long diffMillis = cal2.getTimeInMillis() - cal1.getTimeInMillis(); + int days = (int) (diffMillis / (24 * 60 * 60 * 1000)); + + if (days < 0) { + // date在未来 + return days; + }else{ + // 时间在过去或今天 + return days + 1; + } + } + + + private static void resetTime(Calendar cal) { + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java new file mode 100644 index 0000000..6e776bc --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/DialogUtil.java @@ -0,0 +1,124 @@ +package com.example.iot_controlhost.utils; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.TextView; + +import com.example.iot_controlhost.R; +import com.google.android.material.progressindicator.LinearProgressIndicator; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +public class DialogUtil { + private static Dialog progressDialog; + private static TextView textViewInfo; + private static LinearProgressIndicator progressIndicator; + + /** + * 显示加载中弹窗,支持中途改变提示文字 + * + * @param context 上下文 + * @param info 提示信息 + */ + public static void showLoadingDialog(Context context, String info) { + if (progressDialog != null) { + progressDialog.dismiss(); // 如果之前有Dialog显示,则先关闭 + progressDialog = null; // 释放旧Dialog + } + progressDialog = new Dialog(context, R.style.MyDialogTheme); + progressDialog.setContentView(R.layout.dialog_loading); + progressDialog.setCancelable(false); + textViewInfo = progressDialog.findViewById(R.id.tv_loading); + textViewInfo.setText(info); + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.copyFrom(progressDialog.getWindow().getAttributes()); + layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.CENTER; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; + progressDialog.getWindow().setAttributes(layoutParams); + progressDialog.show(); + } + + /** + * 显示加载中弹窗 + * 在IO线程中使用 + * + * @param context 上下文 + * @param info 提示信息 + */ + public static void showLoadingDialogInIO(Context context, String info) { + RxJavaUtils.doInUIThread(new RxUITask<>(null) { + @Override + public void doInUIThread(Object o) { + showLoadingDialog(context, info); + } + }); + } + + /** + * 关闭加载中弹窗 + * 假设都在UI线程调用 + */ + public static void hideLoadingDialog() { + if (progressDialog != null) { + progressDialog.dismiss(); + } + } + + /** + * 显示进度条弹窗 + * + * @param context 上下文 + * @param info 提示信息 + */ + public static void showProgressIndicatorDialog(Context context, String info) { + progressDialog = new Dialog(context, R.style.MyDialogTheme); + progressDialog.setContentView(R.layout.dialog_process_indicator); + progressDialog.setCancelable(false); + textViewInfo = progressDialog.findViewById(R.id.tv_loading); + progressIndicator = progressDialog.findViewById(R.id.progress_indicator); + // 设置对话框位置 + Window window = progressDialog.getWindow(); + if (window != null) { + // 设置对话框位置为屏幕顶部 + window.setGravity(Gravity.CENTER); + WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; + layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(layoutParams); + + } + textViewInfo.setText(info); + progressDialog.show(); + } + + /** + * 修改进度条弹窗 + * + * @param info 提示信息 + * @param progress 进度 + */ + public static void setProgressIndicatorDialog(String info, int progress) { + textViewInfo = progressDialog.findViewById(R.id.tv_loading); + progressIndicator = progressDialog.findViewById(R.id.progress_indicator); + textViewInfo.setText(info); + progressIndicator.setProgress(progress, true); + } + + /** + * 关闭进度条弹窗 + */ + public static void hideProgressIndicatorDialog() { + if (progressDialog != null) { + progressDialog.dismiss(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/FrpDataTemp.java b/app/src/main/java/com/example/iot_controlhost/utils/FrpDataTemp.java new file mode 100644 index 0000000..8fa64aa --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/FrpDataTemp.java @@ -0,0 +1,41 @@ +package com.example.iot_controlhost.utils; + +import com.blankj.utilcode.util.SPUtils; + +/** + * 配置 + */ +public class FrpDataTemp { + /** + * FRP服务器-IP + */ + public static final String SERVER_IP = "SERVER_IP"; + /** + * FRP服务器-端口 + */ + public static final String SERVER_PORT = "SERVER_PORT"; + /** + * FRP客户端-名称 + */ + public static final String CLIENT_NAME = "CLIENT_NAME"; + /** + * FRP客户端-端口 + */ + public static final String CLIENT_PORT = "CLIENT_PORT"; + + public static String getServerIp() { + return MMKVUtil.get(SERVER_IP);//171.212.101.201 + } + + public static String getServerPort() { + return MMKVUtil.get(SERVER_PORT);//65533 + } + + public static String getClientName(){ + return MMKVUtil.get(CLIENT_NAME); + } + + public static String getClientPort() { + return MMKVUtil.get(CLIENT_PORT);//6001 + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/FrpUpdateUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/FrpUpdateUtil.java new file mode 100644 index 0000000..3e8d3fc --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/FrpUpdateUtil.java @@ -0,0 +1,117 @@ +package com.example.iot_controlhost.utils; + +import android.content.Context; +import android.content.Intent; +import android.os.Environment; + +import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.model.net.FrpConfig; +import com.example.iot_controlhost.model.net.FrpNewVersion; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.network.PublicNetRequest; +import com.example.iot_controlhost.utils.network.Url; +import com.example.iot_controlhost.utils.network.XHttpManager; +import com.example.iot_controlhost.utils.network.XHttpShorthand; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import es.dmoral.toasty.Toasty; +import io.reactivex.functions.Consumer; + +/** + * App更新工具类 + * + * @author DuanKaiji + */ +public class FrpUpdateUtil { + + /** + * 检查是否下载 + * + * @param mContext 上下文 + * @param needNotice 是否需要提示 + */ + public static void check(Context mContext, boolean needNotice) { + if (needNotice) { + DialogUtil.showLoadingDialog(mContext, "正在请求中"); + } + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.sos); + params.addParameter("HostCode", HardwareSetting.getHostId()); + params.addParameter("VersionNumber", "0"); + params.addParameter("VersionName", "0"); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(FrpNewVersion model) { + Intent intent = mContext.getPackageManager().getLaunchIntentForPackage("com.bbitcn.bbit_frp2"); + boolean needUpdate = false; + if (intent == null) { + //启动失败,下载新版本 + needUpdate = true; + } else { + if (StringUtils.isEmpty(model.getData().getUrl())) { + if (needNotice) { + Toasty.error(mContext, "未获取到下载地址,请联系管理员分配版本").show(); + } + } else { + if (Integer.parseInt(model.getData().getVersionNumber()) > HardwareSetting.getFrpVersion()) { + needUpdate = true; + } else { + Toasty.success(mContext, "即将启动远程协助软件").show(); + MyUtil.relaunchFrp(); + } + } + } + if (needUpdate) { + if (StringUtils.isEmpty(model.getData().getUrl())) { + if (needNotice) { + Toasty.error(mContext, "未获取到下载地址,请联系管理员分配版本").show(); + } + } else { + if (HardwareSetting.isZCAndroid()) { + new ConfirmDialog(mContext, "提示", "未安装最新版远程协助软件\n是否立即下载安装?", + () -> down(mContext, model.getData().getUrl())).show(); + } else { + //直接下载 + down(mContext, model.getData().getUrl()); + } + } + } + } + + @Override + public void onSuccessError(CommonResponse data) { + if (needNotice) { + Toasty.error(mContext, data.getMessage()).show(); + } + } + + @Override + public void error(Throwable e) { + if (needNotice) { + Toasty.error(mContext, "检测失败:" + e.getMessage()).show(); + } + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + public static void down(Context mContext, String url) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + PublicNetRequest.downFile(mContext, url); + } else { + Toasty.error(mContext, "SD卡不可用,请插入SD卡").show(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/MMKVUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/MMKVUtil.java new file mode 100644 index 0000000..31d91fa --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/MMKVUtil.java @@ -0,0 +1,137 @@ +package com.example.iot_controlhost.utils; + + +import android.os.Parcelable; + +import com.blankj.utilcode.util.GsonUtils; +import com.example.iot_controlhost.MyApp; +import com.tencent.mmkv.MMKV; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * MMKV工具类 + */ +public class MMKVUtil { + private static MMKVUtil INSTANCE; + private static MMKV mmkvInstance; + + private MMKVUtil() { + } + + /** + * 初始化MMKV,只能在Application中初始化一次 + */ + public static void init() { + INSTANCE = new MMKVUtil(); + MMKV.initialize(MyApp.getAppContext()); + mmkvInstance = MMKV.defaultMMKV(); + } + + public static MMKVUtil get() { + return INSTANCE; + } + + /** + * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法 + */ + public static void put(String key, T object) { + if (object instanceof String) { + mmkvInstance.encode(key, (String) object); + } else if (object instanceof Integer) { + mmkvInstance.encode(key, (Integer) object); + } else if (object instanceof Boolean) { + mmkvInstance.encode(key, (Boolean) object); + } else if (object instanceof Float) { + mmkvInstance.encode(key, (Float) object); + } else if (object instanceof Long) { + mmkvInstance.encode(key, (Long) object); + } else if (object instanceof Double) { + mmkvInstance.encode(key, (Double) object); + } else if (object instanceof Set) { + mmkvInstance.encode(key, (Set) object); + } else if (object instanceof Parcelable) { + mmkvInstance.encode(key, (Parcelable) object); + } else { + mmkvInstance.encode(key, object == null ? "" : object.toString()); + } + } + + /** + * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值 + */ + public static T get(String key, T defaultObject) { + if (defaultObject instanceof String || defaultObject == null) { + return (T) mmkvInstance.decodeString(key, (String) defaultObject); + } else if (defaultObject instanceof Integer) { + return (T) Integer.valueOf(mmkvInstance.decodeInt(key, (Integer) defaultObject)); + } else if (defaultObject instanceof Boolean) { + return (T) Boolean.valueOf(mmkvInstance.decodeBool(key, (Boolean) defaultObject)); + } else if (defaultObject instanceof Float) { + return (T) Float.valueOf(mmkvInstance.decodeFloat(key, (Float) defaultObject)); + } else if (defaultObject instanceof Long) { + return (T) Long.valueOf(mmkvInstance.decodeLong(key, (Long) defaultObject)); + } else if (defaultObject instanceof Double) { + return (T) Double.valueOf(mmkvInstance.decodeDouble(key, (Double) defaultObject)); + } else if (defaultObject instanceof Parcelable) { + Parcelable p = (Parcelable) defaultObject; + return (T) mmkvInstance.decodeParcelable(key, p.getClass()); + } else { + return null; + } + } + + /** + * 获得字符串 + */ + public static String get(String key) { + return mmkvInstance.decodeString(key, ""); + } + + /** + * 移除某个key值已经对应的值 + */ + public static void remove(String key) { + mmkvInstance.remove(key); + } + + /** + * 查询某个key是否已经存在 + */ + public boolean contains(String key) { + return mmkvInstance.contains(key); + } + + /** + * 清除所有数据 + */ + public static void clear() { + mmkvInstance.clear(); + } + + /** + * 获取所有key-value的json字符串 + */ + public static String getAllKeyValues() { + Map result = new HashMap<>(); + String[] keys = mmkvInstance.allKeys(); + if (keys == null || keys.length == 0) { + return ""; + } + for (String key : keys) { + if (key.contains("HISTORY") || key.contains("user") || key.contains("password")) { + continue; + } + //将所有的key-value转换成json字符串 + result.put(key, mmkvInstance.decodeString(key, "")); + //解决boolean、int等基本类型无法转换成json字符串的问题 + if (result.get(key) == null) { + result.put(key, mmkvInstance.decodeInt(key, 0)); + } + } + return GsonUtils.toJson(result); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/MyQueue.java b/app/src/main/java/com/example/iot_controlhost/utils/MyQueue.java new file mode 100644 index 0000000..7cbca2f --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/MyQueue.java @@ -0,0 +1,119 @@ +package com.example.iot_controlhost.utils; + +import com.example.iot_controlhost.base.BaseQueue; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.model.controller.Controller; + +import java.util.Comparator; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.PriorityBlockingQueue; + +import android_serialport_api.SerialPortUtil; + +/** + * 单线程队列 + * 用于控制器与传感器执行指令用 + */ +public class MyQueue { + private ExecutorService executorService; + private BlockingQueue taskQueue; + private static MyQueue controllerInstance; + private static MyQueue sensorInstance; + + /** + * 串口通信类别-控制器 + */ + public final static String TYPE_CONTROLLER = "TYPE_CONTROLLER"; + /** + * 串口通信类别-传感器 + */ + public final static String TYPE_SENSOR = "TYPE_SENSOR"; + + public static void start(String type, QueueIOUITask queueTask) { + MyQueue myQueue; + if (TYPE_CONTROLLER.equals(type) || Controller.isIntegratedController()) { + // 控制器 | 集成控制器 + if (controllerInstance == null) { + synchronized (MyQueue.class) { + if (controllerInstance == null) { + controllerInstance = new MyQueue(); + } + } + } + myQueue = controllerInstance; + } else { + // 传感器 + if (sensorInstance == null) { + synchronized (MyQueue.class) { + if (sensorInstance == null) { + sensorInstance = new MyQueue(); + } + } + } + myQueue = sensorInstance; + } + myQueue.taskQueue.offer(queueTask); + } + + public static MyQueue getInstance(String type) { + if (TYPE_CONTROLLER.equals(type)) { + // 控制器 + if (controllerInstance == null) { + synchronized (MyQueue.class) { + if (controllerInstance == null) { + controllerInstance = new MyQueue(); + } + } + } + return controllerInstance; + } else { + // 传感器 + if (sensorInstance == null) { + synchronized (SerialPortUtil.class) { + if (sensorInstance == null) { + sensorInstance = new MyQueue(); + } + } + } + return sensorInstance; + } + } + + public void addTask(QueueIOTask queueIOTask) { + taskQueue.offer(queueIOTask); + } + + private MyQueue() { + // 使用单线程的ExecutorService + executorService = Executors.newSingleThreadExecutor(); + taskQueue = new PriorityBlockingQueue<>(10, Comparator.comparingInt(BaseQueue::getPriority)); + // 启动任务执行线程 + startExecution(); + } + + public void startExecution() { + executorService.execute(() -> { + while (true) { + try { + // 取出队列中优先级最高的任务 + Runnable task = taskQueue.take(); + task.run(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + }); + } + + public void shutdown() { + if (executorService != null) { + executorService.shutdown(); + } + } + +} + diff --git a/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java new file mode 100644 index 0000000..fed868b --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/MyUtil.java @@ -0,0 +1,465 @@ +package com.example.iot_controlhost.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.view.animation.LinearInterpolator; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import androidx.appcompat.app.AppCompatDelegate; + +import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.AppUtils; +import com.blankj.utilcode.util.StringUtils; +import com.blankj.utilcode.util.Utils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.adapter.ModelsAdapter; +import com.example.iot_controlhost.base.BaseMode; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.controller.Controller; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.thread.QueueIOUITask; +import com.example.iot_controlhost.ui.fragment.AutoFragment; +import com.example.iot_controlhost.ui.fragment.IntelligenceFragment; +import com.example.iot_controlhost.ui.fragment.ManualFragment; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorAmmoniaSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.mainboard.MyAPIContext; +import com.jakewharton.processphoenix.ProcessPhoenix; +import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator; +import com.luckycatlabs.sunrisesunset.dto.Location; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import es.dmoral.toasty.Toasty; +import me.samlss.broccoli.Broccoli; +import me.samlss.broccoli.BroccoliGradientDrawable; +import me.samlss.broccoli.PlaceholderParameter; + +public class MyUtil { + + /** + * 在温湿度目标值变化时,返回最大值和最小值应该的变化值 + * + * @param targetOld + * @param targetDelta + * @param nowMax + * @param nowMin + * @param type + * @return + */ + public static double[] getTHMinMax(double targetOld, double targetDelta, double nowMax, double nowMin, double type) { + double max = 100; + double min = 0; + double targetDeltaLimit = 5; + if (type == 0) { + max = 33; + min = 17; + targetDeltaLimit = 1; + } + // 计算target与现有最大值和最小值的偏移 + double resultMax = nowMax + targetDelta; + // 最大值超过自定义范围 或者 超过规定最大值 则取规定最大值 + if (targetOld + targetDelta >= max - targetDeltaLimit || resultMax > max) { + resultMax = max; + } + double resultMin = nowMin + targetDelta; + // 最小值超过自定义范围 或者 超过规定最小值 则取规定最小值 + if (targetOld + targetDelta <= min + targetDeltaLimit || resultMin < min) { + resultMin = min; + } + double resultTarget = targetOld + targetDelta; + if (resultTarget >= max) { + resultTarget = max; + } + if (resultTarget <= min) { + resultTarget = min; + } + return new double[]{resultMin, resultTarget, resultMax}; + } + + + /** + * 获得端口下配置的设备 + * + * @param port + * @return 设备名 + */ + public static String getPortToDeviceName(int port) { + for (Relay controller : RoomController.getAllRelayDevice()) { + for (int controllerPort : controller.getPorts()) { + if (controllerPort == port) { + return controller.getName(); + } + } + } + return "--"; + } + + /** + * 校验红外指令正确与否 + */ + public static boolean checkInstruction(String instruction) { + //如果全为0或者全为F + if (StringUtils.isEmpty(instruction) || instruction.replaceAll("0", "").equals("") || instruction.replaceAll("F", "").equals("")) { + return false; + } + return true; + } + + /** + * 切换亮暗色调 + * + * @param isDark 是否为黑色调 + */ + public static void switchDarkMode(boolean isDark) { + if (isDark) { + // 切换到暗色主题 + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + MMKVUtil.put(RoomSetting.IS_DARK_THEME, true); + } else { + // 切换到浅色主题 + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + MMKVUtil.put(RoomSetting.IS_DARK_THEME, false); + } + } + + public static Broccoli getMyBroccoli(View... v) { + Broccoli broccoli = new Broccoli(); + for (int i = 0; i < v.length; i++) { + broccoli.addPlaceholders(new PlaceholderParameter.Builder() + .setView(v[i]) + .setDrawable(new BroccoliGradientDrawable(Color.parseColor("#DDDDDD"), + Color.parseColor("#CCCCCC"), 0, 1000, new LinearInterpolator())) + .build()); + } + return broccoli; + } + + /** + * 获得目标模式名 + */ + public static String getModeName(int i) { + switch (i) { + case 0: + return "手动"; + case 1: + return "自动"; + case 2: + return "智能"; + default: + return ""; + } + } + + /*** + * 获得格式化后的时间 + */ + public static int[] getHourAndMinuteUsingLocalTime(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + + return new int[]{hour, minute}; + } + + + /** + * 获取当前版本号 + */ + public static int getVersion(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return packageInfo.versionCode; + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + + /** + * 获取当前版本号 + */ + public static String getVersionId(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return String.valueOf(packageInfo.versionCode); + } catch (Exception e) { + e.printStackTrace(); + return "Err"; + } + } + + /** + * 获取当前版本名称 + */ + public static String getVersionName(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return packageInfo.versionName; + } catch (Exception e) { + e.printStackTrace(); + return "Err"; + } + } + + /** + * 获取当前时间 + */ + public static String getDateTime() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); + } + + /** + * 获取当前日期 + */ + public static String getDateDay() { + return new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + } + + /** + * 转换date格式为标准时间String + */ + public static String getFormatDateTime(Date date) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + } + + public static void changeIcon(Context context, ImageView iv, boolean state, LinearLayout ll_auto_set_param, LinearLayout ll_auto_set_param_none) { + iv.setImageDrawable(context.getDrawable(state ? R.mipmap.on : R.mipmap.off)); + ll_auto_set_param.setVisibility(state ? View.VISIBLE : View.GONE); + ll_auto_set_param_none.setVisibility(state ? View.GONE : View.VISIBLE); + } + + public static void changeIcon(Context context, ImageView iv, LinearLayout ll_auto_set_param, LinearLayout ll_auto_set_param_none) { + changeIcon(context, iv, !iv.getDrawable().getConstantState().equals(context.getDrawable(R.mipmap.on).getConstantState()), ll_auto_set_param, ll_auto_set_param_none); + } + + /** + * 关闭APP + */ + public static void exitApp() { + UserLog.system("关闭APP"); + MyUtil.stopAppControl("正在关闭软件中", () -> AppUtils.exitApp()); + } + + /** + * 重启APP + */ + public static void relaunchApp() { + UserLog.system("重启APP"); + MyUtil.stopAppControl("正在重启软件中", () -> { +// AppUtils.relaunchApp(); // 这个方法重启不干净 + ProcessPhoenix.triggerRebirth(MyApp.getAppContext()); + }); + } + + /** + * 关闭设备 + */ + public static void shutDown() { + UserLog.system("关闭设备"); + //先关闭所有设备 + MyUtil.stopAppControl("正在关闭设备中", () -> MyAPIContext.getInstance().shutDown()); + } + + /** + * 重启设备 + */ + public static void reboot() { + UserLog.system("重启设备"); + MyUtil.stopAppControl("正在重启设备中", () -> MyAPIContext.getInstance().reBoot()); + } + + /** + * 停止APP的控制 + * 于进入配置或离开关闭设备时使用 + */ + public static void stopAppControl(String tips, Runnable runnable) { + DialogUtil.showLoadingDialog(ActivityUtils.getTopActivity(), tips); + MyQueue.start(MyQueue.TYPE_CONTROLLER, new QueueIOUITask<>() { + @Override + public Object doInQueueThread() { + //关闭单独设备 + ModelsAdapter.getModeByPosition(RoomSetting.getMode()).stopMode(); + // 关闭RxBus监听 + RxBusUtils.get().unregisterAll(RxTag.UPDATE_MAIN); + RxBusUtils.get().unregisterAll(RxTag.UPDATE_AUTO); + RoomController.floor.setPowerSupply(false); + //关闭串口 因为关掉串口后会导致部分设备无法关闭 暂时注释 +// SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR).close(); +// SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).close(); + return null; + } + + @Override + public void doInUIThread(Object o) { + DialogUtil.hideLoadingDialog(); + runnable.run(); + } + }); + } + + /** + * 直接关闭某个设备 + */ + public static boolean autoControlOperateThird(Controller controller) { + return autoControlOperateThird(controller, false); + } + + public static boolean autoControlOperateThird(Controller controller, boolean targetState) { + return autoControlOperateThird(controller, targetState, true); + } + + /** + * 自动模式下的操作 + * 最多连续错误三次,否则输出错误日志 + * + * @param controller 被操作的设备 + * @param targetState 目标状态 + * @param needAutoLog 是否需要输出"自动"日志 + * @return + */ + public static boolean autoControlOperateThird(Controller controller, boolean targetState, boolean needAutoLog) { + if (!controller.isAvailable()) { + return false; + } + int maxCount = 3; + int count = 0; + boolean success = false; + if (controller.isPowerSupply() != targetState) { + // 只有在状态不一致时方才输出日志 + MyLog.auto((needAutoLog ? "自动:" : "") + (targetState ? "开启" : "关闭") + controller.getName()); + UserLog.operate((needAutoLog ? "自动:" : "") + (targetState ? "开启" : "关闭") + controller.getName()); + } + while (count < maxCount) { + count++; // 将计数器递增放在循环开始处 + if (controller.setPowerSupply(targetState)) { + success = true; + // 成功后退出循环 + break; + } + } + if (!success) { + MyLog.auto((needAutoLog ? "自动" : "") + "自动操作(连续" + maxCount + "次)失败:" + (targetState ? "开启" : "关闭") + controller.getName()); + UserLog.operateError((needAutoLog ? "自动" : "") + "操作(连续" + maxCount + "次)失败:" + (targetState ? "开启" : "关闭") + controller.getName()); + } + //自动操作完设备需要更新UI + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); + return success; + } + + /** + * 保存字符串至“下载”文件夹中 + * + * @param context 上下文 + * @param content 字符串 + * @param fileName 文件名 + */ + public static void saveStringToTxtFile(Context context, String content, String fileName) { + File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + if (!downloadDir.exists()) { + downloadDir.mkdirs(); + } + File file = new File(downloadDir, fileName); + try { + FileOutputStream fos = new FileOutputStream(file); + fos.write(content.getBytes()); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * @param latitude 纬度 + * @param longitude 经度 + */ + public static void calculateSunriseSunset(double latitude, double longitude) { + //成都坐标 + Location location = new Location(latitude, longitude); + // 创建日出日落计算器对象 + SunriseSunsetCalculator calculator = new SunriseSunsetCalculator(location, TimeZone.getDefault()); + // 获取日出时间 + String sunrise = calculator.getOfficialSunriseForDate(Calendar.getInstance()); + // 获取日落时间 + String sunset = calculator.getOfficialSunsetForDate(Calendar.getInstance()); + MyLog.test("日出时间:" + sunrise); + MyLog.test("日落时间:" + sunset); + } + + /** + * 检查设备重名 + */ + public static boolean checkDeviceNameAvailable(String name) { + List devices = new ArrayList<>(); + // 检查继电器 + devices.addAll(RoomController.getAllRelayDevice()); + // 检查控制器 + devices.add(RoomController.airCondition); + devices.add(RoomController.floor); + devices.add(RoomController.airConditionInfrared); + devices.add(RoomController.airConditionInfrared2); + devices.add(RoomController.relayFloor); + // 检查固定传感器 + Collections.addAll(devices, RoomSensor.outdoorTHSensor, RoomSensor.consumptionSensor, RoomSensor.airTHSensor, RoomSensor.co2OutdoorSensor); + // 检查动态传感器 + devices.addAll(IndoorTHSensorUtil.getAllSensors()); + devices.addAll(IndoorCO2SensorUtil.getAllSensors()); + devices.addAll(IndoorAmmoniaSensorUtil.getAllSensors()); + devices.addAll(IndoorFloorTSensorUtil.getAllSensors()); + for (Device device : devices) { + if (device.getName().equals(name)) { + return false; + } + } + return true; + } + + + public static void relaunchFrp(){ + Context mContext = ActivityUtils.getTopActivity(); + Intent intent = new Intent(); + intent.setAction("receiver_control"); + intent.putExtra("control", -1); + mContext.sendBroadcast(intent); + // 等待2sFRP关闭,然后重启 + RxJavaUtils.delay(2, aLong -> { + Intent intent2 = mContext.getPackageManager().getLaunchIntentForPackage("com.bbitcn.bbit_frp2"); + intent2.putExtra("host_id", HardwareSetting.getHostId()); + mContext.startActivity(intent2); + }); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java b/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java new file mode 100644 index 0000000..a3aa3f1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/PollingTask.java @@ -0,0 +1,171 @@ +package com.example.iot_controlhost.utils; + +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.NonNull; + +import com.example.iot_controlhost.utils.log.MyLog; +import com.xuexiang.rxutil2.rxjava.impl.IRxUITask; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class PollingTask { + + private static Map instances = new HashMap<>(); + private ScheduledExecutorService scheduler; + private Map> taskMap; + + // 私有化构造函数,确保外部不能直接实例化 + private PollingTask() { + createNewScheduler(); + } + // 创建新的 ScheduledExecutorService 实例 + private void createNewScheduler() { + if (scheduler != null && !scheduler.isShutdown()) { + scheduler.shutdown(); + } + scheduler = Executors.newScheduledThreadPool(3); + taskMap = new HashMap<>(); + } + + /** + * 获取单例实例-用户正常运行时的任务 + * @return + */ + public static synchronized PollingTask getInstance() { + return getInstance("MAIN"); + } + + // 获取基于ID的单例实例 + public static synchronized PollingTask getInstance(String id) { + if (!instances.containsKey(id)) { + instances.put(id, new PollingTask()); + } + return instances.get(id); + } + /** + * 启动延迟任务 + * + * @param taskId 任务ID,用于唯一标识该任务 + * @param delaySeconds 延迟时间(以秒为单位) + * @param task 需要执行的任务 + */ + public void startDelayedTask(String taskId, long delaySeconds, @NonNull final Runnable task) { + stopPollingTask(taskId); // 如果已有相同ID的任务,先停止 + + ScheduledFuture future = scheduler.schedule(() -> { + task.run(); // 执行任务 + }, delaySeconds, TimeUnit.SECONDS); + taskMap.put(taskId, future); // 保存任务的ScheduledFuture + } + + /** + * 启动IO线程的轮询任务 + * + * @param taskId 任务ID,用于唯一标识该任务 唯一!! 不可重复,否则会覆盖之前的任务 + * @param intervalSeconds 轮询间隔时间(以秒为单位) + * @param task 需要在IO线程中执行的任务 + */ + public void startPollingTaskOnIOThread(String taskId, long intervalSeconds, @NonNull final Runnable task) { + startPollingTask(taskId, intervalSeconds, () -> { + task.run(); + }); + } + + /** + * 启动UI线程的轮询任务 + * + * @param taskId 任务ID,用于唯一标识该任务 + * @param intervalSeconds 轮询间隔时间(以秒为单位) + * @param task 需要在UI线程中执行的任务 + */ + public void startPollingTaskOnUIThread(String taskId, long intervalSeconds, @NonNull final Runnable task) { + startPollingTask(taskId, intervalSeconds, () -> { + // 执行UI线程任务(需要在UI线程中执行) + new Handler(Looper.getMainLooper()).post(task); + }); + } + + /** + * 启动IO线程任务并在UI线程中处理结果的轮询任务 + * + * @param taskId 任务ID,用于唯一标识该任务 + * @param intervalSeconds 轮询间隔时间(以秒为单位) + * @param ioTask 需要在IO线程中执行的任务 + * @param uiTask 需要在UI线程中执行的任务 + * @param IO任务返回结果的类型 + */ + public void startPollingTask(String taskId, long intervalSeconds, @NonNull final IRxIOTask ioTask, @NonNull final IRxUITask uiTask) { + startPollingTask(taskId, intervalSeconds, () -> { + // 执行IO线程任务 + T result = ioTask.doInIOThread(); + // 将结果传递给UI线程任务 + new Handler(Looper.getMainLooper()).post(() -> uiTask.doInUIThread(result)); + }); + } + + /** + * 启动基础轮询任务,供内部使用 + * + * @param taskId 任务ID,用于唯一标识该任务 + * @param intervalSeconds 轮询间隔时间(以秒为单位) + * @param task 需要执行的任务 + */ + private void startPollingTask(String taskId, long intervalSeconds, @NonNull final Runnable task) { + stopPollingTask(taskId); // 如果已有相同ID的任务,先停止 + + ScheduledFuture future = scheduler.scheduleAtFixedRate(task, 0, intervalSeconds, TimeUnit.SECONDS); + taskMap.put(taskId, future); // 保存任务的ScheduledFuture + } + + // 停止所有轮询任务 + public void stopAllPollingTasks() { + for (String taskId : taskMap.keySet()) { + MyLog.test("关闭轮询任务" + taskId); + } + if (scheduler != null && !scheduler.isShutdown()) { +// scheduler.shutdown(); // 停止所有任务 +// try { +// if (!scheduler.awaitTermination(3, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); // 超时后强制停止 +// } +// } catch (InterruptedException e) { +// scheduler.shutdownNow(); // 当前线程被中断时强制停止 +// } + taskMap.clear(); // 清空任务映射 + } + // 从实例映射中移除当前实例 + instances.values().remove(this); + } + /** + * 停止轮询任务 + * + * @param taskId 任务ID + */ + public void stopPollingTask(String taskId) { + ScheduledFuture future = taskMap.get(taskId); + if (future != null) { + future.cancel(true); // 取消任务,中断正在执行的任务 + taskMap.remove(taskId); // 从任务列表中移除 + } + } + + // 定义IO任务接口 + public interface IRxIOTask { + T doInIOThread(); + } + + // 定义UI任务接口 + public interface IRxUITask { + void doInUIThread(T t); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/QRCodeUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/QRCodeUtil.java new file mode 100644 index 0000000..f0e5e5f --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/QRCodeUtil.java @@ -0,0 +1,310 @@ +package com.example.iot_controlhost.utils; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.text.TextUtils; + +import androidx.annotation.ColorInt; +import androidx.annotation.Nullable; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.CharacterSetECI; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +import java.util.Hashtable; + +/** + * @ClassName: QRCodeUtil + * @Description: 二维码工具类 + * @Author Sunny + * @Date 2021/1/10 + */ +public class QRCodeUtil { + /** + * 创建二维码位图 + * @param content 字符串内容(支持中文) + * @param width 位图宽度(单位:px) + * @param height 位图高度(单位:px) + * + * @return + */ + @Nullable + public static Bitmap createQRCodeBitmap(String content, int width, int height){ + return createQRCodeBitmap(content, width, height, "UTF-8", "H", "2", Color.BLACK, Color.WHITE); + } + + /** + * 创建二维码位图(带logo) + * + * @param content 字符串内容(支持中文) + * @param width 位图宽度(单位:px) + * @param height 位图高度(单位:px) + * @return + */ + @Nullable + public static Bitmap createQRCodeBitmap(String content, int width, int height,Bitmap logoBitmap, float logoPercent){ + return createQRCodeBitmap(content, width, height, "UTF-8", "H", "2", Color.BLACK, Color.WHITE,logoBitmap,logoPercent); + } + + /** + * 创建二维码位图 (支持自定义配置和自定义样式) + * + * @param content 字符串内容 + * @param width 位图宽度,要求>=0(单位:px) + * @param height 位图高度,要求>=0(单位:px) + * @param character_set 字符集/字符转码格式 (支持格式:{@link CharacterSetECI })。传null时,zxing源码默认使用 "ISO-8859-1" + * @param error_correction 容错级别 (支持级别:{@link ErrorCorrectionLevel })。传null时,zxing源码默认使用 "L" + * @param margin 空白边距 (可修改,要求:整型且>=0), 传null时,zxing源码默认使用"4"。 + * @param color_black 黑色色块的自定义颜色值 + * @param color_white 白色色块的自定义颜色值 + * @return + */ + @Nullable + public static Bitmap createQRCodeBitmap(String content, int width, int height, + @Nullable String character_set, @Nullable String error_correction, @Nullable String margin, + @ColorInt int color_black, @ColorInt int color_white){ + + /** 1.参数合法性判断 */ + if(TextUtils.isEmpty(content)){ // 字符串内容判空 + return null; + } + + if(width < 0 || height < 0){ // 宽和高都需要>=0 + return null; + } + + try { + /** 2.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */ + Hashtable hints = new Hashtable<>(); + + if(!TextUtils.isEmpty(character_set)) { + hints.put(EncodeHintType.CHARACTER_SET, character_set); // 字符转码格式设置 + } + + if(!TextUtils.isEmpty(error_correction)){ + hints.put(EncodeHintType.ERROR_CORRECTION, error_correction); // 容错级别设置 + } + + if(!TextUtils.isEmpty(margin)){ + hints.put(EncodeHintType.MARGIN, margin); // 空白边距设置 + } + BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints); + + /** 3.创建像素数组,并根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */ + int[] pixels = new int[width * height]; + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + if(bitMatrix.get(x, y)){ + pixels[y * width + x] = color_black; // 黑色色块像素设置 + } else { + pixels[y * width + x] = color_white; // 白色色块像素设置 + } + } + } + + /** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,之后返回Bitmap对象 */ + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } catch (WriterException e) { + e.printStackTrace(); + } + + return null; + } + + /** + *生成带Logo的二维码 + * @param content 字符串内容 + * @param width 二维码宽度 + * @param height 二维码高度 + * @param character_set 编码方式(一般使用UTF-8) + * @param error_correction_level 容错率 L:7% M:15% Q:25% H:35% + * @param margin 空白边距(二维码与边框的空白区域) + * @param color_black 黑色色块 + * @param color_white 白色色块 + * @param logoBitmap logo图片 + * @param logoPercent logo所占百分比 + * @return + */ + public static Bitmap createQRCodeBitmap(String content, int width, int height, String character_set, + String error_correction_level,String margin, int color_black, + int color_white,Bitmap logoBitmap, float logoPercent) { + // 字符串内容判空 + if (TextUtils.isEmpty(content)) { + return null; + } + // 宽和高>=0 + if (width < 0 || height < 0) { + return null; + } + try { + /** 1.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */ + Hashtable hints = new Hashtable<>(); + // 字符转码格式设置 + if (!TextUtils.isEmpty(character_set)) { + hints.put(EncodeHintType.CHARACTER_SET, character_set); + } + // 容错率设置 + if (!TextUtils.isEmpty(error_correction_level)) { + hints.put(EncodeHintType.ERROR_CORRECTION, error_correction_level); + hints.put(EncodeHintType.MARGIN, margin); + } + /** 2.将配置参数传入到QRCodeWriter的encode方法生成BitMatrix(位矩阵)对象 */ + // 空白边距设置 + if (!TextUtils.isEmpty(margin)) { + BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints); + /** 3.创建像素数组,并根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */ + int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + + //bitMatrix.get(x,y)方法返回true是黑色色块,false是白色色块 + if (bitMatrix.get(x, y)) { + pixels[y * width + x] = color_black;//黑色色块像素设置 + } else { + pixels[y * width + x] = color_white;//白色色块像素设置 + } + } + } + /** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,并返回Bitmap对象 */ + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + /** 5.为二维码添加logo图标 */ + if (logoBitmap != null) { + return addLogo(bitmap, logoBitmap, logoPercent); + } + return bitmap; + } + } catch (WriterException e) { + e.printStackTrace(); + return null; + } + return null; + } + /** + * 生成自定义二维码 + * + * @param content 字符串内容 + * @param width 二维码宽度 + * @param height 二维码高度 + * @param character_set 编码方式(一般使用UTF-8) + * @param error_correction_level 容错率 L:7% M:15% Q:25% H:35% + * @param margin 空白边距(二维码与边框的空白区域) + * @param color_black 黑色色块 + * @param color_white 白色色块 + * @param logoBitmap logo图片(传null时不添加logo) + * @param logoPercent logo所占百分比 + * @param bitmap_black 用来代替黑色色块的图片(传null时不代替) + * @return + */ + public static Bitmap createQRCodeBitmap(String content, int width, int height, String character_set, String error_correction_level, + String margin, int color_black, int color_white, Bitmap logoBitmap, float logoPercent, Bitmap bitmap_black) { + // 字符串内容判空 + if (TextUtils.isEmpty(content)) { + return null; + } + // 宽和高>=0 + if (width < 0 || height < 0) { + return null; + } + try { + /** 1.设置二维码相关配置,生成BitMatrix(位矩阵)对象 */ + Hashtable hints = new Hashtable<>(); + // 字符转码格式设置 + if (!TextUtils.isEmpty(character_set)) { + hints.put(EncodeHintType.CHARACTER_SET, character_set); + } + // 容错率设置 + if (!TextUtils.isEmpty(error_correction_level)) { + hints.put(EncodeHintType.ERROR_CORRECTION, error_correction_level); + } + // 空白边距设置 + if (!TextUtils.isEmpty(margin)) { + hints.put(EncodeHintType.MARGIN, margin); + } + /** 2.将配置参数传入到QRCodeWriter的encode方法生成BitMatrix(位矩阵)对象 */ + BitMatrix bitMatrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints); + + /** 3.创建像素数组,并根据BitMatrix(位矩阵)对象为数组元素赋颜色值 */ + if (bitmap_black != null) { + //从当前位图按一定的比例创建一个新的位图 + bitmap_black = Bitmap.createScaledBitmap(bitmap_black, width, height, false); + } + int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + //bitMatrix.get(x,y)方法返回true是黑色色块,false是白色色块 + if (bitMatrix.get(x, y)) {// 黑色色块像素设置 + if (bitmap_black != null) {//图片不为null,则将黑色色块换为新位图的像素。 + pixels[y * width + x] = bitmap_black.getPixel(x, y); + } else { + pixels[y * width + x] = color_black; + } + } else { + pixels[y * width + x] = color_white;// 白色色块像素设置 + } + } + } + + /** 4.创建Bitmap对象,根据像素数组设置Bitmap每个像素点的颜色值,并返回Bitmap对象 */ + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + + /** 5.为二维码添加logo图标 */ + if (logoBitmap != null) { + return addLogo(bitmap, logoBitmap, logoPercent); + } + return bitmap; + } catch (WriterException e) { + e.printStackTrace(); + return null; + } + } + + + /** + * 向二维码中间添加logo图片(图片合成) + * @param srcBitmap 原图片(生成的简单二维码图片) + * @param logoBitmap logo图片 + * @param logoPercent 百分比 (用于调整logo图片在原图片中的显示大小, 取值范围[0,1] ) + * @return + */ + private static Bitmap addLogo(Bitmap srcBitmap, Bitmap logoBitmap, float logoPercent){ + if(srcBitmap == null){ + return null; + } + if(logoBitmap == null){ + return srcBitmap; + } + //传值不合法时使用0.2F + if(logoPercent < 0F || logoPercent > 1F){ + logoPercent = 0.2F; + } + + /** 1. 获取原图片和Logo图片各自的宽、高值 */ + int srcWidth = srcBitmap.getWidth(); + int srcHeight = srcBitmap.getHeight(); + int logoWidth = logoBitmap.getWidth(); + int logoHeight = logoBitmap.getHeight(); + + /** 2. 计算画布缩放的宽高比 */ + float scaleWidth = srcWidth * logoPercent / logoWidth; + float scaleHeight = srcHeight * logoPercent / logoHeight; + + /** 3. 使用Canvas绘制,合成图片 */ + Bitmap bitmap = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + canvas.drawBitmap(srcBitmap, 0, 0, null); + canvas.scale(scaleWidth, scaleHeight, srcWidth/2, srcHeight/2); + canvas.drawBitmap(logoBitmap, srcWidth/2 - logoWidth/2, srcHeight/2 - logoHeight/2, null); + + return bitmap; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java b/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java new file mode 100644 index 0000000..d2b29db --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/Control.java @@ -0,0 +1,58 @@ +package com.example.iot_controlhost.utils.control; + +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +/** + * @Description 自动控制父类 + * @Author DuanKaiji + * @CreateTime 2023年11月09日 10:27:46 + */ +public abstract class Control { + /** + * 是否在开始中 + */ + protected boolean start = false; + + protected PollingTask pollingTask ; + + /** + * 开始控制 + */ + public void start() { + if (start) { + return; + } + pollingTask = PollingTask.getInstance(getClass().getSimpleName()); + start = startMethod(); + } + + /** + * 开始自动控制的具体实现 + * + * @return 操作成功与否 + */ + protected abstract boolean startMethod(); + + /** + * 停止控制 + */ + public void stop() { + if(pollingTask != null){ + pollingTask.stopAllPollingTasks(); + } + if (!start) { + return; + } + stopMethod(); + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); + start = false; + } + + /** + * 停止自动控制的具体实现 + */ + protected abstract void stopMethod(); +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/DeviceAnomalyDetection.java b/app/src/main/java/com/example/iot_controlhost/utils/control/DeviceAnomalyDetection.java new file mode 100644 index 0000000..3bed844 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/DeviceAnomalyDetection.java @@ -0,0 +1,418 @@ +package com.example.iot_controlhost.utils.control; + +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.StringUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.DeviceErrorInfo; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.network.PublicNetRequest; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * 异常检测-预警 + */ +public class DeviceAnomalyDetection { + private static DeviceAnomalyDetection instance; + /** + * 上次温度预警时间 为null则表示无异常 + */ + Date lastTemperatureWarningTime; + /** + * 上次湿度预警时间 为null则表示无异常 + */ + Date lastHumidityWarningTime; + + public static void init() { + instance = new DeviceAnomalyDetection(); + } + + public static DeviceAnomalyDetection getInstance() { + return instance; + } + + /** + * 设备出错信息 + * 0室内温度传感器 1室内湿度传感器 2地暖 + * 3室内湿度控制设备 4 室内温度控制设备 + */ + List deviceErrorInfos; + /** + * 继电器下设备出错信息 + */ + List relayInfos; + + PollingTask pollingTask; + + private DeviceAnomalyDetection() { + pollingTask = PollingTask.getInstance(); + relayInfos = new ArrayList<>(); + for (Relay relay : RoomController.getAllRelayDevice()) { + relayInfos.add(new DeviceErrorInfo(relay.getName(), 0, R.mipmap.warning_other)); + } + deviceErrorInfos = new ArrayList<>(); + deviceErrorInfos.add(new DeviceErrorInfo("温度计疑似故障", RoomSensor.getAverageIndoorTemperature(), R.mipmap.warning_sensor)); + deviceErrorInfos.add(new DeviceErrorInfo("湿度计疑似故障", RoomSensor.getAverageIndoorHumidity(), R.mipmap.warning_sensor)); + deviceErrorInfos.add(new DeviceErrorInfo("加湿器疑似故障", RoomSensor.getAverageIndoorHumidity(), R.mipmap.warning_humidifier)); + deviceErrorInfos.add(new DeviceErrorInfo("空调疑似故障", RoomSensor.getAverageIndoorTemperature(), R.mipmap.warning_air_conditioning)); + deviceErrorInfos.add(new DeviceErrorInfo(RoomSetting.getFloorName() + "疑似故障", RoomSensor.getFloorTemperature(), R.mipmap.warning_floor)); + deviceErrorInfos.add(new DeviceErrorInfo("换气扇疑似故障", 0, R.mipmap.warning_ventilator)); + deviceErrorInfos.add(new DeviceErrorInfo("设备连接故障", 0, R.mipmap.warning_other)); + pollingTask.startPollingTaskOnIOThread(RxTag.DEVICE_ANOMALY_DETECTION, Variable.DEVICE_ANOMALY_CHECK_TIME, () -> { + if (RoomSetting.getMode() == 1) { + //检查硬性错误-全部设备 + checkError(deviceErrorInfos.get(6)); + if (SilkwormDBManager.isStarting()) { + //检查疑似错误-室内温度计 + checkDoubtErrorT(deviceErrorInfos.get(0)); + //检查疑似错误-室内湿度计 + checkDoubtErrorH(deviceErrorInfos.get(1)); + //检查疑似错误-换气控制设备-换气扇 + checkDoubtErrorVentilator(deviceErrorInfos.get(5)); + //检查疑似错误-继电器设备-采用BBIT型号继电器 + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + checkRelaySensor(); + } + // 非手动模式 + if (RoomSetting.getMode() != 0) { + double targetTemperature = AutoModelSet.getTargetTemperature(); + double targetHumidity = AutoModelSet.getTargetHumidity(); + double minTemperature; + double maxTemperature; + double minHumidity; + double maxHumidity; + if (RoomSetting.getMode() == 1) { + minTemperature = AutoModelSet.getMinTemperature(); + maxTemperature = AutoModelSet.getMaxTemperature(); + minHumidity = AutoModelSet.getMinHumidity(); + maxHumidity = AutoModelSet.getMaxHumidity(); + } else { + // 智能模式 || 其他情况 + minTemperature = AIModelSet.getTargetTemperature() - 2; + maxTemperature = AIModelSet.getTargetTemperature() + 2; + minHumidity = AIModelSet.getTargetHumidity() - 15; + maxHumidity = AIModelSet.getTargetHumidity() + 15; + } + //检查疑似错误-湿度控制设备-加湿器 + checkDoubtErrorHumidity(deviceErrorInfos.get(2), maxHumidity, minHumidity); + //检查疑似错误-温度控制设备-空调、地暖 + checkDoubtErrorTemperature(deviceErrorInfos.get(3), deviceErrorInfos.get(4), maxTemperature, minTemperature); + //检查-室内温湿度-自动或智能模式下是否持续超出预警限度(持续超过最高最低值) + warningTH(targetTemperature, maxTemperature, minTemperature, targetHumidity, maxHumidity, minHumidity); + } + } + } + }); + } + + /** + * 检查硬性错误-全部设备 + */ + private void checkError(DeviceErrorInfo deviceErrorInfo) { + deviceErrorInfo.setErrorMessage(null); + StringBuffer reportName = new StringBuffer(""); + StringBuffer reportMessage = new StringBuffer(""); + for (Device device : RoomController.getAllDevice()) { + if (device.getFailTimes() > Variable.WARNING_REPORT_ERROR_TIMES) { + reportName.append(device.getName() + ";"); + reportMessage.append("<" + device.getName() + ">连续操作失败" + device.getFailTimes() + "次\n"); + } + } +// if (!StringUtils.isEmpty(reportName.toString())) { +// PublicNetRequest.AnomalyNotice("存在设备连续操作失败", 2, 3, reportMessage.toString()); +// } + // 一旦发现错误,立即发送通知 (此处设为5,为checkNotice()中+1操作后能直接识别为符合语音报警次数) + deviceErrorInfo.setErrorTimes(DeviceErrorInfo.errorCheckTimes2 - 1); + deviceErrorInfo.setErrorMessage(reportMessage.toString()); + deviceErrorInfo.checkNotice(2); + } + + /** + * 检查疑似错误-室内温度传感器 + */ + private void checkDoubtErrorT(DeviceErrorInfo t) { + List indoorT = new ArrayList<>(); + for (THSensor sensor : RoomSensor.dynamicTHSensorList) { + indoorT.add(new DeviceErrorInfo(sensor.getName(), sensor.temperature)); + } + for (GasSensor sensor : RoomSensor.dynamicCO2SensorList) { + indoorT.add(new DeviceErrorInfo(sensor.getName(), sensor.getTemperature())); + } + hasAnyDifferenceGreaterThan("温度", 4, indoorT, "温度差大于4度,疑似存在故障", t); + } + + /** + * 检查疑似错误-室内湿度传感器 + */ + private void checkDoubtErrorH(DeviceErrorInfo h) { + List indoorH = new ArrayList<>(); + for (THSensor sensor : RoomSensor.dynamicTHSensorList) { + indoorH.add(new DeviceErrorInfo(sensor.getName(), sensor.humidity)); + } + for (GasSensor sensor : RoomSensor.dynamicCO2SensorList) { + indoorH.add(new DeviceErrorInfo(sensor.getName(), sensor.getHumidity())); + } + hasAnyDifferenceGreaterThan("湿度", 10, indoorH, "湿度差大于10%,疑似存在故障", h); + } + + /** + * 检查疑似错误-室内湿度控制设备 + */ + private void checkDoubtErrorHumidity(DeviceErrorInfo humidity, double maxHumidity, double minHumidity) { + humidity.setErrorMessage(null); + double curHumidity = RoomSensor.getAverageIndoorHumidity(); + boolean curState = RoomController.humidifier.isPowerSupply(); + if (curHumidity < minHumidity || curHumidity > maxHumidity) { + //湿度异常 检测可能的异常 + if (curState && humidity.isOpen() && curHumidity <= humidity.getValue()) { + humidity.setErrorMessage("加湿器已开启,但湿度未按预期上升,疑似加湿器存在故障"); + } + if (!curState && !humidity.isOpen() && curHumidity >= humidity.getValue() + 1) { + humidity.setErrorMessage("加湿器已关闭,但湿度仍持续上升,疑似加湿器存在故障"); + } + } + humidity.checkNotice(); + humidity.setOpen(curState); + humidity.setValue(curHumidity); + } + + /** + * 检查疑似错误-室内温度控制设备-空调、地暖 + */ + private void checkDoubtErrorTemperature(DeviceErrorInfo air, DeviceErrorInfo floor, double maxTemperature, double minTemperature) { + air.setErrorMessage(null); + floor.setErrorMessage(null); + + //当前参数-空调 + boolean curAirState = MyInfraredUtils.getInstances().isPowerSupply(); + int curAirMode = MyInfraredUtils.getInstances().getMode(); + double curAirTemperature = RoomSensor.getAverageIndoorTemperature(); + //当前参数-地暖 + boolean curFloorState = RoomController.floor.isPowerSupply(); + //之前参数-空调 + boolean lastAirState = air.isOpen(); + int lastAirMode = air.getOperateType(); + double lastAirTemperature = air.getValue(); + //之前参数-地暖 + boolean lastFloorState = floor.isOpen(); + + if (curAirTemperature < minTemperature || curAirTemperature > maxTemperature) { + //温度异常 检测可能的异常 + if (!lastAirState && !curAirState && !lastFloorState && !curFloorState && Math.abs(curAirTemperature - lastAirTemperature) > 1.5) { + //之前设备全关 && 现在设备全关 && 温度变化超过1.5度 + floor.setErrorMessage("升温设备全关,但温度持续上升,疑似空调故障"); + air.setErrorMessage("升温设备全关,但温度持续上升,疑似" + RoomSetting.getFloorName() + "故障"); + } + if (lastAirState && curAirState && lastAirMode == 2 && curAirMode == 2 && curAirTemperature > lastAirTemperature) { + //之前空调开制冷 && 现在空调开制冷 && 现在温度大于之前 + air.setErrorMessage("空调已开启制冷,但温度未按预期下降,疑似空调故障"); + } + if (lastAirState && curAirState && lastAirMode == 0 && curAirMode == 0 && curAirTemperature < lastAirTemperature) { + //之前空调开制热 && 现在空调开制热 && 现在温度小于之前 + air.setErrorMessage("空调已开启制热,但温度未按预期上升,疑似空调故障"); + } + //如果地面温度传感器可用 + if (!RoomSensor.dynamicFloorTSensorList.isEmpty()) { + //之前地暖关着 && 现在地暖关着 && (地面温度上升 &&(地面温度保持 && 地面温度 > 30)) + double curFloorT = RoomSensor.getFloorTemperature(); + double lastFloorT = floor.getValue(); + if (!lastFloorState && !curFloorState && curFloorT > lastFloorT + 0.3) { + //之前地暖关着 && 现在地暖关着 && 地面温度上升 + floor.setErrorMessage("地暖已关闭,但地面温度持续上升,疑似地暖故障"); + } + if (!lastFloorState && !curFloorState && curFloorT > 30) { + //之前地暖关着 && 现在地暖关着 && 地面温度保持30以上 + floor.setErrorMessage("地暖已关闭,但地面温度仍保持在30度以上,疑似地暖故障"); + } + } + if (lastFloorState && curFloorState && curAirTemperature < lastAirTemperature - 1) { + //之前地暖开着 && 现在地暖开着 && 现在温度小于之前1度 (因为地暖有时只起保暖作用,所以可能存在地暖开启,但现在温度依然低于之前的情况) + floor.setErrorMessage("地暖已开启,但温度仍持续下降,疑似地暖故障"); + } + if (RoomSensor.consumptionSensor.isAvailable() && RoomController.floor.isAvailable() && //能耗传感器可用 + RoomSensor.consumptionSensor.getPower() != 0 && + RoomSensor.consumptionSensor.getPower() < RoomController.floor.getPwm(RoomController.floor.getGear()).getPower() - 200) {//当前功率小于地暖应有功率 + floor.setErrorMessage("地暖已开启,但当前功率小于地暖应有功率温度,疑似地暖故障"); + } + } + floor.checkNotice(); + air.checkNotice(); + //更新状态 + air.setOpen(curAirState); + air.setValue(curAirTemperature); + air.setOperateType(curAirMode); + floor.setOpen(curFloorState); + floor.setValue(RoomSensor.getFloorTemperature()); + } + + /** + * 检查疑似错误-室内换气控制设备-换气扇 + */ + private void checkDoubtErrorVentilator(DeviceErrorInfo ventilator) { + ventilator.setErrorMessage(null); + if (RoomSensor.dynamicCO2SensorList.isEmpty() || !RoomController.fan.isAvailable()) { + //CO2传感器、换气扇均不可用 + return; + } + boolean curState = RoomController.fan.isPowerSupply(); + double curCO2 = RoomSensor.dynamicCO2SensorList.stream().mapToDouble(GasSensor::getValue).average().orElse(0.0); + if (curState && ventilator.isOpen() && curCO2 >= ventilator.getValue()) { + ventilator.setErrorMessage("换气扇已开启,但CO2浓度未下降,疑似换气扇故障"); + } + ventilator.checkNotice(); + ventilator.setOpen(curState); + ventilator.setValue(curCO2); + } + + /** + * 检查疑似错误-新型继电器下的设备 + * 采用BBIT型号继电器,可以通过判断每个端口的功率来判断是否存在疑似故障 + */ + public void checkRelaySensor() { + for (DeviceErrorInfo info : relayInfos) { + for (Relay relay : RoomController.getAllAvailableRelays()) { + if (info.getName().equals(relay.getName())) { + if (relay.isPowerSupply() && relay.getPower() < 50 + && (!relay.getName().equals(RoomController.DEVICE_NAME[1]) || MyInfraredUtils.getInstances().isPowerSupply())) { + //已开启,功率少于50W 且(不是空调或 是空调并且红外已开启) + info.setErrorMessage(relay.getName() + "已开启,但功率少于50W"); + } else if (!relay.isPowerSupply() && relay.getPower() >= 50 + && (!relay.getName().equals(RoomController.DEVICE_NAME[1]) || !MyInfraredUtils.getInstances().isPowerSupply())) { + //已关闭,功率大于50W 且(不是空调或 是空调并且红外已关闭) + info.setErrorMessage(relay.getName() + "已关闭,但功率大于50W"); + } else { + // 正常 清除错误消息 + info.setErrorMessage(null); + } + info.checkNotice(3); + } + } + } + + } + + /** + * 判断是否室内温湿度传感器是否“疑似故障” + * + * @param threshold 波动值 + * @param values 待比较的传感器 + * @param tip 提示值 + * @param deviceErrorInfo 设备类型 + */ + private void hasAnyDifferenceGreaterThan(String type, double threshold, List values, String tip, DeviceErrorInfo deviceErrorInfo) { + deviceErrorInfo.setErrorMessage(null); + if (values.size() < 2) { + // 如果提供的参数不足两个,无法比较差值,返回 false + deviceErrorInfo.clearErrorTimes(); + return; + } + for (int i = 0; i < values.size(); i++) { + for (int j = i + 1; j < values.size(); j++) { + DeviceErrorInfo x = values.get(i); + DeviceErrorInfo y = values.get(j); + if (Math.abs(x.getValue() - y.getValue()) > threshold) { + // 存在任意两个值之间的差值超过指定值 + deviceErrorInfo.setErrorMessage(x.getName() + "与" + y.getName() + tip); + } + // 有任意值为0 + if (x.getValue() == 0 || y.getValue() == 0) { + deviceErrorInfo.setErrorMessage((x.getValue() == 0 ? x : y).getName() + type + "值为0,请确认是否正常"); + } + } + } + deviceErrorInfo.checkNotice(1); + } + + /** + * 温湿度预警判断 + */ + private void warningTH(double targetTemperature, double maxTemperature, double minTemperature, + double targetHumidity, double maxHumidity, double minHumidity) { + try { + double curTemperature = RoomSensor.getAverageIndoorTemperature(); + StringBuffer temperatureStr = new StringBuffer(curTemperature + " ℃ "); + double curHumidity = RoomSensor.getAverageIndoorHumidity(); + StringBuffer HumidityStr = new StringBuffer(curHumidity + " % "); + StringBuffer remarks = new StringBuffer(""); + boolean needReport = false; + //温度异常 + if (curTemperature < minTemperature || curTemperature > maxTemperature) { + if (lastTemperatureWarningTime == null) { + //没记录过,记录开始时间 + MyLog.warningError("(首次)室内温度有误,记录开始异常时间:" + MyUtil.getDateTime()); + lastTemperatureWarningTime = new Date(); + } else { + String tip = (curTemperature < minTemperature ? "温度偏低" : "温度偏高") + "-目标【" + targetTemperature + "℃】"; + needReport = true; + MyLog.warningError(tip); + temperatureStr.append(tip); + remarks.append("温度已异常" + TimeUtils.getTimeSpan(new Date(), lastTemperatureWarningTime, TimeConstants.MIN) + "分钟,"); + } + } else { + MyLog.warning("室内温度无误,初始化异常记录时间"); + lastTemperatureWarningTime = null; + } + //湿度异常 + if (curHumidity < minHumidity || curHumidity > maxHumidity) { + if (lastHumidityWarningTime == null) { + MyLog.warning("室内湿度有误,记录异常开始时间:" + MyUtil.getDateTime()); + //没记录过,记录开始时间 + lastHumidityWarningTime = new Date(); + } else { + needReport = true; + HumidityStr.append((curHumidity < minHumidity ? "湿度偏低" : "湿度偏高") + "-目标【" + targetHumidity + "%】"); + remarks.append("湿度已异常" + TimeUtils.getTimeSpan(new Date(), lastHumidityWarningTime, TimeConstants.MIN) + "分钟,"); + } + } else { + MyLog.warning("室内湿度无误,初始化异常记录时间"); + lastHumidityWarningTime = null; + } + if (needReport) { + long tempTimeBias = 0; + long humTimeBias = 0; + if (lastTemperatureWarningTime != null) { + tempTimeBias = TimeUtils.getTimeSpan(new Date(), lastTemperatureWarningTime, TimeConstants.MIN); + } + if (lastHumidityWarningTime != null) { + humTimeBias = TimeUtils.getTimeSpan(new Date(), lastHumidityWarningTime, TimeConstants.MIN); + } + if (tempTimeBias > Variable.WARNING_REPORT_TIME_INTERVAL_VOICE || humTimeBias > Variable.WARNING_REPORT_TIME_INTERVAL_VOICE) { + // 公众号+语音预警 需要优先判断是否超出语音预警时间 + PublicNetRequest.AnomalyNotice("室内温湿度", 1, 3, remarks.toString()); + // 发送语音后重置预警检测 + lastTemperatureWarningTime = null; + lastHumidityWarningTime = null; + } else if (tempTimeBias > Variable.WARNING_REPORT_TIME_INTERVAL || humTimeBias > Variable.WARNING_REPORT_TIME_INTERVAL) { + // 公众号预警 + PublicNetRequest.THWarningToWeChat(temperatureStr.toString(), HumidityStr.toString(), remarks.toString()); + } else { + MyLog.warning("预警无需发送:室内温湿度有误,但未到指定错误持续时间"); + } + } else { + MyLog.warning("预警无需发送:室内温湿度无误"); + } + } catch (Exception e) { + MyLog.warningError("发送温湿度预警信息异常:" + e.getMessage()); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java b/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java new file mode 100644 index 0000000..4d6a004 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/RemoteControl.java @@ -0,0 +1,111 @@ +package com.example.iot_controlhost.utils.control; + + +import android.provider.Settings; + +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.old.OldSet; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +/** + * 远程控制类 + */ +public class RemoteControl { + + /** + * 远程控制设备 + * + * @param controlCommand 控制指令json + * @return 操作成功与否 + */ + public static boolean remoteControl(int controlCommand, int gear, int value) { + try { + AirConditionInfrared air = MyInfraredUtils.getInstances(); + switch (controlCommand) { + case 0: + //开始升温 + return air.setAirCondition(0, 30) + && RoomController.floor.setGear(gear); + case 1: + //停止升温 + boolean res = true; + if (RoomController.floor.isPowerSupply()) { + res = RoomController.floor.setPowerSupply(false); + } + if (air.isPowerSupply() && air.getMode() == AirConditionInfrared.HEAT) { + res = air.setPowerSupply(false) && res; + } + return res; + case 3: + // 3停止降温 + boolean result = true; + if (air.isPowerSupply() && air.getMode() == AirConditionInfrared.COLD) { + result = air.setPowerSupply(false); + } + return result; + case 2: + //开始降温 + return air.setAirCondition(2, 17) + && RoomController.floor.setPowerSupply(false); + case 4: + //开始加湿 + if (RoomController.airExchange.isAvailable()) { + RoomController.airExchange.setPowerSupply(true); + } + return RoomController.humidifier.setPowerSupply(true); + case 5: + //停止加湿 + if (RoomController.airExchange.isAvailable()) { + RoomController.airExchange.setPowerSupply(false); + } + return RoomController.humidifier.setPowerSupply(false); + case 6: + //开始换气 + return RoomController.fan.setPowerSupply(true); + case 7: + //停止换气 + return RoomController.fan.setPowerSupply(false); + case 8: + //切换模式 + int newMode = OldSet.oldModeToNewMode(value); + if (RoomSetting.getMode() != newMode) { + int targetSignal = newMode == 0 ? 5 : newMode == 1 ? 3 : 6; + RxBusUtils.get().post(RxTag.UPDATE_MAIN, targetSignal); + MyLog.remote("远程设置" + MyUtil.getModeName(newMode) + "模式"); + } + return true; + case 9: + //开始匀风 + return RoomController.evenFan.setPowerSupply(true); + case 10: + //停止匀风 + return RoomController.evenFan.setPowerSupply(false); + case 11: + //开启灯光 + boolean l11 = RoomController.whiteLight.setPowerSupply(true); + boolean l12 = RoomController.redLight.setPowerSupply(true); + boolean l13 = RoomController.uvLight.setPowerSupply(true); + return l11 || l12 || l13; + case 12: + //关闭灯光 + boolean l21 = RoomController.whiteLight.setPowerSupply(false); + boolean l22 = RoomController.redLight.setPowerSupply(false); + boolean l23 = RoomController.uvLight.setPowerSupply(false); + return l21 || l22 || l23; + } + } catch (Exception e) { + e.printStackTrace(); + } + //均不符合条件 + return false; + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java new file mode 100644 index 0000000..1a8b0a8 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/disinfect/DisinfectController.java @@ -0,0 +1,82 @@ +package com.example.iot_controlhost.utils.control.disinfect; + +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.Control; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; + +import java.util.Calendar; + +/** + * @Description 自动模式-控制逻辑-消毒灯 + * @Author DuanKaiji + * @CreateTime 2023年11月09日 10:27:11 + */ +public class DisinfectController extends Control { + + private static DisinfectController instance; + + public static DisinfectController getInstance() { + if (instance == null) { + synchronized (DisinfectController.class) { + if (instance == null) { + instance = new DisinfectController(); + } + } + } + return instance; + } + + @Override + protected boolean startMethod() { + //初始化消毒定时任务 + MyLog.auto("初始化消毒定时任务"); + if (!RoomController.uvLight.isAvailable()) { + return false; + } + //开始控制消毒任务时,先检查当前是否在消毒计划中 + boolean target = UVTaskUtil.getUvTask().stream().anyMatch(task -> + isBetween(task.getStartTimeHour(), task.getStartTimeMinute(), task.getEndTimeHour(), task.getEndTimeMinute())); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> MyUtil.autoControlOperateThird(RoomController.uvLight, target))); + for (MyTask task : UVTaskUtil.getUvTask()) { + UVTaskUtil.stopTask(task); + } + for (MyTask task : UVTaskUtil.getUvTask()) { + UVTaskUtil.startTask(task); + } + return true; + } + + public static boolean isBetween(int startHour, int startMinute, int endHour, int endMinute) { + // 获取当前时间 + Calendar currentTime = Calendar.getInstance(); + int currentHour = currentTime.get(Calendar.HOUR_OF_DAY); + int currentMinute = currentTime.get(Calendar.MINUTE); + + // 计算开始时间和结束时间的总分钟数 + int startTimeInMinutes = startHour * 60 + startMinute; + int endTimeInMinutes = endHour * 60 + endMinute; + + // 计算当前时间的总分钟数 + int currentTimeInMinutes = currentHour * 60 + currentMinute; + + // 检查当前时间是否在开始时间和结束时间之间 + return currentTimeInMinutes >= startTimeInMinutes && currentTimeInMinutes <= endTimeInMinutes; + } + + @Override + protected void stopMethod() { + MyLog.auto("停止自动控制消毒————————————————————————————————————————————————————"); + UserLog.operate("自动:停止自动控制消毒"); + for (MyTask task : UVTaskUtil.getUvTask()) { + UVTaskUtil.stopTask(task); + } + //关闭消毒灯 + MyUtil.autoControlOperateThird(RoomController.uvLight); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java new file mode 100644 index 0000000..2ce7ba1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/even/EvenController.java @@ -0,0 +1,82 @@ +package com.example.iot_controlhost.utils.control.even; + +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.Control; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +/** + * @Description 自动模式-控制逻辑-匀风扇 + * @Author DuanKaiji + * @CreateTime 2023年12月25日 10:46:05 + */ +public class EvenController extends Control { + + private static EvenController instance; + + public static EvenController getInstance() { + if (instance == null) { + synchronized (EvenController.class) { + if (instance == null) { + instance = new EvenController(); + } + } + } + return instance; + } + + @Override + protected boolean startMethod() { + MyLog.auto("匀风扇开始自动控制——————————————————————————————————————————————————————————————————"); + if (!RoomController.evenFan.isAvailable()) { + return false; + } + pollingTask.startPollingTaskOnIOThread(RxTag.AUTO_EVEN, Variable.AUTO_CHECK_TIME, () -> { + boolean target; + if (AutoModelSet.isAutoEvenModeDuringHumidify() && RoomController.humidifier.isPowerSupply()) { + //加湿器工作时匀风扇不工作 + target = false; + } else { + // 检测控制方式{"手动控制", "升温时跟随启动", "降温时跟随启动", "升降温时跟随启动"} + String evenMode = AutoModelSet.getAutoEvenMode(); + if (evenMode.equals(SpinnerList.AUTO_EVEN_MODES[1])) { + //升温时跟随启动 + target = RoomController.floor.isPowerSupply() || + (MyInfraredUtils.getInstances().isPowerSupply() && MyInfraredUtils.getInstances().getMode() == AirConditionInfrared.HEAT); + } else if (evenMode.equals(SpinnerList.AUTO_EVEN_MODES[2])) { + //降温时跟随启动 + target = !RoomController.floor.isPowerSupply() && + (MyInfraredUtils.getInstances().isPowerSupply() && MyInfraredUtils.getInstances().getMode() == AirConditionInfrared.COLD); + } else if (evenMode.equals(SpinnerList.AUTO_EVEN_MODES[3])) { + //升降温时跟随启动 + target = RoomController.floor.isPowerSupply() || MyInfraredUtils.getInstances().isPowerSupply(); + } else { + //手动控制 + target = false; + } + } + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> MyUtil.autoControlOperateThird(RoomController.evenFan, target))); + }); + return true; + } + + @Override + protected void stopMethod() { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + MyUtil.autoControlOperateThird(RoomController.evenFan); + })); + MyLog.auto("停止自动控制匀风————————————————————————————————————————————————————"); + UserLog.operate("自动:停止自动控制匀风"); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java new file mode 100644 index 0000000..282b17a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/humidity/HumidityController.java @@ -0,0 +1,129 @@ +package com.example.iot_controlhost.utils.control.humidity; + +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.Control; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.xuexiang.rxutil2.rxjava.DisposablePool; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +/** + * @Description 自动模式-控制逻辑-湿度 + * @Author DuanKaiji + * @CreateTime 2023年11月09日 10:25:43 + */ +public class HumidityController extends Control { + /** + * 是否需要开启加湿器直到达到目标湿度 + */ + private static boolean needOpenToTarget = false; + /** + * 目标湿度 + */ + double target = 50; + private static HumidityController instance; + + public static HumidityController getInstance() { + if (instance == null) { + synchronized (HumidityController.class) { + if (instance == null) { + instance = new HumidityController(); + } + } + } + return instance; + } + + @Override + protected boolean startMethod() { + if (!RoomController.humidifier.isAvailable()) { + MyLog.autoError("加湿器不可用,无法自动控制"); + return false; + } + pollingTask.startPollingTaskOnIOThread(RxTag.AUTO_HUMIDITY, Variable.AUTO_CHECK_TIME, () -> { + double cur = RoomSensor.getAverageIndoorHumidity(); + double min = 0; + double max = 0; + if (RoomSetting.getMode() == 1) { + target = AutoModelSet.getTargetHumidity(); + min = AutoModelSet.getMinHumidity(); + max = AutoModelSet.getMaxHumidity(); + } else if (RoomSetting.getMode() == 2) { + target = AIModelSet.getTargetHumidity(); + min = target - Variable.AUTO_DO_NOTHING_HUMIDITY; + max = target + Variable.AUTO_DO_NOTHING_HUMIDITY; + } + boolean needHumidify = false; + boolean needDehumidify = false; + if (cur < min //当前小于用户规定最小湿度 + || cur < target - Variable.AUTO_DO_NOTHING_HUMIDITY //小于系统要求最小湿度 + || cur < target && needOpenToTarget) { // 需要急剧加湿到目标湿度 + needOpenToTarget = true; + needHumidify = true; + } else { + needOpenToTarget = false; + needHumidify = false; + } + + // 除湿逻辑(新增) + if (cur > max) { + needDehumidify = true; + } + // === 互斥处理 === + if (needHumidify && needDehumidify) { + // 理论不会发生,保险处理 + needDehumidify = false; + } + boolean humidifierState = needHumidify; + boolean dehumidifierState = needDehumidify; + + if (RoomController.humidifier.isPowerSupply() == humidifierState && RoomController.dehumidifier.isPowerSupply() == dehumidifierState) { + MyLog.auto("湿度-控制维持当前状态"); + } else { + // 加湿器控制 + if (RoomController.humidifier.isAvailable()) { + MyLog.auto("自动:" + (humidifierState ? "开启" : "关闭") + "加湿器--"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + MyUtil.autoControlOperateThird(RoomController.humidifier, humidifierState); + String airExchangeMode = AutoModelSet.getAirExchangeMode(); + if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[2])) { + MyUtil.autoControlOperateThird(RoomController.airExchange, humidifierState); + } + })); + } + // 除湿器控制(新增) + if (RoomController.dehumidifier.isAvailable()) { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + MyLog.auto("自动:" + (dehumidifierState ? "开启" : "关闭") + "除湿机--"); + MyUtil.autoControlOperateThird(RoomController.dehumidifier, dehumidifierState); + })); + } + } + }); + return true; + } + + @Override + protected void stopMethod() { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + MyUtil.autoControlOperateThird(RoomController.humidifier); + String airExchangeMode = AutoModelSet.getAirExchangeMode(); + if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[2])) { + MyUtil.autoControlOperateThird(RoomController.airExchange, false); + } + })); + MyLog.auto("停止自动控制湿度————————————————————————————————————————————————————"); + UserLog.operate("自动:停止自动控制湿度"); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/infrared/MyInfraredUtils.kt b/app/src/main/java/com/example/iot_controlhost/utils/control/infrared/MyInfraredUtils.kt new file mode 100644 index 0000000..19544e0 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/infrared/MyInfraredUtils.kt @@ -0,0 +1,160 @@ +package com.example.iot_controlhost.utils.control.infrared + +import com.example.iot_controlhost.model.controller.AirConditionInfrared +import com.example.iot_controlhost.utils.global.RoomController + +class MyInfraredUtils : AirConditionInfrared("空调红外总控") { + + companion object { + val infraredList = listOf( + RoomController.airConditionInfrared, + RoomController.airConditionInfrared2, + ) + + val instance: MyInfraredUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + MyInfraredUtils() + } + + @JvmStatic + fun getInstances(): MyInfraredUtils { + return instance + } + } + + override fun getState(): String { + return if (infraredList[1].isEnable) { + "空调1:${infraredList[0].getState()}\n" + "空调2:${infraredList[1].getState()}" + } else { + infraredList[0].getState() + } + } + + override fun isPowerSupply(): Boolean { + val result = mutableListOf() + for (infrared in infraredList) { + if (infrared.isEnable) { + result.add(infrared.isPowerSupply()) + } + } + // 如果所有的空调都是开机状态,返回true + return result.all { it } + } + + override fun getMode(): Int { + // 因为一起控制,所以只需要返回第一个的模式即可 + return infraredList[0].getMode() + } + + override fun getModel(): String { + val result = mutableListOf() + for (infrared in infraredList) { + if (infrared.isEnable) { + result.add("${infrared.name}:${infrared.getModel()}\n") + } + } + return result.toString() + } + + override fun setAirCondition(newMode: Int, temperature: Int): Boolean { + // 记录当前的模式和温度 用于回退操作 + val curMode = infraredList[0].getMode() + val curTemperature = infraredList[0].getTemperature() + + val result = mutableListOf() + for (infrared in infraredList) { + if (infrared.isEnable) { + result.add(infrared.setAirCondition(newMode, temperature)) + } + } + // 如果有一个操作失败,回退操作 + if (result.any { !it }) { + for (infrared in infraredList) { + infrared.setAirCondition(curMode, curTemperature) + } + return false + } + return true + } + + override fun setPowerSupply(state: Boolean): Boolean { + val curState = infraredList[0].isPowerSupply() + val result = mutableListOf() + for (infrared in infraredList) { + if (infrared.isEnable) { + result.add(infrared.setPowerSupply(state)) + } + } + // 如果有一个操作失败,回退操作 + if (result.any { !it }) { + for (infrared in infraredList) { + infrared.setPowerSupply(curState) + } + return false + } + return true + } + + override fun isEnable(): Boolean { + return infraredList.any { it.isEnable } + } + + override fun setTemperature(temperature: Int) { + // 更新时,更新所有开启的空调 + for (infrared in infraredList) { + if (infrared.isEnable) { + infrared.setTemperature(temperature) + } + } + } + + override fun setMode(mode: Int) { + for (infrared in infraredList) { + if (infrared.isEnable) { + infrared.setMode(mode) + } + } + } + + override fun setAirConditionMode(newMode: Int): Boolean { + val result = mutableListOf() + for (infrared in infraredList) { + if (infrared.isEnable) { + result.add(infrared.setAirConditionMode(newMode)) + } + } + return result.all { it } + } + + override fun setAirConditionTemperature(temperature: Int): Boolean { + // 记录当前的温度 用于回退操作 + val curTemperature = infraredList[0].getTemperature() + + val result = mutableListOf() + for (infrared in infraredList) { + if (infrared.isEnable) { + result.add(infrared.setAirConditionTemperature(temperature)) + } + } + if (result.any { !it }) { + for (infrared in infraredList) { + infrared.setAirConditionTemperature(curTemperature) + } + return false + } + return true + } + + override fun isAvailable(): Boolean { + return infraredList.any { it.isAvailable() } + } + + // 只返回一个空调的内容 + override fun getTemperature(): Int { + return infraredList[0].getTemperature() + } + + override fun getModeName(): String { + return infraredList[0].getModeName() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java new file mode 100644 index 0000000..573604c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/TemperatureController.java @@ -0,0 +1,135 @@ +package com.example.iot_controlhost.utils.control.temperature; + +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.Control; +import com.example.iot_controlhost.utils.control.temperature.strategy.ControlStrategy; +import com.example.iot_controlhost.utils.control.temperature.strategy.FullPowerControl; +import com.example.iot_controlhost.utils.control.temperature.strategy.GearControl; +import com.example.iot_controlhost.utils.control.temperature.strategy.SavingControl; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; + +/** + * @Description 自动模式-控制逻辑-温度 策略模式+工厂方法 + * @Author DuanKaiji + * @CreateTime 2023年11月09日 10:26:30 + */ +public class TemperatureController extends Control { + /** + * 目标温度 + */ + double target = 26; + private static TemperatureController instance; + private ControlStrategy strategy; + + private TemperatureController(ControlStrategy strategy) { + this.strategy = strategy; + } + + public static TemperatureController getInstance() { + return getInstance(getStrategy()); + } + + public static TemperatureController getInstance(ControlStrategy strategy) { + if (instance == null) { + synchronized (TemperatureController.class) { + if (instance == null) { + instance = new TemperatureController(strategy); + } + } + } + return instance; + } + + private static ControlStrategy getStrategy() { + String curMode = AutoModelSet.getAutoTemperatureMode(); + ControlStrategy strategy; + if (curMode.equals(SpinnerList.TEMPERATURE_MODE[0])) { + //高精度模式 + strategy = new FullPowerControl(); + } else if (curMode.equals(SpinnerList.TEMPERATURE_MODE[1])) { + //普通模式 + strategy = new GearControl(); + } else if (curMode.equals(SpinnerList.TEMPERATURE_MODE[2])) { + //节能模式 + strategy = new SavingControl(); + } else { + strategy = new GearControl(); + } + return strategy; + } + + /** + * 设置新自动控制温度算法 + */ + public void refreshStrategy() { + if (start) { + MyLog.auto("切换自动温控参数:" + + "\n-算法" + AutoModelSet.getAutoTemperatureMode() + + "\n-加热方式" + AutoModelSet.getAutoTemperatureHeat() + + "\n-降温方式" + AutoModelSet.getAutoTemperatureCool()); + UserLog.operate("手动:更新温控参数为:" + + "\n-算法" + AutoModelSet.getAutoTemperatureMode() + + "\n-加热方式:" + AutoModelSet.getAutoTemperatureHeat() + + "\n-降温方式:" + AutoModelSet.getAutoTemperatureCool()); + start(); + strategy = getStrategy(); + start = startMethod(); + } + } + + /** + * 控制温度 + */ + private void controlTemperature(double curTemperature, double tarTemperature) { + if (strategy != null) { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> strategy.control(curTemperature, tarTemperature))); + } + } + + @Override + protected boolean startMethod() { + MyLog.auto("开启温度自动控制"); + MyLog.auto("启用" + AutoModelSet.getAutoTemperatureMode() + "算法"); + pollingTask.startPollingTaskOnIOThread(RxTag.AUTO_TEMPERATURE, Variable.AUTO_CHECK_TIME, () -> { + double curTemperature = RoomSensor.getAverageIndoorTemperature(); + if (curTemperature == 0) { + MyLog.autoError("室内温度计为0,有误,停止该次温度自动控制操作"); + return; + } + if (RoomSetting.getMode() == 1) { + target = AutoModelSet.getTargetTemperature(); + } else if (RoomSetting.getMode() == 2) { + target = AIModelSet.getTargetTemperature(); + } + controlTemperature(curTemperature, target); + }); + return true; + } + + /** + * 停止自动控制 + */ + protected void stopMethod() { + UserLog.operate("自动:停止自动控制温度,关闭空调,关闭地暖"); + MyLog.auto("停止自动控制温度————————————————————————————————————————————————————"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + MyUtil.autoControlOperateThird(RoomController.airConditionInfrared); + if (RoomController.airConditionInfrared2.isEnable()) { + MyUtil.autoControlOperateThird(RoomController.airConditionInfrared2); + } + MyUtil.autoControlOperateThird(RoomController.floor); + })); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java new file mode 100644 index 0000000..fe844b0 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/ControlStrategy.java @@ -0,0 +1,189 @@ +package com.example.iot_controlhost.utils.control.temperature.strategy; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +/** + * 所有控制逻辑算法的通用操作 + */ +public abstract class ControlStrategy { + /** + * 空调模式 -1时表示不控制 + */ + int mode = -1; + /** + * 空调温度 -1时表示不控制 + */ + int temp = -1; + /** + * 地暖档位 0时表示不控制 + */ + int gear = 0; + /** + * 提示信息,用于防止重复发送相同的提示日志 + */ + private String tipsTempStr = ""; + /** + * 地暖已经相同操作的次数 + * 初始化为最大次数,即第一次可以执行 + */ + private int sameOperateTimesFloor = Variable.SAME_OPERATE_TIMES; + /** + * 空调已经相同操作的次数 + * 初始化为最大次数,即第一次可以执行 + */ + private int sameOperateTimes = Variable.SAME_OPERATE_TIMES; + + /** + * 温控模式 + * + * @param curTemperature 当前温度 + * @param tarTemperature 目标温度 + * @return 主界面提示信息 + */ + protected abstract void controlAlgorithm(double curTemperature, double tarTemperature); + + /** + * 外部控制 + */ + public void control(double curTemperature, double tarTemperature) { + init(); + controlAlgorithm(curTemperature, tarTemperature); + operate(); + } + + /** + * 初始化控制方式 + * 均不控制 + */ + public void init() { + mode = -1; + temp = -1; + gear = 0; + } + + boolean canOperateFloor() { + if (gear == RoomController.floor.getGear()) { + if (sameOperateTimesFloor >= Variable.SAME_OPERATE_TIMES) { + sameOperateTimesFloor = 0; + } else { + sameOperateTimesFloor++; +// MyLog.test("地暖相同控制,无需再发送指令"); + return false; + } + } else { + sameOperateTimesFloor = 0; + } + if (gear > RoomController.floor.getMaxGear()) { + //自动模式控制算法要求的地暖档位大于地暖设定档位 重定向档位 + MyLog.auto("温控算法需要的" + RoomSetting.getFloorName() + "档位大于预设最大档位,现重定档至" + RoomController.floor.getMaxGear() + "档"); + gear = RoomController.floor.getMaxGear(); + } + if (!RoomSensor.dynamicFloorTSensorList.isEmpty() && RoomSensor.getFloorTemperature() > AutoModelSet.getAutoFlorMaxTemp()) { + //地面温度过高 重定向至档位0 + MyLog.autoError(RoomSetting.getFloorName() + "地面温度过高,大于" + AutoModelSet.getAutoFlorMaxTemp() + "℃,停止操作"); + gear = 0; + } + return true; + } + + boolean canOperateAir() { + boolean state = mode != -1 && temp != -1; + if (!state && !MyInfraredUtils.getInstances().isPowerSupply() || + state && MyInfraredUtils.getInstances().isPowerSupply() + && MyInfraredUtils.getInstances().getMode() == mode + && MyInfraredUtils.getInstances().getTemperature() == temp) { + //一样的操作 + if (!RoomSetting.isAirRecycleControl()) { + // 相同操作,不需要重复发送的情况 + return false; + } + // 相同操作,需要重复发送的情况 + if (sameOperateTimes >= Variable.SAME_OPERATE_TIMES) { + //已经大于可重复操作的次数,开始操作 +// MyLog.test("空调相同控制,已经大于可重复操作的次数"); + sameOperateTimes = 0; + } else { + sameOperateTimes++; +// MyLog.test("空调相同控制,无需再发送指令"); + return false; + } + } else { + // 不一样的操作 + sameOperateTimes = 0; + } + return true; + } + + public void operate() { + StringBuffer tips = new StringBuffer(""); + boolean operateSuccess = true; + boolean needSentLog = canSentLog(); + if (canOperateAir()) { + if (mode == -1 || temp == -1) { + tips.append("关闭空调\t"); + operateSuccess = MyInfraredUtils.getInstances().setPowerSupply(false) && operateSuccess; + } else { + operateSuccess = MyInfraredUtils.getInstances().setAirCondition(mode, temp) && operateSuccess; + tips.append("空调" + MyInfraredUtils.getInstances().getState() + "\t"); + } + } + if (canOperateFloor()) { + if (gear == 0) { + tips.append(" 关闭" + RoomSetting.getFloorName()); + operateSuccess = RoomController.floor.setPowerSupply(false) && operateSuccess; + } else { + tips.append(" 开启" + RoomSetting.getFloorName() + gear + "档"); + operateSuccess = RoomController.floor.setGear(gear) && operateSuccess; + } + } + if (!StringUtils.isEmpty(tips) && needSentLog) { + //更新自动模式-温控-界面UI + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 2); + if (operateSuccess) { + if (!tipsTempStr.equals(tips.toString())) { + //防止重复发送相同的提示日志 + tipsTempStr = tips.toString(); + UserLog.operate("自动:" + tips); + } + } else { + if (mode != -1 && temp != -1 && RoomSetting.isAirConditionForceClose()) { + tips.append(",在消毒期间无法开启空调"); + } + UserLog.operateError("自动操作失败:" + tips); + } + } + } + + + /** + * 能否发送用户日志 + * + * @return 只有在空调地暖状态改变时才发送true,即如果命令一直相同,则不发送false + */ + private boolean canSentLog() { + return !(//地暖 + //地暖一直关着 + (gear == -1 || gear == 0) && !RoomController.floor.isPowerSupply() + //地暖一直开的 + || gear > 0 && gear == RoomController.floor.getGear()) + || !(//空调 + //空调一直关着 + (mode == -1 || temp == -1) && !MyInfraredUtils.getInstances().isPowerSupply() + //空调一直开着 并且温度模式相同 + || mode == MyInfraredUtils.getInstances().getMode() + && temp == MyInfraredUtils.getInstances().getTemperature() + && MyInfraredUtils.getInstances().isPowerSupply() + ); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/FullPowerControl.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/FullPowerControl.java new file mode 100644 index 0000000..331c0a5 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/FullPowerControl.java @@ -0,0 +1,103 @@ +package com.example.iot_controlhost.utils.control.temperature.strategy; + +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description 高精度模式,与GearControl相同,只是温差范围不同,以用户自定义优先(普通模式不可设置过小的温差) + * @Author DuanKaiji + * @CreateTime 2023年10月24日 09:36:38 + */ +public class FullPowerControl extends ControlStrategy { + private static boolean needHeatToTarget = false; + private static boolean needColdToTarget = false; + + @Override + public void controlAlgorithm(double curTemperature, double tarTemperature) { + double max = AutoModelSet.getMaxTemperature(); + double min = AutoModelSet.getMinTemperature(); + String autoTemperatureHeat = AutoModelSet.getAutoTemperatureHeat(); + String autoTemperatureCool = AutoModelSet.getAutoTemperatureCool(); + double outDoorTemperature = RoomSensor.outdoorTHSensor.temperature; + if (curTemperature < tarTemperature - Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL + || curTemperature < min //用户自定义最低温度 + || curTemperature < tarTemperature && needHeatToTarget) { + MyLog.auto("高精度模式:当前温度已低于目标值,开启最大档位升温"); + needHeatToTarget = true; + if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[1])) { + //地暖 + gear = 5; + MyLog.auto("高精度模式:开启地暖最大档位"); + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[2])) { + //空调 + mode = 0; + temp = 30; + MyLog.auto("高精度模式:开启空调制热30度"); + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[3])) { + //空调和地暖 + mode = 0; + temp = 30; + gear = 5; + MyLog.auto("高精度模式:开启空调制热30度与地暖5档"); + } else { + //均不可用 + MyLog.auto("高精度模式:理应升温,但升温设备设置为‘均不可用’"); + } + } else if (curTemperature > tarTemperature + Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL + || curTemperature > max // 用户自定义最大温度 + || curTemperature > tarTemperature && needColdToTarget) { + MyLog.auto("高精度模式:当前温度已高于目标值,即将开启最大档位降温"); + needColdToTarget = true; + if (autoTemperatureCool.equals(SpinnerList.TEMPERATURE_COOL[1])) { + //空调 + mode = 2; + temp = 17; + MyLog.auto("高精度模式:开启空调制冷17度"); + } else { + //均不可用 + MyLog.auto("高精度模式:理应降温,但降温设备设置为‘均不可用’"); + } + } else { + //是目标范围温度 + needHeatToTarget = false; + needColdToTarget = false; + //地暖逻辑 + if ((autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[1]) || autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[3]))//可以用地暖 + && curTemperature < tarTemperature) {//并且:当前温度低于目标温度 + //保温-重定向地暖档位-根据与目标温差 + double bias = tarTemperature - curTemperature; + if (bias > 0.7) { + gear = 4; + } else if (bias >= 0.5) { + gear = 3; + } else if (bias >= 0.4) { + gear = 2; + } else if (bias >= 0.3) { + gear = 1; + } + //保温-重定向地暖档位-根据室内外温差 + if (RoomSensor.outdoorTHSensor.isAvailable()) { + if (curTemperature > outDoorTemperature + 3) { + if (curTemperature > outDoorTemperature + 10 && gear <= 1) { + //室外温度 < 室内温度10度及以上,地暖2档保温 + gear = 2; + } else if (curTemperature > outDoorTemperature + 3 && gear == 0) { + //室外温度 < 室内温度3度及以上,地暖1档保温 + gear = 1; + MyLog.auto("高精度模式:室外温度与室内温度偏差3度及以上," + RoomSetting.getFloorName() + "1档保温"); + } + } else if (curTemperature < outDoorTemperature - 2) { + //室外温度 > 室内温度2度及以上,地暖不保温 + gear = 0; + MyLog.auto("高精度模式:室外温度比室内高2度或以上," + RoomSetting.getFloorName() + "关闭"); + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/GearControl.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/GearControl.java new file mode 100644 index 0000000..5984098 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/GearControl.java @@ -0,0 +1,96 @@ +package com.example.iot_controlhost.utils.control.temperature.strategy; + +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description 按档位控制 温差范围为Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL + * 需要考虑室内外温度 保温逻辑 + * @Author DuanKaiji + * @CreateTime 2023年10月24日 09:36:38 + */ +public class GearControl extends ControlStrategy { + private static boolean needHeatToTarget = false; + private static boolean needColdToTarget = false; + + @Override + public void controlAlgorithm(double curTemperature, double targetTemperature) { + String autoTemperatureHeat = AutoModelSet.getAutoTemperatureHeat(); + String autoTemperatureCool = AutoModelSet.getAutoTemperatureCool(); + if (curTemperature < targetTemperature - Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL + || curTemperature < targetTemperature && needHeatToTarget) { + MyLog.auto("普通模式:正在升温"); + needHeatToTarget = true; + if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[0])) { + //均不可用 + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[1])) { + //地暖 + gear = 5; + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[2])) { + //空调 + temp = 30; + mode = 0; + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[3])) { + //空调和地暖 + temp = 30; + mode = 0; + gear = 5; + } + } else if (curTemperature > targetTemperature + Variable.AUTO_DO_NOTHING_TEMPERATURE_NORMAL + || curTemperature > targetTemperature && needColdToTarget) { + MyLog.auto("普通模式:正在降温"); + needColdToTarget = true; + if (autoTemperatureCool.equals(SpinnerList.TEMPERATURE_COOL[1])) { + //空调 + mode = 2; + temp = 17; + MyLog.auto("高精度模式:开启空调制冷17度"); + } else { + //均不可用 + MyLog.auto("高精度模式:理应降温,但降温设备设置为‘均不可用’"); + } + } else { + //在正常范围内 + needHeatToTarget = false; + needColdToTarget = false; + // 地暖逻辑 2024年9月9日 13:32:21 去掉普通模式地暖保温逻辑 +// double outDoorTemperature = RoomSensor.outdoorTHSensor.getTemperature(); +// if (//可以用地暖 +// (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[1]) || autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[3])) +// //并且:当前温度低于目标温度 +// && curTemperature < targetTemperature) { +// //保温-重定向地暖档位-根据与目标温差 +// double bias = targetTemperature - curTemperature; +// if (bias > 0.7) { +// gear = 4; +// } else if (bias >= 0.5) { +// gear = 3; +// } else if (bias >= 0.4) { +// gear = 2; +// } else if (bias >= 0.3) { +// gear = 1; +// } +// //保温-重定向地暖档位-根据室内外温差 +// if (curTemperature > outDoorTemperature + 3) { +// if (curTemperature > outDoorTemperature + 10 && gear <= 1) { +// //室外温度与室内温度偏差10度及以上,地暖2档保温 +// gear = 2; +// } else if (curTemperature > outDoorTemperature + 3 && gear == 0) { +// //室外温度与室内温度偏差3度及以上,地暖1档保温 +// gear = 1; +// MyLog.auto("普通模式:室外温度与室内温度偏差3度及以上," + RoomSetting.getFloorName() + "1档保温"); +// } +// } else if (curTemperature < outDoorTemperature - 5) { +// //比室外温度低3度以上 +// gear = 0; +// MyLog.auto("普通模式:室外温度比室内高5度或以上," + RoomSetting.getFloorName() + "关闭"); +// } +// } + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/SavingControl.java b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/SavingControl.java new file mode 100644 index 0000000..cda7d36 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/temperature/strategy/SavingControl.java @@ -0,0 +1,61 @@ +package com.example.iot_controlhost.utils.control.temperature.strategy; + +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.global.Variable; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description 节能模式 更宽泛的温度区间 无保温措施 + * @Author DuanKaiji + * @CreateTime 2023年10月24日 09:36:38 + */ +public class SavingControl extends ControlStrategy { + private static boolean needHeatToTarget = false; + private static boolean needColdToTarget = false; + + @Override + public void controlAlgorithm(double curTemperature, double targetTemperature) { + String autoTemperatureHeat = AutoModelSet.getAutoTemperatureHeat(); + String autoTemperatureCool = AutoModelSet.getAutoTemperatureCool(); + if (curTemperature < targetTemperature - Variable.AUTO_DO_NOTHING_TEMPERATURE_SAVING + || curTemperature < targetTemperature && needHeatToTarget) { + MyLog.auto("节能模式:正在升温"); + needHeatToTarget = true; + if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[0])) { + //均不可用 + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[1])) { + //地暖 + gear = 5; + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[2])) { + //空调 + temp = 30; + mode = 0; + } else if (autoTemperatureHeat.equals(SpinnerList.TEMPERATURE_HEAT[3])) { + //空调和地暖 + temp = 30; + mode = 0; + gear = 5; + } + } else if (curTemperature > targetTemperature + Variable.AUTO_DO_NOTHING_TEMPERATURE_SAVING + || curTemperature > targetTemperature && needColdToTarget) { + MyLog.auto("节能模式:正在降温"); + needColdToTarget = true; + if (autoTemperatureCool.equals(SpinnerList.TEMPERATURE_COOL[1])) { + //空调 + mode = 2; + temp = 17; + MyLog.auto("高精度模式:开启空调制冷17度"); + } else { + //均不可用 + MyLog.auto("高精度模式:理应降温,但降温设备设置为‘均不可用’"); + } + } else { + //在正常范围内 + needHeatToTarget = false; + needColdToTarget = false; + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java b/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java new file mode 100644 index 0000000..633753f --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/control/ventilator/VentilatorController.java @@ -0,0 +1,117 @@ +package com.example.iot_controlhost.utils.control.ventilator; + +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.Control; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.global.SpinnerList; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description 自动模式下换气扇控制 + * @Author DuanKaiji + * @CreateTime 2023年11月09日 10:26:30 + */ +public class VentilatorController extends Control { + /** + * 开启持续时间 + */ + int targetOpen; + /** + * 关闭持续时间 + */ + int targetClose; + + /** + * 单例模式 + */ + private static VentilatorController instance; + + public static VentilatorController getInstance() { + if (instance == null) { + synchronized (VentilatorController.class) { + if (instance == null) { + instance = new VentilatorController(); + } + } + } + return instance; + } + + @Override + protected boolean startMethod() { + //每次开启时获取最新参数 + refreshRule(); + MyLog.auto("自动:首先关闭换气扇"); + stopFan(0); + return true; + } + + private void refreshRule() { + if (RoomSetting.getMode() == 1) { + targetOpen = AutoModelSet.getCycleStart(); + targetClose = AutoModelSet.getCycleStop(); + } else if (RoomSetting.getMode() == 2) { + targetOpen = AIModelSet.getAiCycleStart(); + targetClose = AIModelSet.getAiCycleStop(); + } + } + + private void startFan(int minutes) { + MyLog.auto("自动:" + minutes + "分钟后开启换气扇"); + pollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_START_FUN, minutes * 60, () -> { + refreshRule(); + if (targetOpen == 0 && targetClose == 0) { + //如果开始停止时间均为0,则3分钟后再次检测配置项 + MyLog.auto("开始停止时间均为0,3分钟后再次检测配置项"); + startFan(3); + return; + } + // 开启换气扇 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + String airExchangeMode = AutoModelSet.getAirExchangeMode(); + RoomController.fan.setPowerSupply(true); + if (RoomController.airExchange.isAvailable() && airExchangeMode.equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { + MyUtil.autoControlOperateThird(RoomController.airExchange, true); + } + })); + MyLog.auto("自动:" + targetOpen + "分钟后关闭换气扇"); + stopFan(targetOpen); + }); + } + + /** + * 关闭风扇 + * + * @param minutes 该时间后检测下一步动作 + */ + private void stopFan(int minutes) { + // 开启换气扇,持续targetOpen分钟 + pollingTask.startDelayedTask(RxTag.VENTILATOR_AUTO_STOP_FUN, minutes * 60, () -> { + // 关闭换气扇 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + if (RoomController.airExchange.isAvailable() && AutoModelSet.getAirExchangeMode().equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { + MyUtil.autoControlOperateThird(RoomController.airExchange, false); + } + RoomController.fan.setPowerSupply(false); + })); + startFan(targetClose); + }); + } + + @Override + protected void stopMethod() { + MyLog.auto("停止自动控制换气————————————————————————————————————————————————————"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + MyUtil.autoControlOperateThird(RoomController.fan); + if (RoomController.airExchange.isAvailable() && AutoModelSet.getAirExchangeMode().equals(SpinnerList.AIR_EXCHANGE_MODES[1])) { + MyUtil.autoControlOperateThird(RoomController.airExchange, false); + } + })); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/AITHModeDBManager.java b/app/src/main/java/com/example/iot_controlhost/utils/database/AITHModeDBManager.java new file mode 100644 index 0000000..123fe53 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/AITHModeDBManager.java @@ -0,0 +1,60 @@ +package com.example.iot_controlhost.utils.database; + + +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.model.AITHMode; +import com.example.iot_controlhost.model.AITHModeDao; + +import java.util.List; + +public class AITHModeDBManager { + private static AITHModeDao aithModeDao = MyApp.getDaoSession().getAITHModeDao(); + + /** + * 增加 + */ + public static void insert(AITHMode AITHMode) { + aithModeDao.insert(AITHMode); + } + + /** + * 增加一整个模板 + * 要先情况之前的模板,然后再添加新的 + */ + public static void insert(List aithModes) { + aithModeDao.deleteAll(); + aithModeDao.insertInTx(aithModes); + } + + /** + * 删除 + */ + public static void remove(Long id) { + aithModeDao.deleteByKey(id); + } + + public static void removeAll() { + aithModeDao.deleteAll(); + } + + /** + * 根据天数和蚕种名获取AITHMode + */ + public static List getAllSilkworms() { + return aithModeDao.queryBuilder().list(); + } + + /** + * 改 + */ + public static void update(AITHMode AITHMode) { + aithModeDao.update(AITHMode); + } + + /** + * 删除指定蚕种名的所有数据 + */ + public static void removeBySilkworm(String silkwormName) { + + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/BaseTempDataBase.kt b/app/src/main/java/com/example/iot_controlhost/utils/database/BaseTempDataBase.kt new file mode 100644 index 0000000..709002a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/BaseTempDataBase.kt @@ -0,0 +1,29 @@ +package com.bbitcn.sericulture.base + +import com.blankj.utilcode.util.GsonUtils +import com.example.iot_controlhost.utils.MMKVUtil +import com.google.gson.Gson +import java.lang.reflect.Type + +/** + * + * @Description 临时数据库(单实体,列表请用@BaseListTempDataBase) + * @Author DuanKaiji + * @CreateTime 2024年08月06日 11:39:31 + */ +abstract class BaseTempDataBase { + + abstract fun getKey(): String + abstract fun defaultData(): T + abstract fun getType(): Type + + fun init(value :T) { + MMKVUtil.put(getKey(), Gson().toJson(value)) + } + + fun getData(): T { + val json = MMKVUtil.get(getKey(), GsonUtils.toJson(defaultData())) + return Gson().fromJson(json, getType()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/EnergyDBManager.java b/app/src/main/java/com/example/iot_controlhost/utils/database/EnergyDBManager.java new file mode 100644 index 0000000..803bfcf --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/EnergyDBManager.java @@ -0,0 +1,109 @@ +package com.example.iot_controlhost.utils.database; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.model.EnergyRecord; +import com.example.iot_controlhost.model.EnergyRecordDao; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.model.LogDao; +import com.example.iot_controlhost.model.sensor.ConsumptionSensor; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import org.greenrobot.greendao.query.QueryBuilder; +import org.greenrobot.greendao.query.WhereCondition; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class EnergyDBManager { + /** + * 操作类 + */ + private static EnergyRecordDao energyDao = MyApp.getDaoSession().getEnergyRecordDao(); + + private static ExecutorService executor = Executors.newCachedThreadPool(); + + /** + * 增加 + */ + public static void insert(EnergyRecord energyRecord) { + executor.execute(() -> energyDao.insert(energyRecord)); + } + + + /** + * 删除 + */ + public static void remove(Long id) { + energyDao.deleteByKey(id); + } + + public static Double queryMaxDate() { + QueryBuilder qb = energyDao.queryBuilder(); + List data = qb.orderDesc(EnergyRecordDao.Properties.Datetime).limit(10).list(); + if (!data.isEmpty()) { + if(data.get(0).getEnergy() == 0) { + MyLog.test("!!!queryMaxDate: data is empty: " + data.size()); + return 0.0; + } + return data.get(0).getEnergy(); + } + MyLog.test("queryMaxDate: data is empty: " + data.size()); + return 0.0; + } + public static Double queryDateRange(Long startDate, Long endDate) { + List data = energyDao.queryBuilder() + .where(EnergyRecordDao.Properties.Datetime.between(new Date(startDate), new Date(endDate))) + .orderAsc(EnergyRecordDao.Properties.Datetime) + .list(); + + if (data.isEmpty()) return 0.0; + + double earliestValue = data.get(0).getEnergy(); + double latestValue = data.get(data.size() - 1).getEnergy(); + double result = latestValue - earliestValue; + return result < 0 ? 0.0 : result; + } + + /** + * 获取最后记录能耗的时间 + */ + public static Date queryLastRecordDate() { + QueryBuilder qb = energyDao.queryBuilder(); + List data = qb.orderDesc(EnergyRecordDao.Properties.Datetime).limit(1).list(); + if (!data.isEmpty()) { + return data.get(0).getDatetime(); + } + return null; + } + + /** + * 删除2个月前的日志 + */ + public static void clearLogsOlderThanThreeMonths() { + RxJavaUtils.doInIOThread(new RxIOTask(null) { + @Override + public Void doInIOThread(Object o) { + try { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MONTH, -2); + Date threeMonthsAgo = calendar.getTime(); + energyDao.queryBuilder() + .where(LogDao.Properties.Datetime.lt(threeMonthsAgo)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java b/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java new file mode 100644 index 0000000..a43adba --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/LogDBManager.java @@ -0,0 +1,142 @@ +package com.example.iot_controlhost.utils.database; + +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.model.Log; +import com.example.iot_controlhost.model.LogDao; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import org.greenrobot.greendao.query.QueryBuilder; +import org.greenrobot.greendao.query.WhereCondition; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class LogDBManager { + + private static ExecutorService executor = Executors.newCachedThreadPool(); + + /** + * 增加 + */ + public static void insertLog(Log log) { + executor.execute(() -> logDao.insert(log)); + } + + /** + * 每页的长度 + */ + private static int pageSize = 20; + /** + * 日志操作类 + */ + private static LogDao logDao = MyApp.getDaoSession().getLogDao(); + + /** + * 删除 + */ + public static void removeLog(Long id) { + logDao.deleteByKey(id); + } + + public static void removeAll() { + logDao.deleteAll(); + } + + public static List queryDateRange(Date startDate, Date endDate) { + QueryBuilder qb = logDao.queryBuilder(); + // 创建日期区间查询条件 + WhereCondition dateCondition = LogDao.Properties.Datetime.between(startDate, endDate); + QueryBuilder dateRangeQueryBuilder = qb.where(dateCondition); + return dateRangeQueryBuilder.list(); + } + + + /** + * 返回某类型的所有日志 + * + * @param tagType MyLog类下的TAG常量 为空时返回全部 + */ + public static List queryList(String tagType, int currentPage) { + QueryBuilder qb = logDao.queryBuilder(); + if (!StringUtils.isEmpty(tagType)) { + qb.where(LogDao.Properties.Tag.like("%" + tagType + "%")); + }else{ + qb.whereOr( + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_OPERATE + "%"), + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SENSOR + "%"), + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_TASK + "%"), + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SYSTEM + "%") + ); + } + return qb.offset((currentPage - 1) * pageSize) + .limit(pageSize) + .orderDesc(LogDao.Properties.Datetime) + .list(); + } + + /** + * 返回某类型的所有日志 + */ + public static List queryAllList() { + QueryBuilder qb = logDao.queryBuilder(); + Calendar calendar = Calendar.getInstance(); + Date currentDate = calendar.getTime(); + calendar.add(Calendar.DAY_OF_YEAR, -7); + Date oneWeekAgo = calendar.getTime(); + return qb.orderDesc(LogDao.Properties.Datetime) + .where(LogDao.Properties.Datetime.between(oneWeekAgo, currentDate)) + .whereOr( + LogDao.Properties.Tag.like("%" + MyLog.TAG_APP + "%"), + LogDao.Properties.Tag.like("%" + MyLog.TAG_NETWORK + "%") + ) + .list(); + } + + public static List queryWorkLogs(int currentPage) { + return logDao.queryBuilder() + .orderDesc(LogDao.Properties.Datetime) + .whereOr( + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_OPERATE + "%"), + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SENSOR + "%"), + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_TASK + "%"), + LogDao.Properties.Tag.like("%" + UserLog.TAG_USER_SYSTEM + "%") + ) + .offset((currentPage - 1) * pageSize) + .limit(pageSize) + .list(); + } + + /** + * 改 + */ + public static void update(Log log) { + logDao.update(log); + } + + /** + * 删除1个月前的日志 + */ + public static void clearLogsOlderThanOneMonthsSync() { + try { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.MONTH, -1); + Date oneMonthAgo = calendar.getTime(); + + logDao.queryBuilder() + .where(LogDao.Properties.Datetime.lt(oneMonthAgo)) + .buildDelete() + .executeDeleteWithoutDetachingEntities(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/MessageReadUtil.kt b/app/src/main/java/com/example/iot_controlhost/utils/database/MessageReadUtil.kt new file mode 100644 index 0000000..92c28c0 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/MessageReadUtil.kt @@ -0,0 +1,40 @@ +package com.example.iot_controlhost.utils.database + +import com.example.iot_controlhost.utils.MMKVUtil +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +/** + * 消息已读工具类 + */ +object MessageReadUtil { + + const val MESSAGE_READ: String = "MESSAGE_READ" + + private val defaultJson: String by lazy { + Gson().toJson(mapOf()) + } + + fun init() { + MMKVUtil.put(MESSAGE_READ, defaultJson) + } + + fun readMessage(messageId: String) { + val json = MMKVUtil.get(MESSAGE_READ, defaultJson) + val temp = Gson().fromJson>( + json, object : TypeToken>() {}.getType() + ) + temp.put(messageId, true) + MMKVUtil.put(MESSAGE_READ, Gson().toJson(temp)) + } + + fun isRead(messageId: String): Boolean { + val json = MMKVUtil.get(MESSAGE_READ, defaultJson) + val temp = Gson().fromJson>( + json, + object : TypeToken>() {}.getType() + ) + return temp.getOrDefault(messageId, false) + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/MyAIModeUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/database/MyAIModeUtil.java new file mode 100644 index 0000000..ea57f51 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/MyAIModeUtil.java @@ -0,0 +1,48 @@ +package com.example.iot_controlhost.utils.database; + +import com.example.iot_controlhost.model.AITHMode; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.List; + +/** + * 农户模板工具类 + */ +public class MyAIModeUtil { + + public static final String MY_AI_MODE = "MY_AI_MODE"; + + public static void initMyAiMode(List temp) { + MMKVUtil.put(MY_AI_MODE, new Gson().toJson(temp)); + } + + public static List getMyAiMode() { + String json = MMKVUtil.get(MY_AI_MODE, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public static void update(AITHMode aithMode) { + String json = MMKVUtil.get(MY_AI_MODE, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + for (AITHMode mode : temp) { + if (mode.getSort() == aithMode.getSort()) { + mode.setTargetTemp(aithMode.getTargetTemp()); + mode.setTargetHumi(aithMode.getTargetHumi()); + mode.setTargetVentOpen(aithMode.getTargetVentOpen()); + mode.setTargetVentClose(aithMode.getTargetVentClose()); + break; + } + } + MMKVUtil.put(MY_AI_MODE, new Gson().toJson(temp)); + } + + public static void removeMyAiMode() { + MMKVUtil.put(MY_AI_MODE, "[]"); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/SilkwormDBManager.java b/app/src/main/java/com/example/iot_controlhost/utils/database/SilkwormDBManager.java new file mode 100644 index 0000000..a805559 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/SilkwormDBManager.java @@ -0,0 +1,103 @@ +package com.example.iot_controlhost.utils.database; + + +import com.bbitcn.sericulture.base.BaseTempDataBase; +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormDao; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.utils.DateUtils; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.log.MyLog; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import org.greenrobot.greendao.query.QueryBuilder; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Type; +import java.util.Date; +import java.util.List; + +public class SilkwormDBManager extends BaseTempDataBase { + + public static SilkwormDBManager INSTANCE = new SilkwormDBManager(); + + @Override + public @NotNull String getKey() { + return "SILKWORM_TEMP_DB"; + } + + @Override + public SilkwormNew defaultData() { + return new SilkwormNew(); + } + + @Override + public @NotNull Type getType() { + return new TypeToken() { + }.getType(); + } + + + public static final String SILKWORM_TEMP_DB_IS_INIT = "SILKWORM_TEMP_DB_IS_INIT"; + + + public static void initSK() { + if (MMKVUtil.get(SILKWORM_TEMP_DB_IS_INIT, false)) { + return; + } + QueryBuilder qb = MyApp.getDaoSession().getSilkwormDao().queryBuilder(); + qb.orderDesc(SilkwormDao.Properties.Id); + qb.limit(1); + Silkworm old = qb.unique(); + if (old != null && old.getEndDate() == null) { + // 如果数据库有数据,则迁移到现在的临时数据库 + INSTANCE.init(new SilkwormNew(MMKVUtil.get("CULTIVATE_CODE"), old.getName(), old.getStartDate(), old.getEndDate(), old.getNumber(), old.getVariety())); + } + MMKVUtil.put(SILKWORM_TEMP_DB_IS_INIT, true); + } + + + //--------------------------------------------------- + + public static SilkwormNew getLatestSilkworm() { + return INSTANCE.getData(); + } + + /** + * 当前是否是共育 + */ + public static boolean isStarting() { + SilkwormNew silkworm = INSTANCE.getData(); + // kotlin不会返回null,所以这里不能判断silkworm !=null 而是要用 silkworm.getId()!=null + if (silkworm.getId() != null && silkworm.getEndDate() == null) { + return true; + } + return false; + } + + /** + * 获取当前共育第几天 + */ + public static int getStartToNowDays() { + if (isStarting()) { + return DateUtils.daysFromTodayInclusive(getLatestSilkworm().getStartDate()); + } else { + return -1; + } + } + + /** + * 改 + */ + public static void updateCurDate() { + SilkwormNew silkwormNew = INSTANCE.getData(); + silkwormNew.setEndDate(new Date()); + INSTANCE.init(silkwormNew); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/TempTHVDBManager.kt b/app/src/main/java/com/example/iot_controlhost/utils/database/TempTHVDBManager.kt new file mode 100644 index 0000000..d367388 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/TempTHVDBManager.kt @@ -0,0 +1,46 @@ +package com.example.iot_controlhost.utils.database + +import com.bbitcn.sericulture.base.BaseTempDataBase +import com.blankj.utilcode.util.GsonUtils +import com.example.iot_controlhost.model.net.ControlMode +import com.example.iot_controlhost.utils.MMKVUtil +import com.example.iot_controlhost.utils.global.AutoModelSet +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.tencent.mmkv.MMKV +import java.lang.reflect.Type + +object TempTHVDBManager : BaseTempDataBase>() { + + fun appSet() { + for (data in getData()) { + MMKVUtil.put(data.key, data.value) + } + } + + override fun getKey(): String = "control_mode_map1" + + override fun defaultData(): Map { + return mapOf() + } + + override fun getType(): Type { + return object : TypeToken>() {}.type + } + + val STAGE_ID_NAME = "stage_id_name" + val STAGE_ID_CONTENT = "stage_id_content" + + fun setStage(name: String?,content: String?) { + MMKVUtil.put(STAGE_ID_NAME, name) + MMKVUtil.put(STAGE_ID_CONTENT, content) + } + + fun getStageName(): String? { + return MMKVUtil.get(STAGE_ID_NAME, null) + } + fun getStageContent(): String? { + return MMKVUtil.get(STAGE_ID_CONTENT, null) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorAmmoniaSensorUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorAmmoniaSensorUtil.java new file mode 100644 index 0000000..256a309 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorAmmoniaSensorUtil.java @@ -0,0 +1,97 @@ +package com.example.iot_controlhost.utils.database.dynamicSensor; + +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.List; + +/** + * 农户模板工具类 + */ +public class IndoorAmmoniaSensorUtil { + + public static final String INDOOR_GAS_AMMONIA_SENSOR = "INDOOR_GAS_AMMONIA_SENSOR"; + + public static String[] getAllNames() { + String json = MMKVUtil.get(INDOOR_GAS_AMMONIA_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + String[] names = new String[temp.size()]; + for (int i = 0; i < temp.size(); i++) { + names[i] = temp.get(i).getName(); + } + return names; + } + + public static List getAllSensors() { + String json = MMKVUtil.get(INDOOR_GAS_AMMONIA_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public static boolean insert(GasSensor sensor) { + String json = MMKVUtil.get(INDOOR_GAS_AMMONIA_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + //判断是否已经存在同名传感器 + if (MyUtil.checkDeviceNameAvailable(sensor.getName())) { + temp.add(sensor); + MMKVUtil.put(INDOOR_GAS_AMMONIA_SENSOR, new Gson().toJson(temp)); + return true; + } + return false; + } + + public static void update(Sensor sensor) { + String json = MMKVUtil.get(INDOOR_GAS_AMMONIA_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (GasSensor mode : temp) { + if (mode.getName().equals(sensor.getName())) { + mode.setEnable(sensor.isEnable()); + mode.setModel(sensor.getModel()); + mode.setAddress(sensor.getAddress()); + break; + } + } + } + MMKVUtil.put(INDOOR_GAS_AMMONIA_SENSOR, new Gson().toJson(temp)); + } + + public static void deleteByName(String name) { + String json = MMKVUtil.get(INDOOR_GAS_AMMONIA_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (GasSensor sensor : temp) { + if (sensor.getName().equals(name)) { + sensor.clearSetting(); + temp.remove(sensor); + MMKVUtil.put(INDOOR_GAS_AMMONIA_SENSOR, new Gson().toJson(temp)); + return; + } + } + } + } + + public static boolean hasSensor(String name) { + String json = MMKVUtil.get(INDOOR_GAS_AMMONIA_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (GasSensor sensor : temp) { + if (sensor.getName().equals(name)) { + return true; + } + } + } + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorCO2SensorUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorCO2SensorUtil.java new file mode 100644 index 0000000..d59d420 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorCO2SensorUtil.java @@ -0,0 +1,96 @@ +package com.example.iot_controlhost.utils.database.dynamicSensor; + +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.List; + +/** + * 农户模板工具类 + */ +public class IndoorCO2SensorUtil { + + public static final String INDOOR_GAS_CO2_SENSOR = "INDOOR_GAS_CO2_SENSOR0"; + + public static String[] getAllNames() { + String json = MMKVUtil.get(INDOOR_GAS_CO2_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + String[] names = new String[temp.size()]; + for (int i = 0; i < temp.size(); i++) { + names[i] = temp.get(i).getName(); + } + return names; + } + + public static List getAllSensors() { + String json = MMKVUtil.get(INDOOR_GAS_CO2_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public static boolean insert(GasSensor sensor) { + String json = MMKVUtil.get(INDOOR_GAS_CO2_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + //判断是否已经存在同名传感器 + if (MyUtil.checkDeviceNameAvailable(sensor.getName())) { + temp.add(sensor); + MMKVUtil.put(INDOOR_GAS_CO2_SENSOR, new Gson().toJson(temp)); + return true; + } + return false; + } + + public static void update(Sensor sensor) { + String json = MMKVUtil.get(INDOOR_GAS_CO2_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (GasSensor mode : temp) { + if (mode.getName().equals(sensor.getName())) { + mode.setEnable(sensor.isEnable()); + mode.setModel(sensor.getModel()); + mode.setAddress(sensor.getAddress()); + break; + } + } + } + MMKVUtil.put(INDOOR_GAS_CO2_SENSOR, new Gson().toJson(temp)); + } + + public static void deleteByName(String name) { + String json = MMKVUtil.get(INDOOR_GAS_CO2_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (GasSensor sensor : temp) { + if (sensor.getName().equals(name)) { + sensor.clearSetting(); + temp.remove(sensor); + MMKVUtil.put(INDOOR_GAS_CO2_SENSOR, new Gson().toJson(temp)); + return; + } + } + } + } + + public static boolean hasSensor(String name) { + String json = MMKVUtil.get(INDOOR_GAS_CO2_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (GasSensor sensor : temp) { + if (sensor.getName().equals(name)) { + return true; + } + } + } + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorFloorTSensorUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorFloorTSensorUtil.java new file mode 100644 index 0000000..ccb7575 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorFloorTSensorUtil.java @@ -0,0 +1,96 @@ +package com.example.iot_controlhost.utils.database.dynamicSensor; + +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.FloorTSensor; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.List; + +/** + * 农户模板工具类 + */ +public class IndoorFloorTSensorUtil { + + public static final String INDOOR_FLOOR_T_SENSOR = "INDOOR_FLOOR_T_SENSOR"; + + public static String[] getAllNames() { + String json = MMKVUtil.get(INDOOR_FLOOR_T_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + String[] names = new String[temp.size()]; + for (int i = 0; i < temp.size(); i++) { + names[i] = temp.get(i).getName(); + } + return names; + } + + public static List getAllSensors() { + String json = MMKVUtil.get(INDOOR_FLOOR_T_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public static boolean insert(FloorTSensor sensor) { + String json = MMKVUtil.get(INDOOR_FLOOR_T_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + //判断是否已经存在同名传感器 + if (MyUtil.checkDeviceNameAvailable(sensor.getName())) { + temp.add(sensor); + MMKVUtil.put(INDOOR_FLOOR_T_SENSOR, new Gson().toJson(temp)); + return true; + } + return false; + } + + public static void update(Sensor sensor) { + String json = MMKVUtil.get(INDOOR_FLOOR_T_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (FloorTSensor mode : temp) { + if (mode.getName().equals(sensor.getName())) { + mode.setEnable(sensor.isEnable()); + mode.setModel(sensor.getModel()); + mode.setAddress(sensor.getAddress()); + break; + } + } + } + MMKVUtil.put(INDOOR_FLOOR_T_SENSOR, new Gson().toJson(temp)); + } + + public static void deleteByName(String name) { + String json = MMKVUtil.get(INDOOR_FLOOR_T_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (FloorTSensor sensor : temp) { + if (sensor.getName().equals(name)) { + sensor.clearSetting(); + temp.remove(sensor); + MMKVUtil.put(INDOOR_FLOOR_T_SENSOR, new Gson().toJson(temp)); + return; + } + } + } + } + + public static boolean hasSensor(String name) { + String json = MMKVUtil.get(INDOOR_FLOOR_T_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (FloorTSensor sensor : temp) { + if (sensor.getName().equals(name)) { + return true; + } + } + } + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorTHSensorUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorTHSensorUtil.java new file mode 100644 index 0000000..c1dc09b --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/database/dynamicSensor/IndoorTHSensorUtil.java @@ -0,0 +1,98 @@ +package com.example.iot_controlhost.utils.database.dynamicSensor; + +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.List; + +/** + * 农户模板工具类 + */ +public class IndoorTHSensorUtil { + + public static final String INDOOR_TH_SENSOR = "INDOOR_SENSOR_TS2"; + + public static String[] getAllNames() { + String json = MMKVUtil.get(INDOOR_TH_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + String[] names = new String[temp.size()]; + for (int i = 0; i < temp.size(); i++) { + names[i] = temp.get(i).getName(); + } + return names; + } + + public static List getAllSensors() { + String json = MMKVUtil.get(INDOOR_TH_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public static boolean insert(THSensor sensor) { + String json = MMKVUtil.get(INDOOR_TH_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + //判断是否已经存在同名传感器 + if (MyUtil.checkDeviceNameAvailable(sensor.getName())) { + temp.add(sensor); + MMKVUtil.put(INDOOR_TH_SENSOR, new Gson().toJson(temp)); + return true; + } + return false; + } + + public static void update(Sensor sensor) { + String json = MMKVUtil.get(INDOOR_TH_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (THSensor mode : temp) { + if (mode.getName().equals(sensor.getName())) { + mode.setEnable(sensor.isEnable()); + mode.setModel(sensor.getModel()); + mode.setAddress(sensor.getAddress()); + break; + } + } + } + MMKVUtil.put(INDOOR_TH_SENSOR, new Gson().toJson(temp)); + } + + public static void deleteByName(String name) { + String json = MMKVUtil.get(INDOOR_TH_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (THSensor sensor : temp) { + if (sensor.getName().equals(name)) { + sensor.clearSetting(); + temp.remove(sensor); + MMKVUtil.put(INDOOR_TH_SENSOR, new Gson().toJson(temp)); + return; + } + } + } + } + + public static boolean hasSensor(String name) { + String json = MMKVUtil.get(INDOOR_TH_SENSOR, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + if (temp != null) { + for (THSensor sensor : temp) { + if (sensor.getName().equals(name)) { + return true; + } + } + } + return false; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/AIModelSet.java b/app/src/main/java/com/example/iot_controlhost/utils/global/AIModelSet.java new file mode 100644 index 0000000..270c5fb --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/AIModelSet.java @@ -0,0 +1,129 @@ +package com.example.iot_controlhost.utils.global; + +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.model.Silkworm; +import com.example.iot_controlhost.model.SilkwormNew; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.database.SilkwormDBManager; + +public class AIModelSet { + + /** + * 智能方案名称 + */ + public static final String AI_SCHEME_NAME = "AI_SCHEME_NAME"; + /** + * 智能方案ID + */ + public static final String AI_CONTROL_PROGRAM_ID = "AI_CONTROL_PROGRAM_ID"; + /** + * 智能方案-总天数 + */ + public static final String AI_CONTROL_PROGRAM_TOTAL_DAYS = "AI_CONTROL_PROGRAM_TOTAL_DAYS"; + + /** + * 智能模式下是否共育中 + * 智能模式下开启和停止设备 + */ + public static final String AI_PROCESSING = "AI_PROCESSING"; + + /** + * 当前所处智能方案内的所处时间 + * 与共育时间不同,此时间可能会由于具体的共育龄期不同而前后调整 + * 单位:小时 + */ + public static final String AI_BIAS_TIME = "AI_BIAS_TIME"; + + public static int getAiControlProgramTotalDays(){ + return MMKVUtil.get(AI_CONTROL_PROGRAM_TOTAL_DAYS,12); + } + + public static int getAiBiasTime() { + return MMKVUtil.get(AI_BIAS_TIME, 0); + } + + /** + * 获取当前共育第几个小时 + * 可能会被农户手动调整所影响,与共育天数可能存在差异 + */ + public static int getStartToNowHours() { + SilkwormNew silkworm = SilkwormDBManager.getLatestSilkworm(); + int result = 0; + if (SilkwormDBManager.isStarting()) { + result = (int) -TimeUtils.getTimeSpanByNow(silkworm.getStartDate(), TimeConstants.HOUR); + } + result += getAiBiasTime(); + result = result < 0 ? 0 : result; + return result; + } + + public static String getAiSchemeName() { + return MMKVUtil.get().get(AI_SCHEME_NAME); + } + + public static boolean isAiProcessing() { + return MMKVUtil.get(AI_PROCESSING, true); + } + + public static String getAiControlProgramId() { + return MMKVUtil.get().get(AI_CONTROL_PROGRAM_ID); + } + + //————————————————————————温度相关———————————————————————— + /** + * 目标温度 + */ + public static final String AI_TARGET_TEMPERATURE = "AI_TARGET_TEMPERATURE"; + + public static double getTargetTemperature() { + return MMKVUtil.get(AI_TARGET_TEMPERATURE, 26.0); + } + + public static void setTargetTemperature(double value) { + MMKVUtil.put(AI_TARGET_TEMPERATURE, value); + } + + //————————————————————————湿度相关———————————————————————— + /** + * 目标湿度 + */ + public static final String AI_TARGET_HUMIDITY = "AI_TARGET_HUMIDITY"; + + + public static double getTargetHumidity() { + return MMKVUtil.get(AI_TARGET_HUMIDITY, 50.0); + } + + public static void setAiTargetHumidity(double value) { + MMKVUtil.put(AI_TARGET_HUMIDITY, value); + } + + //————————————————————————换气相关———————————————————————— + + /** + * 执行时间 + */ + public static final String AI_CYCLE_START = "AI_CYCLE_START"; + /** + * 停止时间 + */ + public static final String AI_CYCLE_STOP = "AI_CYCLE_STOP"; + + public static int getAiCycleStart() { + return MMKVUtil.get(AI_CYCLE_START, 5); + } + + public static void setAiCycleStart(int value) { + MMKVUtil.put(AI_CYCLE_START, value); + } + + public static int getAiCycleStop() { + return MMKVUtil.get(AI_CYCLE_STOP, 5); + } + + public static void setAiCycleStop(int value) { + MMKVUtil.put(AI_CYCLE_STOP, value); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/AutoModelSet.java b/app/src/main/java/com/example/iot_controlhost/utils/global/AutoModelSet.java new file mode 100644 index 0000000..49f8fb1 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/AutoModelSet.java @@ -0,0 +1,230 @@ +package com.example.iot_controlhost.utils.global; + +import com.example.iot_controlhost.utils.MMKVUtil; + +/** + * 自动模式下的相关配置 + * 配合MMKV使用 + * + * @author DuanKaiji + */ +public class AutoModelSet { + + //————————————————————————总控相关———————————————————————— + /** + * 自动模式-升温方式 + * {"均不启用", "地暖", "空调", "地暖和空调"} + */ + public static final String AUTO_TEMPERATURE_HEAT = "AUTO_TEMPERATURE_HEAT"; + /** + * 自动模式-降温方式 + */ + public static final String AUTO_TEMPERATURE_COOL = "AUTO_TEMPERATURE_COOL"; + /** + * 自动模式-控制方式 + * {"比例控制", "偏差控制", "PID控制", "分挡位控制", "全功率控制"}; + */ + public static final String AUTO_TEMPERATURE_MODE = "AUTO_TEMPERATURE_MODE"; + /** + * 自动模式-匀风扇控制算法 + * {"手动控制", "升温时跟随启动", "降温时跟随启动", "升降温时跟随启动"} + */ + public static final String AUTO_EVEN_MODE = "AUTO_EVEN_MODE"; + /** + * 换气扇控制算法 + * {"全部开启", "仅开进气扇", "仅开排气扇"} + */ + public static final String VENTILATOR_MODE = "VENTILATOR_MODE"; + + public static String getVentilatorMode() { + return MMKVUtil.get(VENTILATOR_MODE, SpinnerList.VENTILATION_MODES[0]); + } + + public static String getAutoEvenMode() { + return MMKVUtil.get(AUTO_EVEN_MODE, SpinnerList.AUTO_EVEN_MODES[3]); + } + + /** + * 匀风扇在加湿期间不开启 + */ + public static final String AUTO_EVEN_MODE_DURING_HUMIDIFY = "AUTO_EVEN_MODE_DURING_HUMIDIFY"; + + public static boolean isAutoEvenModeDuringHumidify() { + return MMKVUtil.get(AUTO_EVEN_MODE_DURING_HUMIDIFY, true); + } + + /** + * 自动模式-新风控制控制算法 + * {"总是关闭","随换气扇启动", "随加湿器启动"} + */ + public static final String AIR_EXCHANGE_MODE = "AIR_EXCHANGE_MODE"; + + public static String getAirExchangeMode() { + return MMKVUtil.get(AIR_EXCHANGE_MODE, SpinnerList.AIR_EXCHANGE_MODES[0]); + } + /** + * 升温方式 + * + * @return {"均不启用", "地暖", "空调", "地暖和空调"} + */ + public static String getAutoTemperatureHeat() { + return MMKVUtil.get(AUTO_TEMPERATURE_HEAT, SpinnerList.TEMPERATURE_HEAT[3]); + } + + /** + * 降温方式 + * @return {"均不启用", "空调"} + */ + public static String getAutoTemperatureCool() { + return MMKVUtil.get(AUTO_TEMPERATURE_COOL, SpinnerList.TEMPERATURE_COOL[1]); + } + + + public static String getAutoTemperatureMode() { + return MMKVUtil.get(AUTO_TEMPERATURE_MODE, SpinnerList.TEMPERATURE_MODE[1]); + } + + //————————————————————————温度相关———————————————————————— + /** + * 温控是否自动 + */ + public static final String AUTO_TEMPERATURE = "AUTO_TEMPERATURE"; + /** + * 目标温度 + */ + public static final String TARGET_TEMPERATURE = "TARGET_TEMPERATURE"; + /** + * 温度预警最大值 + */ + public static final String MAX_TEMPERATURE = "MAX_TEMPERATURE"; + /** + * 温度预警最小值 + */ + public static final String MIN_TEMPERATURE = "MIN_TEMPERATURE"; + + public static boolean isAutoTemperature() { + return MMKVUtil.get(AUTO_TEMPERATURE, true); + } + + public static double getTargetTemperature() { + String formattedValue = String.format("%.2f", MMKVUtil.get(TARGET_TEMPERATURE, 26.0)); + return Double.parseDouble(formattedValue); + } + + public static double getMaxTemperature() { + String formattedValue = String.format("%.1f", MMKVUtil.get(MAX_TEMPERATURE, 27.0)); + return Double.parseDouble(formattedValue); + } + + public static double getMinTemperature() { + String formattedValue = String.format("%.1f", MMKVUtil.get(MIN_TEMPERATURE, 25.0)); + return Double.parseDouble(formattedValue); + } + + //————————————————————————湿度相关———————————————————————— + /** + * 湿度控制是否自动 + */ + public static final String AUTO_HUMIDITY = "AUTO_HUMIDITY"; + /** + * 目标湿度 + */ + public static final String TARGET_HUMIDITY = "TARGET_HUMIDITY"; + /** + * 湿度预警最大值 + */ + public static final String MAX_HUMIDITY = "MAX_HUMIDITY"; + /** + * 湿度预警最小值 + */ + public static final String MIN_HUMIDITY = "MIN_HUMIDITY"; + + public static double getMaxHumidity() { + String formattedValue = String.format("%.1f", MMKVUtil.get(MAX_HUMIDITY, 70.0)); + return Double.parseDouble(formattedValue); + } + + public static double getMinHumidity() { + String formattedValue = String.format("%.1f", MMKVUtil.get(MIN_HUMIDITY, 50.0)); + return Double.parseDouble(formattedValue); + } + + public static double getTargetHumidity() { + String formattedValue = String.format("%.1f", MMKVUtil.get(TARGET_HUMIDITY, 60.0)); + return Double.parseDouble(formattedValue); + } + + public static boolean isAutoHumidity() { + return MMKVUtil.get(AUTO_HUMIDITY, true); + } + + + //————————————————————————换气相关———————————————————————— + /** + * 换气控制是否自动 + */ + public static final String AUTO_VENTILATOR = "AUTO_VENTILATOR"; + /** + * 执行时间 + */ + public static final String CYCLE_START = "CYCLE_START"; + /** + * 停止时间 + */ + public static final String CYCLE_STOP = "CYCLE_STOP"; + /** + * CO2最大浓度 + */ + public static final String MAX_DENSITY_CO2 = "MAX_DENSITY_CO2"; + + public static boolean isAutoVentilator() { + return MMKVUtil.get(AUTO_VENTILATOR, true); + } + + public static int getCycleStart() { + return MMKVUtil.get(CYCLE_START, 3); + } + + public static int getCycleStop() { + return MMKVUtil.get(CYCLE_STOP, 60); + } + + public static int getMaxDensityCO2() { + int maxDensityCO2 = MMKVUtil.get(MAX_DENSITY_CO2, 1000); + if (maxDensityCO2 < 1000) { + return 1000; + } + return maxDensityCO2; + } + + + //————————————————————————消毒相关———————————————————————— + /** + * 消毒控制是否自动 + */ + public static final String AUTO_DISINFECT = "AUTO_DISINFECT"; + + public static boolean isAutoDisinfect() { + return MMKVUtil.get(AUTO_DISINFECT, true); + } + + + //————————————————————————匀风相关———————————————————————— + /** + * 消毒控制是否自动 + */ + public static final String AUTO_EVEN = "AUTO_EVEN"; + + public static boolean isAutoEven() { + return !AutoModelSet.getAutoEvenMode().equals(SpinnerList.AUTO_EVEN_MODES[0]); + } + /*** + * 自动模式下地暖最大温度限制 + * double + */ + public static final String AUTO_FLOOR_MAX_TEMP = "AUTO_FLOOR_MAX_TEMP"; + public static double getAutoFlorMaxTemp() { + return MMKVUtil.get(AUTO_FLOOR_MAX_TEMP, Variable.FLOOR_MAX_TEMPERATURE_LIMIT); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/HardwareSetting.java b/app/src/main/java/com/example/iot_controlhost/utils/global/HardwareSetting.java new file mode 100644 index 0000000..67d6db6 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/HardwareSetting.java @@ -0,0 +1,200 @@ +package com.example.iot_controlhost.utils.global; + +import android.content.Context; +import android.os.Build; +import android.telephony.TelephonyManager; + +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.utils.MMKVUtil; + +/** + * 硬件相关配置 + */ +public class HardwareSetting { + /** + * 主机码(设备唯一码) + */ + public static final String HOST_ID = "HOST_ID"; + /** + * IMEI(国际移动设备识别码) + */ + public static final String IMEI = "IMEI"; + /** + * 物联网卡号 + */ + public static String IMSI = "IMSI"; + + /** + * 获取物联网卡号-IMSI国际移动用户识别码 + */ + public static String getImsi() { + return MMKVUtil.get(IMSI, "-"); + } + + /** + * 控制器串口 + */ + public static final String CONTROLLER_SERIAL_COM = "控制器串口"; + /** + * 控制器波特率 + */ + public static final String CONTROLLER_SERIAL_BAUD = "控制器波特率"; + /** + * 传感器串口 + */ + public static final String SENSOR_SERIAL_COM = "传感器串口"; + /** + * 传感器波特率 + */ + public static final String SENSOR_SERIAL_COM_BAUD = "传感器波特率"; + /** + * 工作模式 + */ + public static String[] CONTROLLER_WORK_MODE = {"从机模式"/**, "主机模式"**/}; + + /** + * 第一次注册主机 + */ + public static final String IS_REGISTERED = "IS_REGISTERED"; + /** + * 通用地址-集成控制器用 + */ + public static final String COMMON_ADDRESS = "通用地址-集成控制器用"; + /** + * 当前IP地址 ipv4 + */ + public static final String IP_ADDRESS = "IP_ADDRESS"; + /** + * FRP服务器给自己分配的端口 + */ + public static final String REMOTE_PORT = "REMOTE_PORT"; + /** + * FRP服务器给自己分配的端口 + */ + public static final String REMOTE_SERVER_IPADDRESS = "REMOTE_SERVER_IPADDRESS"; + /** + * 控制器工作模式 + * 集成式:主机模式/从机模式 + * 装配式:从机模式 + */ + public static final String MY_CONTROLLER_WORK_MODE = "控制器工作模式"; + + public static String getHostId() { + return MMKVUtil.get(HOST_ID, "未获取"); + } + + public static String getImei() { + return MMKVUtil.get(IMEI, "-"); + } + + public static String getControllerSerialCom() { + return MMKVUtil.get(CONTROLLER_SERIAL_COM, SpinnerList.SERIAL_ADDRESS[5]); + } + + public static void setControllerSerialCom(String controllerSerialCom) { + MMKVUtil.put(CONTROLLER_SERIAL_COM, controllerSerialCom); + } + + public static String getSensorSerialCom() { + return MMKVUtil.get(SENSOR_SERIAL_COM, SpinnerList.SERIAL_ADDRESS[1]); + } + + public static void setSensorSerialCom(String sensorSerialCom) { + MMKVUtil.put(SENSOR_SERIAL_COM, sensorSerialCom); + } + + public static String getCommonAddress() { + return MMKVUtil.get(COMMON_ADDRESS, SpinnerList.ADDRESS[0]); + } + + public static void setCommonAddress(String address) { + MMKVUtil.put(COMMON_ADDRESS, address); + } + + public static String getWorkMode() { + return MMKVUtil.get(MY_CONTROLLER_WORK_MODE, CONTROLLER_WORK_MODE[0]); + } + + public static void setWorkMode(String controllerWorkMode) { + MMKVUtil.put(MY_CONTROLLER_WORK_MODE, controllerWorkMode); + } + + public static String getControllerSerialBaud() { + return MMKVUtil.get(CONTROLLER_SERIAL_BAUD, SpinnerList.BAUD[3]); + } + + public static void setControllerSerialBaud(String controllerSerialBaud) { + MMKVUtil.put(CONTROLLER_SERIAL_BAUD, controllerSerialBaud); + } + + public static String getSensorSerialComBaud() { + return MMKVUtil.get(SENSOR_SERIAL_COM_BAUD, SpinnerList.BAUD[3]); + } + + public static void setSensorSerialComBaud(String sensorSerialComBaud) { + MMKVUtil.put(SENSOR_SERIAL_COM_BAUD, sensorSerialComBaud); + } + + /** + * 是否已经注册 + */ + public static boolean isRegistered() { + return MMKVUtil.get(IS_REGISTERED, false); + } + + /** + * 设置已注册 + */ + public static void setRegistered() { + MMKVUtil.put(IS_REGISTERED, true); + } + + + /** + * 旧版Android 7 + */ + public static final String ZC_328 = "ZC-328"; + /** + * 旧版Android 11 + */ + public static final String ZC_356 = "ZC-356"; + /** + * 新版Android 11 + */ + public static final String RK3568_R = "rk3568_r"; + + public static boolean isZCAndroid() { + return Build.MODEL.equals(HardwareSetting.ZC_328) || Build.MODEL.equals(HardwareSetting.ZC_356); + } + + public static boolean isYSAndroid() { + return Build.MODEL.equals(HardwareSetting.RK3568_R); + } + + /** + * 获取设备型号 + */ + public static String getDeviceModel() { + return Build.MODEL; + } + + public static String getIPAddress() { + return MMKVUtil.get(IP_ADDRESS, "172.16.1.103"); + } + + public static String getRemotePort() { + return MMKVUtil.get(REMOTE_PORT, "6000"); + } + + public static String getRemoteServerIPAddress() { + return MMKVUtil.get(REMOTE_SERVER_IPADDRESS, "0.0.0.0"); + } + + + public static final String FRP_VERSION = "FRP_VERSION"; + + public static int getFrpVersion() { + return MMKVUtil.get(FRP_VERSION, 0); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java new file mode 100644 index 0000000..564bbef --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomController.java @@ -0,0 +1,319 @@ +package com.example.iot_controlhost.utils.global; + +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.controller.DehumidifierInfrared; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.controller.relay.AirCondition; +import com.example.iot_controlhost.model.controller.relay.Dehumidifier; +import com.example.iot_controlhost.model.controller.relay.Fan; +import com.example.iot_controlhost.model.controller.relay.FloorRelay; +import com.example.iot_controlhost.model.controller.relay.Relay; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import android_serialport_api.SerialPortUtil; + +/** + * 房间设备 + */ +public class RoomController { + /** + * 所有控制设备 + */ + public static final String[] DEVICE_NAME = {"地暖",//0 + "空调", "加湿器", "进气扇", "排气扇", "红光灯",//1-5 + "白光灯", "匀风扇", "消毒灯", "空调红外控制器", "继电器",//6-10 + "继电器-地暖", "换气扇", "新风", "空调红外控制器2", "除湿机红外控制器",//11-15 + "除湿机"// 16 + }; + + //——————————————————————————————————————————————以下为控制器—————————————————————————————————————————————————— + /** + * 继电器 + */ + public static Relay relay = new Relay(DEVICE_NAME[10]); + /** + * 地暖 + */ + public static Floor floor = new Floor(DEVICE_NAME[0]); + /** + * 空调红外控制器 + */ + public static AirConditionInfrared airConditionInfrared = new AirConditionInfrared(DEVICE_NAME[9]); + /** + * 空调红外控制器2 + */ + public static AirConditionInfrared airConditionInfrared2 = new AirConditionInfrared(DEVICE_NAME[14]); + /** + * 除湿机 + */ + public static DehumidifierInfrared dehumidifierInfrared = new DehumidifierInfrared(DEVICE_NAME[15]); + + //——————————————————————————————————————————————以下为继电器下设备—————————————————————————————————————————————————— + /** + * 继电器-空调 + */ + public static AirCondition airCondition = new AirCondition(DEVICE_NAME[1]); + /** + * 继电器-除湿机 + */ + public static Dehumidifier dehumidifier = new Dehumidifier(DEVICE_NAME[16]); + /** + * 加湿器 + */ + public static Relay humidifier = new Relay(DEVICE_NAME[2], R.mipmap.hum_open, R.mipmap.hum_close); + /** + * 进气扇 + */ + public static Relay intakeFan = new Relay(DEVICE_NAME[3], R.mipmap.ven_open, R.mipmap.ven_close); + /** + * 排气扇 + */ + public static Relay exhaustFan = new Relay(DEVICE_NAME[4], R.mipmap.ven_open, R.mipmap.ven_close); + /** + * 换气扇-总控 + */ + public static Fan fan = new Fan(DEVICE_NAME[12], R.mipmap.ven_open, R.mipmap.ven_close); + /** + * 红光灯 + */ + public static Relay redLight = new Relay(DEVICE_NAME[5], R.mipmap.red_light_open, R.mipmap.red_light_close); + /** + * 白光灯 + */ + public static Relay whiteLight = new Relay(DEVICE_NAME[6], R.mipmap.white_light_open, R.mipmap.white_light_close); + /** + * 匀风扇 + */ + public static Relay evenFan = new Relay(DEVICE_NAME[7], R.mipmap.even_open, R.mipmap.even_close); + /** + * 消毒灯 + */ + public static Relay uvLight = new Relay(DEVICE_NAME[8], R.mipmap.uv_light_open, R.mipmap.uv_light_close); + /** + * 新风 + */ + public static Relay airExchange = new Relay(DEVICE_NAME[13], R.mipmap.even_open, R.mipmap.even_close); + /** + * 继电器-地暖 + * (特别用途) + */ + public static FloorRelay relayFloor = new FloorRelay(DEVICE_NAME[11]); + + //——————————————————————————————————————————————以下为控制器常用方法—————————————————————————————————————————————————— + + /** + * 获得所有继电器下的设备 + */ + public static List getAllRelayDevice() { + List devices = new ArrayList<>(); + Collections.addAll(devices, relayFloor, airCondition, dehumidifier, humidifier, intakeFan, evenFan, exhaustFan, uvLight, whiteLight, redLight, airExchange); + return devices; + } + + /** + * 获得所有设备 + */ + public static List getAllDevice() { + List devices = new ArrayList<>(); + devices.addAll(getAllAvailableRelays()); + devices.addAll(RoomSensor.getAllAvailableSensors()); + devices.add(floor); + devices.add(airConditionInfrared); + devices.add(dehumidifierInfrared); + return devices; + } + + /** + * 获得可用的-继电器下的设备 + */ + public static List getAllAvailableRelays() { + List devices = getAllRelayDevice(); + Iterator iterator = devices.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().isAvailable()) { + iterator.remove(); + } + } + return devices; + } + + /** + * 获得可控制的继电器下设备 + */ + public static List getAllControlRelays() { + List devices = getAllAvailableRelays(); + devices.add(RoomController.fan); + Iterator iterator = devices.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().isAvailable()) { + iterator.remove(); + } + } + devices.remove(RoomController.intakeFan); + devices.remove(RoomController.exhaustFan); + devices.remove(RoomController.relayFloor); + devices.remove(RoomController.airCondition); + return devices; + } + + /** + * 利用设备名获得设备类 工厂方法 + * + * @param deviceName 设备名称 需要在 PublicClass.DEVICE_NAME选用 + * @return 设备类 可用于设置端口地址等操作 + */ + public static Relay getRelay(String deviceName) { + for (int i = 0; i < DEVICE_NAME.length; i++) { + if (DEVICE_NAME[i].equals(deviceName)) { + switch (i) { + case 0: + case 11: + //地暖/继电器-地暖 均为此 + return relayFloor; + case 1: + return airCondition; + case 2: + return humidifier; + case 3: + return intakeFan; + case 4: + return exhaustFan; + case 5: + return redLight; + case 6: + return whiteLight; + case 7: + return evenFan; + case 8: + return uvLight; + case 13: + return airExchange; + case 16: + return dehumidifier; + default: + //尝试获得名字错误的->继电器 + return null; + } + } + } + return uvLight; + } + + /** + * 初始化控制器 + * 关闭所有设备 + */ + public static void init() { + if (!SerialPortUtil.getInstance(SerialPortUtil.TYPE_CONTROLLER).openSerialPort()) { + MyLog.appError("控制器串口打开失败,请检查设备连接"); + return; + } + // 关闭所有可用控制器电源 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + //关闭单独设备 + MyInfraredUtils.getInstances().setPowerSupply(false); + RoomController.floor.setPowerSupply(false); + //关闭继电器下的所有设备 + List devices = getAllAvailableRelays(); + //除了空调物理电源 + devices.remove(RoomController.airCondition); + //除了除湿机物理电源 + devices.remove(RoomController.dehumidifier); + for (Relay r : devices) { + r.setPowerSupply(false); + } + })); + // BBIT-H-v1.0型继电器 特有逻辑 + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + // 轮询读取所有继电器设备下的功耗信息 + PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_COLLECT_CONTROLLER, 5, () -> { + for (Relay relay : RoomController.getAllAvailableRelays()) { + //以低优先级进行排队 + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(5, () -> { + MyLog.sensor(relay.getName() + "采集能耗信息"); + relay.collectSensorData(); + })); + } + }); + // 检查除湿机功率是否与除湿机物理电源状态匹配,时刻监测当前除湿机功率状态,使之与软件开关匹配 + // 防止出现软件界面中开启除湿器但实际上除湿器未开启(功率不足100W)的情况 + if (RoomController.dehumidifier.isAvailable()) { + PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_DEHUMIDIFIER_STATUS, 60, () -> { + if (RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() < 100) { + MyLog.controllerError("除湿机电源已开启但功率不足100W,现尝试再次开启除湿机物理电源"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupply(true))); + } else if (!RoomController.dehumidifier.isPowerSupply() && RoomController.dehumidifier.getPower() > 100) { + MyLog.controllerError("除湿机电源已关闭但功率大于100W,现尝试再次关闭除湿机物理电源"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupply(false))); + } + }); + } + } + //初始化新线程,时刻监测当前空调红外状态,以使得物理开关与之匹配 + PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_CONTROLLER_STATE_CHECK_AIR, RoomSetting.getTimeOfAirStopRelay(), () -> { +// MyLog.test("空调后台线程:现检查是否需要关闭空调物理电源"); + if (airCondition.isAvailable() && !MyInfraredUtils.getInstances().isPowerSupply() && doNotNeedDelayCloseAirCondition) { + MyLog.controller("空调后台线程:遥控器已关闭,现关闭空调物理电源"); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.airCondition.setPowerSupply(false))); + } else { + doNotNeedDelayCloseAirCondition = true; +// MyLog.test("空调后台线程:不采取操作"); + } + + if (dehumidifier.isAvailable() && !dehumidifierInfrared.isPowerSupply() && doNotNeedDelayCloseDehumidifier) { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> RoomController.dehumidifier.setPowerSupplyForOld(false))); + } else { + doNotNeedDelayCloseDehumidifier = true; + } + }); + //其他设备,使保持运行状态 + PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_CONTROLLER_STATE_CHECK_OTHER, Variable.CHECK_CLOSE_INFRARED, () -> { + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + //地暖 + RoomController.floor.setGear(RoomController.floor.getGear()); + // BBIT继电器需要持续刷新运行状态 + if (RoomController.relay.getModel().equals(RoomController.relay.getModels()[2])) { + for (Relay relay : RoomController.getAllAvailableRelays()) { + if (relay.getName().equals(RoomController.dehumidifier.getName())) { + // 除湿机物理电源不刷新,因为红外开启与关闭命令相同,重复刷新会导致设备持续开关 所以此处只处理继电器开关 2026年3月19日09点36分 + RoomController.dehumidifier.setPowerSupplyForOld(RoomController.dehumidifier.isPowerSupply()); + continue; + } + MyUtil.autoControlOperateThird(relay, relay.isPowerSupply()); + } + } + })); + }); + } + + /** + * 是否不需要延迟一个@Variable.CHECK_CLOSE_INFRARED 时间,直接关闭空调 + * 防止出现关闭红外后刚好@Variable.CHECK_CLOSE_INFRARED时间到而关闭空调从而导致空调外机无法及时散热而出现问题的情况 + */ + public static boolean doNotNeedDelayCloseAirCondition = true; + public static boolean doNotNeedDelayCloseDehumidifier = true; + + public static void closeAllControllers() { + List relays = RoomController.getAllAvailableRelays(); + MyInfraredUtils.getInstances().setPowerSupply(false); + RoomController.dehumidifier.setPowerSupply(false); + RoomController.floor.setPowerSupply(false); + relays.remove(RoomController.airCondition); + relays.remove(RoomController.dehumidifier); + for (Relay r : relays) { + r.setPowerSupply(false); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java new file mode 100644 index 0000000..d0b8f59 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSensor.java @@ -0,0 +1,279 @@ +package com.example.iot_controlhost.utils.global; + +import com.example.iot_controlhost.model.sensor.ConsumptionSensor; +import com.example.iot_controlhost.model.sensor.FloorTSensor; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.LightSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorAmmoniaSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import android_serialport_api.SerialPortUtil; + +/** + * 所有传感器 + */ +public class RoomSensor { + + public static List dynamicTHSensorList = new ArrayList<>(); + public static List dynamicCO2SensorList = new ArrayList<>(); + public static List dynamicAmmoniaSensorList = new ArrayList<>(); + public static List dynamicFloorTSensorList = new ArrayList<>(); + + /** + * 所有传感器 + */ + public static final String[] SENSOR_NAME = {"能耗传感器", "光照传感器", "噪音传感器", "室内CO2传感器", "氨气传感器", + "地面温度传感器", "室外温湿传感器", "室内温湿传感器1", "室内温湿传感器2", "空调温度传感器", "室外CO2传感器"}; + + /** + * 能耗传感器 + */ + public static ConsumptionSensor consumptionSensor = new ConsumptionSensor(SENSOR_NAME[0]); + /** + * 光照传感器 + */ + public static LightSensor lightSensor = new LightSensor(SENSOR_NAME[1]); + /** + * 噪音传感器 已修改为动态传感器 + */ +// public static NoiseSensor noiseSensor = new NoiseSensor(SENSOR_NAME[2]); + /** + * CO2传感器 已修改为动态传感器 + */ +// public static GasSensor co2IndoorSensor = new GasSensor(SENSOR_NAME[3]); + /** + * 氨气传感器 已修改为动态传感器 + */ +// public static GasSensor ammoniaSensor = new GasSensor(SENSOR_NAME[4]); + /** + * 温度传感器——地面 已修改为动态传感器 + */ +// public static FloorTSensor floorTSensor = new FloorTSensor(SENSOR_NAME[5]); + /** + * 温湿传感器——室外 + */ + public static THSensor outdoorTHSensor = new THSensor(SENSOR_NAME[6]); + /** + * 温湿传感器——空调 + */ + public static FloorTSensor airTHSensor = new FloorTSensor(SENSOR_NAME[9]); + /** + * 温湿传感器——室外CO2 + */ + public static GasSensor co2OutdoorSensor = new GasSensor(SENSOR_NAME[10]); + + /** + * 传感器初始化 + */ + public static void init() { + dynamicTHSensorList = new ArrayList<>(); + dynamicCO2SensorList = new ArrayList<>(); + dynamicAmmoniaSensorList = new ArrayList<>(); + dynamicFloorTSensorList = new ArrayList<>(); + + if (SerialPortUtil.getInstance(SerialPortUtil.TYPE_SENSOR).openSerialPort()) { + for (THSensor sensor : IndoorTHSensorUtil.getAllSensors()) { + if (sensor.isAvailable()) { + dynamicTHSensorList.add(sensor); + } + } + for (GasSensor sensor : IndoorCO2SensorUtil.getAllSensors()) { + if (sensor.isAvailable()) { + dynamicCO2SensorList.add(sensor); + } + } + for (GasSensor sensor : IndoorAmmoniaSensorUtil.getAllSensors()) { + if (sensor.isAvailable()) { + dynamicAmmoniaSensorList.add(sensor); + } + } + for (FloorTSensor sensor : IndoorFloorTSensorUtil.getAllSensors()) { + if (sensor.isAvailable()) { + dynamicFloorTSensorList.add(sensor); + } + } + // 每隔半天清理一次过去的历史数据 防止数据堆积导致IO时间过长 后因采集数据暂停,所以该功能暂时关闭 +// RxJavaUtils.polling(5, 12 * 60 * 60, +// aLong -> RxJavaUtils.doInIOThread(new RxIOTask(null) { +// @Override +// public Void doInIOThread(Object i) { +// for (Sensor sensor : RoomSensor.getAllAvailableSensors()) { +// sensor.clearHistoryData(); +// } +// return null; +// } +// })); + //持续采集各种传感器数据 + PollingTask.getInstance().startPollingTaskOnIOThread(RxTag.TAG_COLLECT_SENSOR, Variable.SENSOR_REFRESH_TIME, () -> { + for (Sensor sensor : getAllAvailableSensors()) { + sensor.getAllSensorData(); + } + }); + } + } + + /** + * 获得所有传感器 + */ + public static List getAllSensors() { + List devices = new ArrayList<>(); + Collections.addAll(devices, outdoorTHSensor, consumptionSensor, airTHSensor, co2OutdoorSensor); + devices.addAll(IndoorTHSensorUtil.getAllSensors()); + devices.addAll(IndoorCO2SensorUtil.getAllSensors()); + devices.addAll(IndoorAmmoniaSensorUtil.getAllSensors()); + devices.addAll(IndoorFloorTSensorUtil.getAllSensors()); + return devices; + } + + /** + * 获得所有传感器名字 + */ + public static List getAllSensorNames() { + List names = new ArrayList<>(); + for (Sensor sensor : getAllSensors()) { + names.add(sensor.getName()); + } + return names; + } + + /** + * 获得所有可用传感器 + */ + public static List getAllAvailableSensors() { + List devices = new ArrayList<>(); + Collections.addAll(devices, outdoorTHSensor, consumptionSensor, airTHSensor, co2OutdoorSensor); + Iterator iterator = devices.iterator(); + while (iterator.hasNext()) { + if (!iterator.next().isAvailable()) { + iterator.remove(); + } + } + devices.addAll(dynamicCO2SensorList); + devices.addAll(dynamicAmmoniaSensorList); + devices.addAll(dynamicFloorTSensorList); + devices.addAll(dynamicTHSensorList); + return devices; + } + + /** + * 获取室内平均温度 + */ + public static double getAverageIndoorTemperature() { + double sumOfTemperatures = 0; + int numberOfEnabledSensors = 0; + for (THSensor sensor : dynamicTHSensorList) { + if (sensor.temperature == 0) { + continue; + } + sumOfTemperatures += sensor.temperature; + numberOfEnabledSensors++; + } + for (GasSensor sensor : dynamicCO2SensorList) { + if (sensor.getTemperature() == 0) { + continue; + } + sumOfTemperatures += sensor.getTemperature(); + numberOfEnabledSensors++; + } + for (GasSensor sensor : dynamicAmmoniaSensorList) { + if (sensor.getTemperature() == 0) { + continue; + } + sumOfTemperatures += sensor.getTemperature(); + numberOfEnabledSensors++; + } + if (numberOfEnabledSensors == 0 || sumOfTemperatures == 0) { + return 0.0; + } + return Math.round((sumOfTemperatures / numberOfEnabledSensors + RoomSetting.getBiasTemp()) * 10.0) / 10.0; + } + + /** + * 获取室内平均湿度 + */ + public static double getAverageIndoorHumidity() { + double sumOfHumidities = 0.0; + int numberOfEnabledSensors = 0; + for (THSensor sensor : dynamicTHSensorList) { + if (sensor.humidity == 0 || sensor.humidity == 100) { + continue; + } + sumOfHumidities += sensor.humidity; + numberOfEnabledSensors++; + } + for (GasSensor sensor : dynamicCO2SensorList) { + if (sensor.getHumidity() == 0 || sensor.getHumidity() == 100) { + continue; + } + sumOfHumidities += sensor.getHumidity(); + numberOfEnabledSensors++; + } + for (GasSensor sensor : dynamicAmmoniaSensorList) { + if (sensor.getHumidity() == 0 || sensor.getHumidity() == 100) { + continue; + } + sumOfHumidities += sensor.getHumidity(); + numberOfEnabledSensors++; + } + if (numberOfEnabledSensors == 0 || sumOfHumidities == 0) { + return 0.0; + } + return Math.round((sumOfHumidities / numberOfEnabledSensors + RoomSetting.getBiasHumidity()) * 10.0) / 10.0; + } + + /** + * 获取平均室温 + * 面向用户 使当前温度趋于目标温度 + * 2026年5月3日 10点36分 已废弃该算法 + */ + public static double getAverageIndoorTemperatureForUser() { + double realValue = getAverageIndoorTemperature(); + if (RoomSetting.getType() == 4) { + // 如果是催青室,直接返回平均温度 不进行调整 + return realValue; + } + if (realValue == 0) { + return 0; + } + double targetTemperature = AutoModelSet.getTargetTemperature(); + double setBias = Variable.AUTO_DO_NOTHING_TEMPERATURE_FULL; + double unRealValue; + if (realValue > targetTemperature + setBias) { + // 当前温度高于目标温度 使当前温度趋于目标温度 + unRealValue = realValue - setBias; + } else if (realValue < targetTemperature - setBias) { + // 当前温度低于目标温度 使当前温度趋于目标温度 + unRealValue = realValue + setBias; + } else { + // 当前温度在目标温度灵活值域内 使当前温度等于目标温度 + unRealValue = targetTemperature; + } + return Math.round(unRealValue * 10.0) / 10.0; + } + + /** + * 获得地面平均温度 + * + * @return + */ + public static double getFloorTemperature() { + if (dynamicFloorTSensorList.isEmpty()) { + return 0; + } + double result = 0; + for (FloorTSensor sensor : dynamicFloorTSensorList) { + result += sensor.getTemperature(); + } + return Math.round(result / dynamicFloorTSensorList.size() * 100.0) / 100.0; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSetting.java b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSetting.java new file mode 100644 index 0000000..fa5998c --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/RoomSetting.java @@ -0,0 +1,285 @@ +package com.example.iot_controlhost.utils.global; + +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.net.FaceCheck; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.google.gson.reflect.TypeToken; + +import java.util.HashMap; +import java.util.Map; + +/** + * 房间相关配置 + */ +public class RoomSetting { + /** + * 房间可用与否 + */ + public static final String ROOM_ENABLE = "房间可用"; + /** + * 房间名称 + */ + public static final String ROOM_NAME = "房间名称"; + /** + * 配置密码 + */ + public static final String SET_PASSWORD = "配置密码"; + /** + * 农户名称 + */ + public static final String USER_NAME = "农户名称"; + /** + * 区域ID + * 用于发送给服务器 + */ + public static final String AREA_CODE = "区域码"; + /** + * 当前模式 + * 0手动 1自动 2智能 + */ + public static final String MODE = "当前模式"; + /** + * 当前工作内容 + */ + public static final String WORK_STATE = "WORK_STATE"; + /** + * 主界面锁屏密码 + */ + public static final String MAIN_ACTIVITY_LOCK = "主界面屏幕锁密码"; + /** + * 主题 + * 是否为黑色主题 + */ + public static final String IS_DARK_THEME = "IS_DARK_THEME"; + /** + * 上次配置的时间 + */ + public static final String SET_TIME = "上次配置时间戳"; + + /** + * 主界面无操作超时时间 + * 超过该时间则操作需要先输入密码 + * 单位:秒 + */ + public static final String MAIN_ACTIVITY_OPERATE_LOCK_TIMEOUT = "屏幕锁时间(单位秒)"; + /** + * 当前房间类别 + * 1共育室 2装配共育室 3大蚕房 4催青室 5保种室 6小白盒 0未定义 + */ + public static final String TYPE = "TYPE"; + /** + * 自动亮暗模式调节开关 + */ + public static final String AUTO_LIGHT_NIGHT = "自动亮暗模式调节"; + /** + * 共育室纬度 + */ + public static final String ROOM_LATITUDE_LATITUDE = "共育室坐标-纬度"; + /** + * 共育室经度 + */ + public static final String ROOM_COORDINATE_LONGITUDE = "共育室坐标-经度"; + /** + * 人脸识别激活状态 + */ + public static final String FACE_IS_ACTIVATED = "FACE_IS_ACTIVATED"; + /** + * Token + */ + public static final String TOKEN = "TOKEN_BEARER"; + /** + * 当前在操作的用户 + * 利用人脸识别结果获取 + */ + public static final String USER = "USER"; + /** + * 空调是否在强制关闭期间 + */ + public static final String IS_AIR_CONDITION_FORCE_CLOSE = "IS_AIR_CONDITION_FORCE_CLOSE"; + /** + * 空调是否在强制关闭期间-是否开启 + * 如果这里关闭,那么即使设置了强制关闭,空调也不会被强制关闭 + */ + public static final String IS_AIR_CONDITION_FORCE_CLOSE_ENABLE = "IS_AIR_CONDITION_FORCE_CLOSE_ENABLE"; + + public static boolean isAirConditionForceCloseEnable() { + return MMKVUtil.get(IS_AIR_CONDITION_FORCE_CLOSE_ENABLE, true); + } + /** + * 空调指令是否循环发送 + */ + public static final String AIR_RECYCLE_CONTROL = "AIR_RECYCLE_CONTROL"; + + public static boolean isAirRecycleControl() { + return MMKVUtil.get(AIR_RECYCLE_CONTROL, false); + } + + public static boolean isAirConditionForceClose() { + return MMKVUtil.get(IS_AIR_CONDITION_FORCE_CLOSE, false); + } + + public static String getRoomName() { + return MMKVUtil.get(ROOM_NAME, "无名房间"); + } + + public static FaceCheck.DataDTO getUser() { + return GsonUtils.fromJson(MMKVUtil.get(USER), FaceCheck.DataDTO.class); + } + + /** + * 获取房间-纬度 默认北京 + */ + public static double getRoomCoordinateLatitude() { + // 北京(30.67, 104.06) + // 成都(39.90, 116.40) + return MMKVUtil.get(ROOM_LATITUDE_LATITUDE, 39.9041999); + } + + /** + * 获取房间-经度 默认北京 + */ + public static double getRoomCoordinateLongitude() { + // 北京(30.67, 104.06) + // 成都(39.90, 116.40) + return MMKVUtil.get(ROOM_COORDINATE_LONGITUDE, 116.4073963); + } + + public static boolean getAutoLightNight() { + return MMKVUtil.get(AUTO_LIGHT_NIGHT, true); + } + + /** + * 当前房间类别 + * 1共育室 2装配共育室 3大蚕房 4催青室 5保种室 6小白盒 0未定义 + */ + public static int getType() { + return MMKVUtil.get(TYPE, 1); + } + + /** + * 预警类型 + */ + public static final String NOTICE_TYPE = "预警类型"; + public static final String[] NOTICE_TYPE_SPINNER = {"不通知", "公众号", "公众号与电话"}; + + public static String getNoticeType() { + return MMKVUtil.get(NOTICE_TYPE, NOTICE_TYPE_SPINNER[2]); + } + + public static long getMainActivityOperateLockTimeOut() { + return MMKVUtil.get(MAIN_ACTIVITY_OPERATE_LOCK_TIMEOUT, 5 * 60); + } + + + public static void setSetTime(long time) { + MMKVUtil.put(SET_TIME, time); + } + + public static long getSetTime() { + return MMKVUtil.get(SET_TIME, 0L); + } + + public static boolean isRoomEnable() { + return MMKVUtil.get(ROOM_ENABLE, true); + } + + public static void setRoomEnable(boolean state) { + MMKVUtil.put(ROOM_ENABLE, state); + } + + /** + * 当前模式 + * + * @return 0手动 1自动 2智能 + */ + public static int getMode() { + return MMKVUtil.get(MODE, 0); + } + + public static String getUserName() { + return MMKVUtil.get(USER_NAME, "未配置"); + } + + public static String getAreaCode() { + return MMKVUtil.get(AREA_CODE, "未配置"); + } + + public static String getSetPassword() { + return MMKVUtil.get(SET_PASSWORD, "112233"); + } + + public static String getMainActivityLock() { + return MMKVUtil.get(MAIN_ACTIVITY_LOCK, "123456"); + } + + public static boolean isDarkTheme() { + return MMKVUtil.get(IS_DARK_THEME, false); + } + + /** + * 用于标记是否处于工作中,内容值为Map, + * name:工作名 + * data:服务器返回的唯一工作标识Data + * 当为空时表示当前未在工作, + */ + public static Map getWorkState() { + String json = MMKVUtil.get(WORK_STATE); + if (StringUtils.isEmpty(json)) { + return null; + } else { + Map a = GsonUtils.fromJson(json, new TypeToken>() { + }.getType()); + return a; + } + } + + public static boolean isFaceActivated() { + return !StringUtils.isEmpty(MMKVUtil.get(FACE_IS_ACTIVATED)); + } + + /** + * 地暖别名 + */ + public static final String FLOOR_NAME = "FLOOR_NAME"; + + public static String getFloorName() { + return MMKVUtil.get(FLOOR_NAME, RoomController.floor.getName()); + } + + /** + * 空调后台线程监测当前红外状态以调节空调物理开关的循环检查时间 + * 单位:秒 + */ + public static final String TIME_OF_AIR_STOP_RELAY = "TIME_OF_AIR_STOP_RELAY"; + + public static long getTimeOfAirStopRelay() { + return MMKVUtil.get(TIME_OF_AIR_STOP_RELAY, 4 * 60); + } + + /*** + * 控制空调时先开开关后调节温度 + * boolean + */ + public static final String CONTROL_AIR_START_FIRST = "CONTROL_AIR_START_FIRST"; + public static boolean isAirStartFirst() { + return MMKVUtil.get(CONTROL_AIR_START_FIRST, true); + } + /*** + * 全局手动调整温度偏差 + * double + */ + public static final String BIAS_TEMP = "BIAS_TEMP"; + public static double getBiasTemp() { + return MMKVUtil.get(BIAS_TEMP, 0.0); + } + /*** + * 全局手动调整温度偏差 + * double + */ + public static final String BIAS_HUMIDITY = "BIAS_HUMIDITY"; + public static double getBiasHumidity() { + return MMKVUtil.get(BIAS_HUMIDITY, 0.0); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/RxTag.java b/app/src/main/java/com/example/iot_controlhost/utils/global/RxTag.java new file mode 100644 index 0000000..8d28ab6 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/RxTag.java @@ -0,0 +1,148 @@ +package com.example.iot_controlhost.utils.global; + +public class RxTag { + //RxBus——————————————————————————————————————————————————————————————————— + /** + * 更新自动模式下的参数 + */ + public static final String UPDATE_AUTO = "UPDATE_AUTO"; + /** + * 更新主界面UI + */ + public static final String UPDATE_MAIN = "UPDATE_MAIN_LOG"; + /** + * 更新主界面UI-新消息 + */ + public static final String UPDATE_MAIN_MSG = "UPDATE_MAIN_MSG"; + /** + * 更新手动模式UI + */ + public static final String UPDATE_MANUAL = "UPDATE_MANUAL"; + /** + * 在主界面显示警告画面 + */ + public static final String MAIN_WARNING = "MAIN_WARNING"; + /** + * 刷新智能模式下所有信息 + */ + public static final String AI_INFO = "AI_INFO"; + + //DisposablePool—————————————————————————————————————————————————————————— + /** + * 自动模式-温度 + */ + public static final String AUTO_TEMPERATURE = "AUTO_TEMPERATURE"; + /** + * 自动模式-加湿 + */ + public static final String AUTO_HUMIDITY = "AUTO_HUMIDITY"; + /** + * 自动模式-匀风 + */ + public static final String AUTO_EVEN = "AUTO_EVEN"; + /** + * 智能模式开始共育循环更新图表当前位置 + */ + public static final String AI_CULTIVATE = "AI_CULTIVATE"; + /** + * 预警上报-设备错误检测 + */ + public static final String DEVICE_ANOMALY_DETECTION = "DEVICE_ANOMALY_DETECTION"; + /** + * 持续采集传感器数据 + */ + public static final String TAG_COLLECT_SENSOR = "TAG_COLLECT_SENSOR"; + /** + * 持续采集控制器能耗数据 + */ + public static final String TAG_COLLECT_CONTROLLER = "TAG_COLLECT_CONTROLLER"; + /** + * 持续检测除湿机工作状态 + */ + public static final String TAG_DEHUMIDIFIER_STATUS = "TAG_DEHUMIDIFIER_STATUS"; + /** + * 持续判断是否注册 + */ + public static final String CHECK_REGISTER = "CHECK_REGISTER"; + /** + * 主界面循环更新UI,在离开主界面可以停止 + */ + public static final String MAIN_UI = "MAIN_UI"; + /** + * 主界面循环MQTT上报最新设备状态信息,在离开主界面可以停止 + */ + public static final String MAIN_UPLOAD = "MAIN_UPLOAD"; + /** + * 手动模式自动刷新UI + */ + public static final String MANUAL_UI = "MANUAL_UI"; + /** + * MQTT自动刷新 + */ + public static final String MQTT_CONNECT = "MQTT_CONNECT"; + /** + * 自动模式-温控-UI-刷新 + */ + public static final String AUTO_MODE_UI_REFRESH = "AUTO_MODE_UI_REFRESH"; + /** + * 循环获取Token + */ + public static final String GET_TOKEN = "GET_TOKEN"; + /** + * 信息界面循环获取最新数据 + */ + public static final String INFO_GET_SENSOR_DATA = "INFO_GET_SENSOR_DATA"; + /** + * 循环更新BarUI-日期 + */ + public static final String BAR_UI = "BAR_UI"; + /** + * 循环更新BarUI-天气 + */ + public static final String BAR_UI_WEATHER = "BAR_UI_WEATHER"; + /** + * 作业弹窗-持续刷新工作时间 + */ + public static final String WORK_TIME = "WORK_TIME"; + /** + * 作业弹窗-持续刷新继电器状态 + */ + public static final String GET_RELAY_SENSOR_DATA = "GET_RELAY_SENSOR_DATA"; + /** + * 刷新共育状态 + */ + public static final String REFRESH_CUL_STATE = "REFRESH_CUL_STATE"; + /** + * 上传设备状态 + */ + public static final String UPLOAD_DEVICE_STATE = "UPLOAD_DEVICE_STATE"; + /** + * 轮询-自动模式关闭换气扇 + */ + public static final String VENTILATOR_AUTO_STOP_FUN = "VENTILATOR_AUTO_STOP_FUN"; + /** + * 轮询-自动模式开启换气扇 + */ + public static final String VENTILATOR_AUTO_START_FUN = "VENTILATOR_AUTO_START_FUN"; + /** + * 轮询-设置中的传感器检测 + */ + public static final String TAG_COLLECT_SENSOR_SET = "TAG_COLLECT_SENSOR_SET"; + /** + * 轮询-Ping百度以获取网络可用性 + */ + public static final String PING_BAIDU = "PING_BAIDU"; + /** + * 轮询-检查控制器状态-空调 + */ + public static final String TAG_CONTROLLER_STATE_CHECK_AIR = "TAG_CONTROLLER_STATE_CHECK_AIR"; + /** + * 轮询-检查控制器状态-其他设备 + */ + public static final String TAG_CONTROLLER_STATE_CHECK_OTHER = "TAG_CONTROLLER_STATE_CHECK_OTHER"; + /** + * 长时间未启动 然后启动的 启动时间 + * 类型:时间戳,long + */ + public static String IS_LONG_TIME_NOT_START = "IS_FIRST_START"; +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/SpinnerList.java b/app/src/main/java/com/example/iot_controlhost/utils/global/SpinnerList.java new file mode 100644 index 0000000..d25669d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/SpinnerList.java @@ -0,0 +1,50 @@ +package com.example.iot_controlhost.utils.global; + +/** + * 通用数组 + */ +public class SpinnerList { + /** + * 地址 + */ + public static String[] ADDRESS = {"01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "60", "61", + "62", "63", "64", "65", "66", "67", "68", "69", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "00", "FF"}; + /** + * 串口地址 + */ + public static String[] SERIAL_ADDRESS = {"/dev/ttyS0", "/dev/ttyS1", "/dev/ttyS2", "/dev/ttyS3", "/dev/ttyS4", "/dev/ttyS5", + "/dev/ttyS6", "/dev/ttyS7", "/dev/ttyS8", "/dev/ttyS9", "/dev/ttyFIQ0"}; + /** + * 波特率 + */ + public static String[] BAUD = {"1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"}; + /** + * 自动模式-升温方式 + */ + public static String[] TEMPERATURE_HEAT = {"均不启用", "地暖", "空调", "地暖和空调"}; + /** + * 自动模式-降温方式 + */ + public static String[] TEMPERATURE_COOL = {"均不启用", "空调"}; + /** + * 自动模式-模式 + */ + public static String[] TEMPERATURE_MODE = {"高精度模式", "普通模式", "节能模式"}; + /** + * 自动模式-匀风扇控制算法 + */ + public static String[] AUTO_EVEN_MODES = {"手动控制", "升温时跟随启动", "降温时跟随启动", "升降温时跟随启动"}; + /** + * 换气扇控制算法 + */ + public static String[] VENTILATION_MODES = {"全部开启", "仅开进气扇", "仅开排气扇"}; + /** + * 新风控制算法 + */ + public static String[] AIR_EXCHANGE_MODES = {"手动控制","随换气扇启动", "随加湿器启动"}; + /** + * 通用 + */ + public static String[] YES_OR_NO = {"是","否"}; + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/global/Variable.java b/app/src/main/java/com/example/iot_controlhost/utils/global/Variable.java new file mode 100644 index 0000000..788e77e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/global/Variable.java @@ -0,0 +1,119 @@ +package com.example.iot_controlhost.utils.global; + +/** + * 系统默认值,需要开发人员手动设置 + * + * @Author DuanKaiji + */ +public class Variable { + /** + * 记录传感器历史值时间间隔 + * 单位:毫秒 + */ + public static final double SENSOR_RECORD_TIME = 60 * 1000; + /** + * 刷新获取传感器数据的时间间隔 + * 单位:秒 + */ + public static final long SENSOR_REFRESH_TIME = 5; + /** + * 温湿度异常持续多长时间则上报-公众号 + * + * 最好为@DEVICE_ANOMALY_CHECK_TIME / 60 的倍数 + * 单位:分钟 + */ + public static final long WARNING_REPORT_TIME_INTERVAL = 20; + /** + * 温湿度异常持续多长时间则上报-语音 + * + * 必须大于 @WARNING_REPORT_TIME_INTERVAL + * 最好为 (@DEVICE_ANOMALY_CHECK_TIME / 60) 的倍数 + * 单位:分钟 + */ + public static final long WARNING_REPORT_TIME_INTERVAL_VOICE = 30; + /** + * 其他设备,使保持运行状态的循环检查时间 + * 单位:秒 + */ + public static final long CHECK_CLOSE_INFRARED = 4 * 60; + /** + * 开启物理开关后开启空调遥控的等待时间 + * 单位:毫秒 + */ + public static final long WAIT_START_INFRARED = 2 * 1000; + /** + * 开启物理开关后开启空调遥控的等待时间 + * 单位:毫秒 + */ + public static final long WAIT_START_INFRARED_FOR_DEHUMIDIFIER = 5 * 1000; + /** + * 自动设置地暖PWM时的功率误差比例 + */ + public static final double FLOOR_GEAR_BIAS = 0.02; + /** + * 刷新共育状态 + * 单位:秒 + */ + public static final long REFRESH_CULTURE_STATE_TIME = 60 * 60 * 1; + /** + * MQTT自动上报循环时间-传感器信息 + * 单位:秒 + */ + public static final long UPLOAD_SENSOR_DATA_TIME = 10; + /** + * 自动模式下检测更新策略时间-温度、湿度、换气 + * 单位:秒 + */ + public static final long AUTO_CHECK_TIME = 10; + /** + * 空调/地暖 略过相同操作控制的次数 + */ + public static final int SAME_OPERATE_TIMES = 5; + + /** + * 自动模式下各种温度控制算法的“不作为”范围-普通模式/高精度模式 + * 单位:摄氏度 + */ + public static final double AUTO_DO_NOTHING_TEMPERATURE_NORMAL = 1; + /** + * 自动模式下-高精度模式-(目标与极值)最小温度差 + * 单位:摄氏度 + */ + public static final double AUTO_DO_NOTHING_TEMPERATURE_FULL = 0.2; + /** + * 自动模式下各种温度控制算法的“不作为”范围-节能模式 + * 单位:摄氏度 + */ + public static final double AUTO_DO_NOTHING_TEMPERATURE_SAVING = 2; + /** + * 自动模式下各种湿度控制算法的“不作为”范围 + * 单位:百分比 + */ + public static final double AUTO_DO_NOTHING_HUMIDITY = 5; + /** + * 硬性错误次数达到多少次则上报 + * 单位:次数 + */ + public static final int WARNING_REPORT_ERROR_TIMES = 30; + /** + * 防抖拦截时间,即此时间内的点击事件不会被重复执行 + * 单位:毫秒 + */ + public static final long CLICK_INTERVAL = 600; + /** + * 地暖最大温度限制 + * 单位:摄氏度 + */ + public static final double FLOOR_MAX_TEMPERATURE_LIMIT = 38; + + /** + * 设备异常检测轮询时间 + * 在连续3次后报告 + * 单位:秒 + */ + public static final long DEVICE_ANOMALY_CHECK_TIME = 5 * 60; + /** + * 长时间未开始 的判定时间 + */ + public static long LONG_TIME_NOT_START = 60 * 60 * 24 * 5; // 5 days +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java new file mode 100644 index 0000000..183c484 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/log/CrashHandlerUtil.java @@ -0,0 +1,72 @@ +package com.example.iot_controlhost.utils.log; + + +import android.content.Context; +import android.content.Intent; + +import com.example.iot_controlhost.utils.network.TopicClass; +import com.jakewharton.processphoenix.ProcessPhoenix; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; + +import java.util.Arrays; + +public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler { + /** + * 饿汉式单例模式 + */ + private static CrashHandlerUtil crashHandlerUtil = new CrashHandlerUtil(); + private Thread.UncaughtExceptionHandler mDefaultCaughtExceptionHandler; + private Context mContext; + + private CrashHandlerUtil() { + // 私有构造函数,确保单例模式 + } + + public static CrashHandlerUtil getInstance() { + return crashHandlerUtil; + } + + public void init(Context context) { + mContext = context; + mDefaultCaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + ex.printStackTrace(); + MyLog.appError("软件已崩溃,重启应用:" + getFormattedException(ex)); + TopicClass.uploadLog(4, 3, "已崩溃,系统将自动重启:" + getFormattedException(ex)); + new Thread(() -> { + try { Thread.sleep(500); } catch (Exception ignored) { } + ProcessPhoenix.triggerRebirth(mContext); + }).start(); + + } + + public String getFormattedException(Throwable throwable) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("异常详情:\n") + .append("异常类型:").append(throwable.getClass().getName()).append("\n") + .append("异常消息:").append(throwable.getMessage() != null ? throwable.getMessage() : "无").append("\n"); + + StackTraceElement[] stackTrace = throwable.getStackTrace(); + if (stackTrace != null && stackTrace.length > 0) { + stringBuilder.append("异常位置:").append(stackTrace[0].toString()).append("\n"); + } + + stringBuilder.append("完整堆栈跟踪:\n"); + for (StackTraceElement element : stackTrace) { + stringBuilder.append("\t").append(element.toString()).append("\n"); + } + + Throwable cause = throwable.getCause(); + if (cause != null) { + stringBuilder.append("异常原因:").append(cause.toString()).append("\n"); + } + + return stringBuilder.toString(); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java b/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java new file mode 100644 index 0000000..c4e398a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/log/MyLog.java @@ -0,0 +1,170 @@ +package com.example.iot_controlhost.utils.log; + +import static com.example.iot_controlhost.utils.log.UserLog.TAG_USER_OPERATE; +import static com.example.iot_controlhost.utils.log.UserLog.TAG_USER_SENSOR; +import static com.example.iot_controlhost.utils.log.UserLog.TAG_USER_SYSTEM; +import static com.example.iot_controlhost.utils.log.UserLog.TAG_USER_TASK; + +import android.util.Log; + +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import timber.log.Timber; + +public class MyLog extends Timber.Tree { + public static final String TAG_ERROR = "Error"; + /** + * 测试类 + */ + public static final String TAG_TEST = "调试"; + /** + * 预警类 + */ + public static final String TAG_WARNING = "预警"; + + /** + * 传感器类 + */ + public static final String TAG_SENSOR = "传感器"; + + /** + * 控制器类 + */ + public static final String TAG_CONTROLLER = "控制器"; + + /** + * 网络类 + */ + public static final String TAG_NETWORK = "网络"; + + /** + * 系统类 + */ + public static final String TAG_APP = "系统"; + + /** + * 远程控制类 + */ + public static final String TAG_REMOTE = "远程控制"; + + /** + * 自动控制类 + */ + public static final String TAG_AUTO = "自动控制"; + /** + * 人脸识别类 + */ + public static final String TAG_FACE = "人脸识别"; + /** + * 远程控制 + */ + public static final String TAG_FRP = "远程协助"; + + public static List getUserQueryTag() { + List tags = new ArrayList<>(); + Collections.addAll(tags, TAG_APP, TAG_REMOTE, TAG_CONTROLLER, TAG_WARNING,TAG_SENSOR, TAG_NETWORK, TAG_AUTO); + return tags; + } + + public static void test(String msg) { + Timber.tag(TAG_TEST).i(msg); + } + + public static void auto(String msg) { + Timber.tag(TAG_AUTO).i(msg); + } + + public static void autoError(String msg) { + Timber.tag(TAG_AUTO + TAG_ERROR).e(msg); + } + + public static void warning(String msg) { + Timber.tag(TAG_WARNING).i(msg); + } + + public static void warningError(String msg) { + Timber.tag(TAG_WARNING + TAG_ERROR).e(msg); + } + + public static void sensor(String msg) { + Timber.tag(TAG_SENSOR).i(msg); + } + + public static void sensorError(String msg) { + Timber.tag(TAG_SENSOR + TAG_ERROR).e(msg); + } + + public static void controller(String msg) { + Timber.tag(TAG_CONTROLLER).i(msg); + } + + public static void controllerError(String msg) { + Timber.tag(TAG_CONTROLLER + TAG_ERROR).e(msg); + } + + public static void network(String msg) { + Timber.tag(TAG_NETWORK).i(msg); + } + + public static void networkError(String msg) { + Timber.tag(TAG_NETWORK + TAG_ERROR).e(msg); + } + + public static void app(String msg) { + Timber.tag(TAG_APP).i(msg); + } + + public static void appError(String msg) { + Timber.tag(TAG_APP + TAG_ERROR).e(msg); + } + + public static void remote(String msg) { + Timber.tag(TAG_REMOTE).i(msg); + } + + public static void remoteError(String msg) { + Timber.tag(TAG_REMOTE + TAG_ERROR).e(msg); + } + + public static void face(String msg) { + Timber.tag(TAG_FACE).i(msg); + } + + public static void faceError(String msg) { + Timber.tag(TAG_FACE + TAG_ERROR).e(msg); + } + + public static void frp(String msg) { + Timber.tag(TAG_FRP).i(msg); + } + + public static void frpError(String msg) { + Timber.tag(TAG_FRP + TAG_ERROR).e(msg); + } + + @Override + protected void log(int priority, String tag, String message, Throwable t) { + // 不处理UserLog日志 + if (tag.contains(TAG_USER_OPERATE) || tag.contains(TAG_USER_SENSOR) || tag.contains(TAG_USER_TASK) || tag.contains(TAG_USER_SYSTEM)) { + return; + } + RxJavaUtils.doInIOThread(new RxIOTask<>(null) { + @Override + public Void doInIOThread(Object o) { + //输出日志到控制台 + Log.println(priority, tag, message); + //保存日志到数据库 + LogDBManager.insertLog(new com.example.iot_controlhost.model.Log(tag, priority, new Date(), message)); + return null; + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java b/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java new file mode 100644 index 0000000..f14fb8d --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/log/UserLog.java @@ -0,0 +1,116 @@ +package com.example.iot_controlhost.utils.log; + +import android.util.Log; + +import com.example.iot_controlhost.utils.database.LogDBManager; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.network.TopicClass; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import timber.log.Timber; + +/** + * 日志用户查看用 + */ +public class UserLog extends Timber.Tree { + /** + * 用户-操作类 + */ + public static final String TAG_USER_OPERATE = "用户-操作类"; + /** + * 用户-传感器类 + */ + public static final String TAG_USER_SENSOR = "用户-传感器类"; + /** + * 用户-人工作业类 + */ + public static final String TAG_USER_TASK = "用户-作业类"; + /** + * 用户-系统类 + */ + public static final String TAG_USER_SYSTEM = "用户-系统类"; + + public static List getUserLogTags() { + List tags = new ArrayList<>(); + Collections.addAll(tags, TAG_USER_OPERATE, TAG_USER_SENSOR, TAG_USER_TASK, TAG_USER_SYSTEM); + return tags; + } + + public static void operate(String msg) { + Timber.tag(TAG_USER_OPERATE).i(msg); + } + + public static void operateError(String msg) { + Timber.tag(TAG_USER_OPERATE + MyLog.TAG_ERROR).e(msg); + } + + public static void task(String msg) { + Timber.tag(TAG_USER_TASK).i(msg); + } + + public static void taskError(String msg) { + Timber.tag(TAG_USER_TASK + MyLog.TAG_ERROR).e(msg); + } + + public static void sensor(String msg) { + Timber.tag(TAG_USER_SENSOR).i(msg); + } + + public static void sensorError(String msg) { + Timber.tag(TAG_USER_SENSOR + MyLog.TAG_ERROR).e(msg); + } + + public static void system(String msg) { + Timber.tag(TAG_USER_SYSTEM).i(msg); + } + + public static void systemError(String msg) { + Timber.tag(TAG_USER_SYSTEM + MyLog.TAG_ERROR).e(msg); + } + + @Override + protected void log(int priority, String tag, String message, Throwable t) { + //只有特定UserLog日志才能处理 + if (!tag.contains(TAG_USER_OPERATE) && !tag.contains(TAG_USER_SENSOR) && !tag.contains(TAG_USER_TASK) && !tag.contains(TAG_USER_SYSTEM)) { + return; + } + RxJavaUtils.doInIOThread(new RxIOTask<>(null) { + @Override + public Void doInIOThread(Object o) { + //发送日志到服务器 + int tagInt = 0; + if (tag.contains(TAG_USER_OPERATE)) { + tagInt = 1; + } else if (tag.contains(TAG_USER_SENSOR)) { + tagInt = 2; + } else if (tag.contains(TAG_USER_TASK)) { + tagInt = 3; + } else if (tag.contains(TAG_USER_SYSTEM)) { + tagInt = 4; + } + int newPriority; + //1调试 2信息 3警告 + if (tag.contains(MyLog.TAG_ERROR)) { + newPriority = 3; + } else { + newPriority = 2; + } + //保存日志到数据库 + LogDBManager.insertLog(new com.example.iot_controlhost.model.Log(tag, priority, new Date(), message)); + //输出日志到控制台 + Log.println(priority, tag, message); + //上传日志 + TopicClass.uploadLog(tagInt, newPriority, message); + return null; + } + }); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/mainboard/API.java b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/API.java new file mode 100644 index 0000000..7694449 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/API.java @@ -0,0 +1,29 @@ +package com.example.iot_controlhost.utils.mainboard; + +/** + * @Description 主板API父类 + * @Author DuanKaiji + * @CreateTime 2024年01月05日 14:33:23 + */ +public abstract class API { + /** + * 初始化 + * 持续喂狗 + */ + public abstract void init(); + + /** + * 关机 + */ + public abstract void shutDown(); + + /** + * 重启 + */ + public abstract void reBoot(); + + /** + * 看门狗-喂狗 + */ + public abstract void feed(); +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/mainboard/MyAPIContext.java b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/MyAPIContext.java new file mode 100644 index 0000000..701383f --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/MyAPIContext.java @@ -0,0 +1,65 @@ +package com.example.iot_controlhost.utils.mainboard; + +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.log.MyLog; + +/** + * @Description + * @Author DuanKaiji + * @CreateTime 2024年01月05日 14:33:49 + */ +public class MyAPIContext { + private static API myAPI; + private static MyAPIContext myAPIContext; + private boolean canUse; + + public static MyAPIContext getInstance() { + synchronized (MyAPIContext.class) { + if (myAPIContext == null) { + synchronized (MyAPIContext.class) { + if (myAPIContext == null) { + myAPIContext = new MyAPIContext(); + } + } + } + } + return myAPIContext; + } + + private MyAPIContext() { + if (HardwareSetting.isZCAndroid()) { + myAPI = new ZCAPI(); + } else if (HardwareSetting.isYSAndroid()) { + myAPI = new YSAPI(); + } else { + MyLog.app("未匹配到主板,不使用主板API"); + canUse = false; + return; + } + canUse = true; + } + + public void feet() { + if (canUse) { + myAPI.feed(); + } + } + + public void init() { + if (canUse) { + myAPI.init(); + } + } + + public void shutDown() { + if (canUse) { + myAPI.shutDown(); + } + } + + public void reBoot() { + if (canUse) { + myAPI.reBoot(); + } + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/mainboard/YSAPI.java b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/YSAPI.java new file mode 100644 index 0000000..d495341 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/YSAPI.java @@ -0,0 +1,89 @@ +package com.example.iot_controlhost.utils.mainboard; + +import android.os.Build; + +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.log.MyLog; +import com.ys.mcu7502.Mcu7502; +import com.ys.rkapi.MyManager; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @Description 亿晟API + * @Author DuanKaiji + * @CreateTime 2024年01月05日 14:36:37 + */ +public class YSAPI extends API { + private MyManager manager; + + @Override + public void init() { + manager = MyManager.getInstance(MyApp.getAppContext()); + if (-1 == Mcu7502.open()) { + MyLog.app("亿晟——看门狗初始化失败"); + return; + } + String path; + if (Build.MODEL.contains("rk312")) { + path = "/sys/devices/misc_power_en.19/mcu"; + } else { + path = "/sys/devices/platform/misc_power_en/mcu"; + } + MyLog.app("亿晟——看门狗:" + path); + if (7 == Mcu7502.enableWatchdog(path)) { + MyLog.app("设置属性为1,表示启用看门狗"); + setValueToProp("persist.sys.watchdog", "1"); // 设置属性为1,表示启用看门狗 + } + MyLog.app("亿晟看门狗状态:" + (Mcu7502.getWatchdogStatus() == 0 ? "未启用" : "启用")); + } + + @Override + public void shutDown() { + manager.shutdown(); + } + + @Override + public void reBoot() { + manager.reboot(); + } + + @Override + public void feed() { + MyLog.app("开始持续喂狗"); + PollingTask.getInstance("FeedDog").startPollingTaskOnIOThread("Feed", 5, () -> { + if (7 != Mcu7502.feetDog(10)) { +// MyLog.app("亿晟喂狗失败"); + } else { +// MyLog.app("亿晟喂狗成功"); + } + }); + + } + + private void closeDogs() { + Mcu7502.disableWatchdog(); + Mcu7502.close(); + setValueToProp("persist.sys.watchdog", "0"); + } + + public static void setValueToProp(String key, String val) { + Class classType; + try { + classType = Class.forName("android.os.SystemProperties"); + Method method = classType.getDeclaredMethod("set", new Class[]{String.class, String.class}); + method.invoke(classType, new Object[]{key, val}); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/mainboard/ZCAPI.java b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/ZCAPI.java new file mode 100644 index 0000000..377f6fb --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/mainboard/ZCAPI.java @@ -0,0 +1,48 @@ +package com.example.iot_controlhost.utils.mainboard; + +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.utils.PollingTask; +import com.example.iot_controlhost.utils.log.MyLog; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.zcapi; + +/** + * @Description 卓策API + * @Author DuanKaiji + * @CreateTime 2024年01月05日 14:36:37 + */ +public class ZCAPI extends API { + + zcapi zcApi; + + @Override + public void init() { + // 初始化zcApi + try { + zcApi = new zcapi(); + zcApi.getContext(MyApp.getAppContext()); + zcApi.setStatusBar(true); + zcApi.setGestureStatusBar(true); + MyLog.app("看门狗初始化成功"); + } catch (Exception e) { + e.printStackTrace(); + MyLog.appError("看门狗初始化失败:" + e.getMessage()); + } + } + + @Override + public void shutDown() { + zcApi.shutDown(); + } + + @Override + public void reBoot() { + zcApi.reboot(); + } + + @Override + public void feed() { + MyLog.app("开始持续喂狗"); + PollingTask.getInstance("FeedDog").startPollingTaskOnIOThread("Feed", 45, () -> zcApi.watchDogEnable(true)); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/NetState.java b/app/src/main/java/com/example/iot_controlhost/utils/network/NetState.java new file mode 100644 index 0000000..16ad6bd --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/NetState.java @@ -0,0 +1,138 @@ +package com.example.iot_controlhost.utils.network; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; + +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +public class NetState { + /** + * 信号质量(WIFI、4G) + */ + public static int Semaphore = 0; + + /** + * 判断网络连接方式 + * + * @param context + * @return 0:无网络 1:有线网络 2:WIFI 3:移动网络 + */ + public static int getNetworkType(Context context) { + int result = 0; + String lastIP = HardwareSetting.getIPAddress(); + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager != null) { + NetworkInfo info = connectivityManager.getActiveNetworkInfo(); + if (info != null && info.isConnected()) { + int networkType = info.getType(); + switch (networkType) { + case ConnectivityManager.TYPE_ETHERNET: + // 有线网络 + String ethernetIpAddress = getEthernetIPAddress(); + MMKVUtil.put(HardwareSetting.IP_ADDRESS, ethernetIpAddress); + result = 1; + break; + case ConnectivityManager.TYPE_WIFI: + // WIFI + String wifiIpAddress = getWifiIPAddress(context); + MMKVUtil.put(HardwareSetting.IP_ADDRESS, wifiIpAddress); + result = 2; + break; + case ConnectivityManager.TYPE_MOBILE: + // 移动网络 + String mobileIpAddress = getMobileIPAddress(); + MMKVUtil.put(HardwareSetting.IP_ADDRESS, mobileIpAddress); + result = 3; + break; + } + } else { + MyLog.networkError("当前没有可用网络"); + } + } else { + MyLog.networkError("网络状态获取失败"); + } + if (!lastIP.equals(HardwareSetting.getIPAddress())) { + MyLog.test("IP地址发生变化:旧IP地址:" + lastIP + ",新IP地址:" + HardwareSetting.getIPAddress()); + } + return result; + } + + /** + * 获取有线网络IPv4地址 + */ + private static String getEthernetIPAddress() { + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface iface = interfaces.nextElement(); + if (iface.isUp() && !iface.isLoopback() && !iface.isVirtual()) { + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (!addr.isLoopbackAddress() && addr.getAddress().length == 4) { + return addr.getHostAddress(); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取WIFI网络IPv4地址 + */ + private static String getWifiIPAddress(Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + if (wifiManager != null) { + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + int ipAddress = wifiInfo.getIpAddress(); + return intToIp(ipAddress); + } + return null; + } + + /** + * 获取移动网络IPv4地址 + * @return + */ + private static String getMobileIPAddress() { + try { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface iface = interfaces.nextElement(); + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (!addr.isLoopbackAddress() && addr.getAddress().length == 4) { + return addr.getHostAddress(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 将IP地址的整数形式转换成字符串形式 + */ + private static String intToIp(int ipAddress) { + return (ipAddress & 0xFF) + "." + + ((ipAddress >> 8) & 0xFF) + "." + + ((ipAddress >> 16) & 0xFF) + "." + + (ipAddress >> 24 & 0xFF); + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/PublicNetRequest.java b/app/src/main/java/com/example/iot_controlhost/utils/network/PublicNetRequest.java new file mode 100644 index 0000000..d8ec5c9 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/PublicNetRequest.java @@ -0,0 +1,242 @@ +package com.example.iot_controlhost.utils.network; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; +import android.widget.ImageView; + +import androidx.core.content.FileProvider; + +import com.blankj.utilcode.util.FileUtils; +import com.example.iot_controlhost.R; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.ui.dialog.ConfirmDialog; +import com.example.iot_controlhost.utils.DialogUtil; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.QRCodeUtil; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.MyLog; + +import org.json.JSONException; +import org.json.JSONObject; +import org.xutils.common.Callback; +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.io.File; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 通用网络请求 + * @Author DuanKaiji + * @CreateTime 2023年10月19日 15:18:50 + */ +public class PublicNetRequest { + + /** + * 读取登记作业-签到图片-网络请求 + */ + public static void loadSignImage(Context mContext, ImageView imageView) { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.qrCodeCreate); + params.addParameter("AreaCode", RoomSetting.getAreaCode()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) throws JSONException { + String data = model.getData().toString(); + String codeUrl = new JSONObject(data).getString("url"); + Bitmap logoBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.logo_blue); + if (imageView != null) { + imageView.setImageBitmap(QRCodeUtil.createQRCodeBitmap(codeUrl, 300, 300, logoBitmap, 30)); + } + } + + @Override + public void error(Throwable e) { + Toasty.error(mContext, "扫码签到图片生成失败").show(); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + + /** + * 语音通知-设备故障预警 + * + * @param deviceName 设备名称 + * @param noticeTypeCode 通知类型 + * 只发公众号 0默认 + * 语音+公众号 1数据采集故障 2设备操作故障 3设备疑似故障 + * @param noticeLevel 通知等级 + * @param noticeContent 通知内容 + */ + public static void AnomalyNotice(String deviceName, int noticeTypeCode, int noticeLevel, String noticeContent) { + if (RoomSetting.getNoticeType().equals(RoomSetting.NOTICE_TYPE_SPINNER[0])) { + MyLog.warning("需要预警通知,但是用户设置不需要"); + return; + } else if (RoomSetting.getNoticeType().equals(RoomSetting.NOTICE_TYPE_SPINNER[1]) && noticeTypeCode != 0) { + MyLog.warning("需要语音通知,但是用户设置不需要,修改为公众号通知"); + noticeTypeCode = 0; + } + MyLog.warning("预警:" + + "\n方式:" + (noticeTypeCode == 0 ? "公众号" : "公众号+语音") + + "\n内容:" + noticeContent); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.notice); + params.addParameter("areaCode", RoomSetting.getAreaCode()); + params.addParameter("DeviceName", deviceName); + params.addParameter("NoticeTypeCode", String.valueOf(noticeTypeCode)); + params.addParameter("OccurTime", MyUtil.getDateTime()); + params.addParameter("NoticeLevel", noticeLevel); + params.addParameter("NoticeContent", noticeContent); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + MyLog.warning("设备故障预警成功:发送内容:" + noticeContent); + } + + @Override + public void error(Throwable e) { + MyLog.warning("设备故障预警失败:" + e.getMessage()); + } + }); + } + + /** + * 公众号发送温湿度预警消息 + * + * @param temperatureStr 异常温度 + * @param humidityStr 异常湿度 + * @param remarks 提示信息 + */ + public static void THWarningToWeChat(String temperatureStr, String humidityStr, String remarks) { + // 发送预警 + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.thWarning); + params.addParameter("Code", RoomSetting.getAreaCode()); + params.addParameter("Temperature", temperatureStr); + params.addParameter("Humidity", humidityStr); + params.addParameter("Remarks", remarks); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + //发送完成 + MyLog.warning("温湿度预警信息发送成功"); + } + + @Override + public void error(Throwable e) { + MyLog.warningError("温湿度预警信息发送失败:" + e.getMessage()); + } + + @Override + public void onFinish() { + super.onFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 下载APK + * + * @param path 下载地址 + */ + public static void downFile(Context context, final String path) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { + Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + context.startActivity(intent); + return; + } + String fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator + "BBIT_FRP.apk"; + FileUtils.delete(fileName); + String dirPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + File currentFile = new File(dirPath, "BBIT_FRP.apk"); + File lastFile = new File(dirPath, "BBIT_FRP_last.apk"); + + // 如果存在旧的备份文件,先删除(只保留一个) + if (lastFile.exists()) { + lastFile.delete(); + } + if (currentFile.exists()) { + currentFile.renameTo(lastFile); + } + RequestParams params = new RequestParams(path); + params.setSaveFilePath(currentFile.getAbsolutePath()); + params.setAutoRename(true); + x.http().get(params, new Callback.ProgressCallback() { + @Override + public void onStarted() { + DialogUtil.showProgressIndicatorDialog(context, "开始下载必需文件"); + } + + @Override + public void onLoading(long total, long current, boolean isDownloading) { + if (isDownloading) { + int progress = (int) ((current * 100) / total); + DialogUtil.setProgressIndicatorDialog("正在下载中,当前已下载" + progress + "%", progress); + } + } + + @Override + public void onSuccess(File result) { + MyLog.network("下载成功: " + result); + installAPK(context, fileName); + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + ex.printStackTrace(); + MyLog.networkError("下载失败,原因:" + ex.getMessage()); + new ConfirmDialog(context, context.getString(R.string.tip), "下载失败,原因:" + ex.getMessage(), null).show(); + } + + @Override + public void onWaiting() { + } + + @Override + public void onCancelled(CancelledException cex) { + } + + @Override + public void onFinished() { + DialogUtil.hideProgressIndicatorDialog(); + } + }); + } + + /** + * 安装程序 + * + * @param context 上下文 + * @param filePath 安装的文件路径 + */ + public static void installAPK(Context context, String filePath) { + try { + File apkFile = new File(filePath); + if (apkFile.exists()) { + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else { + Toasty.error(context, "安装包文件不存在").show(); + } + } catch (Exception e) { + e.printStackTrace(); + Toasty.error(context, "安装失败:" + e.getMessage()).show(); + } + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java b/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java new file mode 100644 index 0000000..ce5d93e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/TopicClass.java @@ -0,0 +1,687 @@ +package com.example.iot_controlhost.utils.network; + +import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.StringUtils; +import com.example.iot_controlhost.model.controller.AirConditionInfrared; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.Sensor; +import com.example.iot_controlhost.model.sensor.SensorInfo; +import com.example.iot_controlhost.model.thread.QueueIOTask; +import com.example.iot_controlhost.service.MQTTService; +import com.example.iot_controlhost.utils.AppUpdateUtil; +import com.example.iot_controlhost.utils.FrpUpdateUtil; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.MyQueue; +import com.example.iot_controlhost.utils.MyUtil; +import com.example.iot_controlhost.utils.control.RemoteControl; +import com.example.iot_controlhost.utils.control.infrared.MyInfraredUtils; +import com.example.iot_controlhost.utils.global.AIModelSet; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.RxTag; +import com.example.iot_controlhost.utils.log.MyLog; +import com.example.iot_controlhost.utils.log.UserLog; +import com.example.iot_controlhost.utils.old.OldSet; +import com.xuexiang.rxutil2.rxbus.RxBusUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * MQTT 主题 + */ +public class TopicClass { + /** + * 临时变量,用于判断是否需要上传数据 + */ + private static String energyDataTemp = ""; + private static String areaDataTemp = ""; + private static String sensorDataTemp = ""; + private static String sensorInfoDataTemp = ""; + + //————————————————————————————————————————————————————————————————————————————————获取区域集合———————————————————————————————————————————————————————————————— + + /** + * 获取区域集合-发布 + */ + public static String arealistTopic() { + return "arealist/" + HardwareSetting.getHostId() + "/get"; + } + + /** + * 获取区域集合-发布 + */ + public static void getAreaList() { + MQTTService.publish(arealistTopic(), "{\"code\": \"0\"}"); + } + + /** + * 获取区域集合-订阅 + */ + public static String arealistGet() { + return "arealist/" + HardwareSetting.getHostId() + "/get_reply"; + } + + /** + * 获取区域集合-订阅数据解析 + */ + public static void handleAreaListMessage(String message) throws JSONException { + MyLog.network("MQTT接收到'区域数据'消息"); + JSONObject json = new JSONObject(message); + JSONArray jsons = json.getJSONArray("areas"); + boolean canUse = false; + if (jsons.length() != 0) { + //虽然可能有多个区域,但以可用的最后一个区域为主(因本系统设计初衷只服务于单个区域) + for (int i = 0; i < jsons.length(); i++) { + JSONObject jsonObject = jsons.getJSONObject(i); + String code = jsonObject.getString("code"); + String name = jsonObject.getString("name"); + MMKVUtil.put(RoomSetting.ROOM_NAME, name); + MMKVUtil.put(RoomSetting.AREA_CODE, code); + //修改为可用 + canUse = true; + } + } + RoomSetting.setRoomEnable(canUse); + RxBusUtils.get().post(RxTag.UPDATE_MAIN, 1); + } + + //————————————————————————————————————————————————————————————————————————————————获取主机基础信息———————————————————————————————————————————————————————————————— + + /** + * 获取主机基础信息-订阅 + */ + public static String hostbaseinfoGet() { + return "hostbaseinfo/" + HardwareSetting.getHostId() + "/get_reply"; + } + + //————————————————————————————————————————————————————————————————————————————————数据透传———————————————————————————————————————————————————————————————— + + /** + * 数据透传-发布 + */ + public static String serianetTopic() { + return "serianet/" + HardwareSetting.getHostId() + "/release"; + } + + /** + * 数据透传 数据接收成功 + * 原样返回 + */ + public static void receiveSuccess(String json) { + MQTTService.publish(serianetTopic(), json); + } + + /** + * 数据透传-订阅 + */ + public static String serianetGet() { + return "serianet/" + HardwareSetting.getHostId() + "/excute"; + } + + /** + * 数据透传-订阅解析数据 + */ + public static void hanldeSerianetMessage(String msg) { + String tip = null; + switch (msg) { + case "重启" -> { + tip = "远程:重启设备"; + MyUtil.reboot(); + } + case "关机" -> { + tip = "远程:关闭设备"; + MyUtil.shutDown(); + } + case "重启软件" -> { + tip = "远程:重启软件"; + MyUtil.relaunchApp(); + } + case "更新" -> { + tip = "远程:强制更新"; + AppUpdateUtil.updateAPP(ActivityUtils.getTopActivity(), true, true); + } + case "下载frp" -> { + tip = "远程:更新远程控制软件"; + FrpUpdateUtil.check(ActivityUtils.getTopActivity(), true); + } + case "检测更新" -> { + tip = "远程:检测更新"; + AppUpdateUtil.updateAPP(ActivityUtils.getTopActivity(), false, false); + } + } + if (!StringUtils.isEmpty(tip)) { + TopicClass.receiveSuccess(msg); + UserLog.operate(tip); + } + } + + //————————————————————————————————————————————————————————————————————————————————区域环境控制指令———————————————————————————————————————————————————————————————— + + /** + * 区域环境控制指令-订阅 + */ + public static String areaenvcontrolGet() { + return "areaenvcontrol/" + HardwareSetting.getHostId() + "/set"; + } + + /** + * 区域环境控制指令-订阅数据解析 + * + * @param msg 数据 + */ + public static void handleUploadControlMessage(String msg) { + try { + JSONObject json = new JSONObject(msg); + String areaenvcontrolcommand = json.getString("areaenvcontrolcommand"); + JSONObject jsons = new JSONObject(areaenvcontrolcommand); + //区域唯一码RoomId + String code = jsons.getString("code"); + // 增加功能:如果不是当前区域或者当前区域不可用,则不执行远程控制指令 + if (!RoomSetting.getAreaCode().equals(code) && !RoomSetting.isRoomEnable()) { + return; + } + int controlCommand = jsons.getInt("controlcommand"); + int gear = jsons.getInt("gear"); + int value = jsons.getInt("value"); + //如果运行成功,则立即反馈 + uploadControl(jsons); + MyQueue.getInstance(MyQueue.TYPE_CONTROLLER).addTask(new QueueIOTask(() -> { + boolean operateSuccess = RemoteControl.remoteControl(controlCommand, gear, value); + if (operateSuccess) { + UserLog.operate("远程:" + getCommandNameById(controlCommand, value)); + //设置完远程控制后 上传最新当前状态 + if (controlCommand == 8) { + // 如果是切换模式,为优化速度,直接上传最新的模式,不等转换完成, + uploadDeviceState(true, value); + } else { + uploadDeviceState(true);//设置完远程控制后 上传最新当前状态 + } + } else { + UserLog.operateError("远程操作失败:" + getCommandNameById(controlCommand, value)); + } + //更新手动模式下设备UI + RxBusUtils.get().post(RxTag.UPDATE_MANUAL, 0); + })); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + private static String getCommandNameById(int controlCommand, int value) { + switch (controlCommand) { + case 0: + return "开始升温"; + case 1: + return "停止升温"; + case 3: + return "停止降温"; + case 2: + return "开始降温"; + case 4: + return "开始加湿"; + case 5: + return "停止加湿"; + case 6: + return "开始换气"; + case 7: + return "停止换气"; + case 8: + //切换模式 需要指定具体值 + int newMode = OldSet.oldModeToNewMode(value); + return "切换模式:" + MyUtil.getModeName(newMode); + case 9: + return "开始匀风"; + case 10: + return "停止匀风"; + case 11: + return "开启灯光"; + case 12: + return "关闭灯光"; + } + return null; + } + + /** + * 区域环境控制指令-发布 + */ + public static String areaenvcontrolPost() { + return "areaenvcontrol/" + HardwareSetting.getHostId() + "/post"; + } + + /** + * 区域环境控制指令-发布 + */ + public static void uploadControl(JSONObject jsons) { + MyLog.remote("发送区域环境控制指令操作成功反馈消息"); + try { + JSONObject json = new JSONObject(); + json.put("code", jsons.getString("code")); + json.put("uniquecode", jsons.getString("uniquecode")); + json.put("reason", 0); + json.put("controlcommand", jsons.getString("controlcommand")); + json.put("value", jsons.getString("value")); + json.put("datetime", MyUtil.getDateTime()); + json.put("gear", RoomController.floor.getGear()); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("areaenvcontrolcommandresult", json); + MQTTService.publish(areaenvcontrolPost(), jsonObject.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + //————————————————————————————————————————————————————————————————————————————————设备运行日志上传———————————————————————————————————————————————————————————————— + + /** + * 设备运行日志-上传 + */ + public static String DeviceRunLogsPost() { + return "DeviceRunLogs/" + HardwareSetting.getHostId() + "/release"; + } + + /** + * 上传日志到服务器 + */ + public static void uploadLog(int tag, int priority, String message) { + JSONObject json = new JSONObject(); + try { + json.put("areacode", RoomSetting.getAreaCode()); + json.put("tage", tag); + json.put("msglevel", priority); + json.put("msg", message); + json.put("datetime", MyUtil.getDateTime()); + MQTTService.publish(DeviceRunLogsPost(), json.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + /** + * 设备运行日志-订阅 + */ + public static String DeviceRunLogsGet() { + return "DeviceRunLogs/" + HardwareSetting.getHostId() + "/excute"; + } + + //————————————————————————————————————————————————————————————————————————————————区域环境控制状态上报———————————————————————————————————————————————————————————————— + + /** + * 区域环境控制状态上报 + */ + public static String areaenvcontrolstatusTopic() { + return "areaenvcontrolstatus/" + HardwareSetting.getHostId() + "/post"; + } + + private static String runningStateTemp = ""; + + /** + * + * @param needForceUpload 是否需要无视重复消息 直接上传 + * @param targetMode 目标模式 如果不为-1,则直接使用该模式 如果为-1,则使用当前模式 目的是为了能快速返回服务器切换后的状态 + */ + public static void uploadDeviceState(boolean needForceUpload, int targetMode) { + // 上传最新的温湿度、换气、当前模式等状态 + uploadRunningState(needForceUpload, targetMode); + // 上传最新的目标温湿度、预警阈值等信息 + areaDataUpload(0, needForceUpload); + } + + public static void uploadDeviceState(boolean needForceUpload) { + uploadDeviceState(needForceUpload, -1); + } + + /** + * 区域-环境控制状态-上报 + */ + public static void uploadRunningState(Boolean needForceUpload, int targetMode) { + JSONObject jsonObject = new JSONObject(); + JSONArray areadata = new JSONArray(); + try { + JSONObject json = new JSONObject(); + json.put("code", RoomSetting.getAreaCode()); + //温度控制状态: 0无 1升温中 2降温中 + int tempstatus; + if (RoomController.floor.isPowerSupply() + || (MyInfraredUtils.getInstances().isPowerSupply() && MyInfraredUtils.getInstances().getMode() == AirConditionInfrared.HEAT)) { + tempstatus = 1; + } else if (MyInfraredUtils.getInstances().isPowerSupply() && MyInfraredUtils.getInstances().getMode() == AirConditionInfrared.COLD) { + tempstatus = 2; + } else { + tempstatus = 0; + } + json.put("tempstatus", tempstatus); + //地暖控制状态:0关闭,1-5档 + json.put("gear", RoomController.floor.getGear()); + //湿度控制状态:0关闭 3加湿 + json.put("humistatus", RoomController.humidifier.isPowerSupply() ? 3 : 0); + //换气控制状态:0关闭 4换气 5匀风 + int ventstatus; + if (RoomController.fan.isPowerSupply()) { + ventstatus = 4; + } else if (RoomController.evenFan.isPowerSupply()) { + ventstatus = 5; + } else { + ventstatus = 0; + } + json.put("ventstatus", ventstatus); + //全局控制模式:1智能,2自动,3手动 + int curMode = RoomSetting.getMode(); + json.put("YXMS", targetMode == -1 ? OldSet.newModeToOldMode(curMode) : targetMode); + areadata.put(json); + jsonObject.put("areaenvcontrolstatus", areadata); + if (needForceUpload || !runningStateTemp.equals(jsonObject.toString())) { + // 需要强制发送设备状态信息(不理会日志重复) 或者 设备状态信息有变化(每十秒自动发送时) + MQTTService.publish(areaenvcontrolstatusTopic(), jsonObject.toString()); + runningStateTemp = jsonObject.toString(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + //————————————————————————————————————————————————————————————————————————————————区域能耗上报———————————————————————————————————————————————————————————————— + + /** + * 区域能耗上报 + */ + public static String areaenergydataTopic() { + return "areaenergydata/" + HardwareSetting.getHostId() + "/post"; + } + + + /** + * 区域-能耗-上报 + */ + public static void areaEnergyData() { + double value = RoomSensor.consumptionSensor.getEnergyConsumption(); + if (value != 0) { + //获取当前时间戳 + JSONObject jsonObject = new JSONObject(); + JSONArray areaData = new JSONArray(); + try { + if (RoomSensor.consumptionSensor.isAvailable()) { + JSONObject json = new JSONObject(); + json.put("code", RoomSetting.getAreaCode()); + json.put("date", MyUtil.getDateDay()); + json.put("value", RoomSensor.consumptionSensor.getEnergyConsumption()); + areaData.put(json); + jsonObject.put("areadata", areaData); + if (!energyDataTemp.equals(jsonObject.toString())) { + MQTTService.publish(areaenergydataTopic(), jsonObject.toString()); + energyDataTemp = jsonObject.toString(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + //————————————————————————————————————————————————————————————————————————————————区域数据上报———————————————————————————————————————————————————————————————— + + /** + * 区域数据上报 + */ + public static String areadataTopic() { + return "areadata/" + HardwareSetting.getHostId() + "/post"; + } + + + /** + * 区域-环境数据-上报 + */ + public static void uploadSensorData() { + try { + JSONArray areaData = new JSONArray(); + JSONObject jsonObject = new JSONObject(); + JSONArray sensordata = new JSONArray(); + JSONObject json = new JSONObject(); + json.put("code", RoomSetting.getAreaCode()); + JSONObject item = new JSONObject(); + item.put("type", 1); + item.put("value", RoomSensor.getAverageIndoorTemperature()); + sensordata.put(item); + item = new JSONObject(); + item.put("type", 2); + item.put("value", RoomSensor.getAverageIndoorHumidity()); + sensordata.put(item); + //电压、电流、功率 + if (RoomSensor.consumptionSensor.isAvailable()) { + item = new JSONObject(); + item.put("type", 9); + item.put("value", RoomSensor.consumptionSensor.getVoltage()); + sensordata.put(item); + item = new JSONObject(); + item.put("type", 10); + item.put("value", RoomSensor.consumptionSensor.getCurrent()); + sensordata.put(item); + item = new JSONObject(); + item.put("type", 11); + item.put("value", RoomSensor.consumptionSensor.getPower()); + sensordata.put(item); + } + //地面 + if (!RoomSensor.dynamicFloorTSensorList.isEmpty()) { + item = new JSONObject(); + item.put("type", 12); + item.put("value", RoomSensor.getFloorTemperature()); + sensordata.put(item); + } + //室外 + if (RoomSensor.outdoorTHSensor.isAvailable()) { + item = new JSONObject(); + item.put("type", 13); + item.put("value", RoomSensor.outdoorTHSensor.temperature); + sensordata.put(item); + item = new JSONObject(); + item.put("type", 14); + item.put("value", RoomSensor.outdoorTHSensor.humidity); + sensordata.put(item); + } + //二氧化碳 + if (!RoomSensor.dynamicCO2SensorList.isEmpty()) { + double co2 = 0; + double temperature = 0; + double humidity = 0; + for (GasSensor sensor : RoomSensor.dynamicCO2SensorList) { + co2 += sensor.getValue(); + temperature += sensor.getTemperature(); + humidity += sensor.getHumidity(); + } + int size = RoomSensor.dynamicCO2SensorList.size(); + co2 = co2 / size; + temperature = temperature / size; + humidity = humidity / size; + item = new JSONObject(); + item.put("type", 5); + item.put("value", co2); + sensordata.put(item); + item = new JSONObject(); + item.put("type", 15); + item.put("value", temperature); + sensordata.put(item); + item = new JSONObject(); + item.put("type", 16); + item.put("value", humidity); + sensordata.put(item); + } + json.put("sensordata", sensordata); + areaData.put(json); + jsonObject.put("areadata", areaData); + if (!sensorDataTemp.equals(jsonObject.toString())) { + MQTTService.publish(areadataTopic(), jsonObject.toString()); + sensorDataTemp = jsonObject.toString(); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + //————————————————————————————————————————————————————————————————————————————————区域环境运行模式———————————————————————————————————————————————————————————————— + + /** + * 区域环境运行模式-接收 + */ + public static String arearunmodelGet() { + return "arearunmodel/" + HardwareSetting.getHostId() + "/set"; + } + + /** + * 区域环境运行模式接收(远程设定运行温度湿度等值) + */ + public static void handleAreaRunModelMessage(String msg) throws JSONException { + MyLog.network("MQTT接收到'区域环境运行模式'消息"); + JSONObject json = new JSONObject(msg); + JSONArray areaRunModel = json.getJSONArray("arearunmodel"); + String ss = areaRunModel.getString(0); + JSONObject jsons = new JSONObject(ss); + //唯一区域码 之前通过唯一区域码获得索引,现在就一个房间,所以不需要 + String code = jsons.getString("code"); + if (RoomSetting.getAreaCode().equals(code)) { + //预警值 + MMKVUtil.put(AIModelSet.AI_SCHEME_NAME, jsons.getString("autocontrolprogram")); + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, jsons.getDouble("tempmaxwarning")); + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, jsons.getDouble("tempminwarning")); + MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, jsons.getDouble("humimaxwarning")); + MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, jsons.getDouble("humiminwarning")); + //温度 + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, jsons.getInt("autotemp") != 0); + double targetTemperature = jsons.getDouble("autotempvalue"); + MMKVUtil.put(AutoModelSet.TARGET_TEMPERATURE, targetTemperature); + //自动根据目标值匹配最大最小值 +// if (targetTemperature != AutoModelSet.getTargetTemperature()) { +// //新需求:如果远程设置温度有变化,则同时改变最高温度和最低温度 +// double delta = targetTemperature - AutoModelSet.getTargetTemperature() ; +// double[] minTargetMax = MyUtil.getTHMinMax(AutoModelSet.getTargetTemperature(), delta, AutoModelSet.getMaxTemperature(), AutoModelSet.getMinTemperature(), 0); +// MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, minTargetMax[2]); +// MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, minTargetMax[0]); +// MMKVUtil.put(AutoModelSet.TARGET_TEMPERATURE, minTargetMax[1]); +// } + //湿度 + MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, jsons.getInt("autohumi") != 0); + double targetHumidity = jsons.getDouble("autohumivalue"); + MMKVUtil.put(AutoModelSet.TARGET_HUMIDITY, targetHumidity); + //自动根据目标值匹配最大最小值 +// if (targetHumidity != AutoModelSet.getTargetHumidity()) { +// //新需求:如果远程设置湿度有变化,则同时改变最高湿度和最低湿度 +// double delta = targetHumidity - AutoModelSet.getTargetHumidity(); +// double[] minTargetMax = MyUtil.getTHMinMax(AutoModelSet.getTargetHumidity(), delta, AutoModelSet.getMaxHumidity(), AutoModelSet.getMinHumidity(), 1); +// MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, minTargetMax[0]); +// MMKVUtil.put(AutoModelSet.TARGET_HUMIDITY, minTargetMax[1]); +// MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, minTargetMax[2]); +// } + //换气 + MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, jsons.getInt("autovent") != 0); + MMKVUtil.put(AutoModelSet.CYCLE_START, jsons.getInt("autoventopenvalue")); + MMKVUtil.put(AutoModelSet.CYCLE_STOP, jsons.getInt("autoventclosevalue")); + + //2025年5月7日:取消以下逻辑 +// //切换为自动模式 +// RxBusUtils.get().post(RxTag.UPDATE_MAIN, 3); + UserLog.operate("远程:已更新自动模式参数"); + if (RoomSetting.getMode() == 1) { + // 如果是自动模式,则刷新自动模式参数 + RxBusUtils.get().post(RxTag.UPDATE_AUTO, 3); + } + + uploadDeviceState(true);//设置完成自动模式参数后 上传最新当前状态 + } + } + + /** + * 区域环境运行模式-上报 + */ + public static String arearunmodel() { + return "arearunmodel/" + HardwareSetting.getHostId() + "/post"; + } + + /** + * 区域-运行模式-上报 + * + * @param reason 触发类型,1代表本地触发,0代表远程触发 + */ + public static void areaDataUpload(int reason, boolean needForceUpload) { +// MyLog.app("上报设备运行状态"); + JSONObject jsonObject = new JSONObject(); + JSONArray areadata = new JSONArray(); + try { + JSONObject json = new JSONObject(); + json.put("code", RoomSetting.getAreaCode()); + json.put("reason", reason); +// json.put("autocontrolprogram", MMKVUtil.get(AIModelSet.AI_SCHEME_NAME)); + //温度 + json.put("autotemp", AutoModelSet.isAutoTemperature() ? 1 : 0); + json.put("autotempvalue", AutoModelSet.getTargetTemperature()); + //湿度 + json.put("autohumi", AutoModelSet.isAutoHumidity() ? 1 : 0); + json.put("autohumivalue", AutoModelSet.getTargetHumidity()); + //换气扇 + json.put("autovent", AutoModelSet.isAutoVentilator() ? 1 : 0); + json.put("autoventopenvalue", AutoModelSet.getCycleStart()); + json.put("autoventclosevalue", AutoModelSet.getCycleStop()); + //预警 + json.put("tempminwarning", AutoModelSet.getMinTemperature()); + json.put("tempmaxwarning", AutoModelSet.getMaxTemperature()); + json.put("humiminwarning", AutoModelSet.getMinHumidity()); + json.put("humimaxwarning", AutoModelSet.getMaxHumidity()); + areadata.put(json); + jsonObject.put("areas", areadata); + if (needForceUpload || !areaDataTemp.equals(jsonObject.toString())) { + MQTTService.publish(arearunmodel(), jsonObject.toString()); + areaDataTemp = jsonObject.toString(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + //————————————————————————————————————————————————————————————————————————————————上传自定义数据(传感器数据)——————————————————————————————————————————————————————— + + /** + * 上传自定义数据(传感器数据)-发布 + */ + public static String areaCustomDataTopic() { + return "area_custom_cache/" + HardwareSetting.getHostId() + "/release"; + } + + /** + * 上传自定义数据(传感器数据)-发布 + */ + public static void uploadAreaCustomData() { + JSONObject json = new JSONObject(); + try { + json.put("code", RoomSetting.getAreaCode()); + List infoList = new ArrayList<>(); + // 所有温湿度传感器的数值 + for (Sensor sensor : RoomSensor.getAllAvailableSensors()) { + infoList.add(new SensorInfo(sensor.getName(), sensor.toString())); + } + json.put("data", GsonUtils.toJson(infoList)); + if (!sensorInfoDataTemp.equals(json.toString())) { + MQTTService.publish(areaCustomDataTopic(), json.toString()); + sensorInfoDataTemp = json.toString(); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + + //————————————————————————————————————————————————————————————————————————————————工具方法———————————————————————————————————————————————————————————————— + public static void refreshCatch() { + energyDataTemp = ""; + areaDataTemp = ""; + sensorDataTemp = ""; + sensorInfoDataTemp = ""; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/Url.java b/app/src/main/java/com/example/iot_controlhost/utils/network/Url.java new file mode 100644 index 0000000..5a904db --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/Url.java @@ -0,0 +1,164 @@ +package com.example.iot_controlhost.utils.network; + +public class Url { + /** + * Environment 当前环境 true 生产环境 false 测试环境 + */ + private static final boolean userProduct = true; + + private static String baseUrls; + + public static void initUrl() { + if (userProduct) { + // 生产环境 + baseUrls = "https://iot.api.bbitcn.net/"; + } else { + // 测试环境 + baseUrls = "http://wechat.lkstc.com/"; + // 局域网环境 + baseUrls = "http://10.0.4.43:7474/";// 何 + } + getWorkList = baseUrls + "api/JobContent/GetJobContentConfigs"; + getWorkDetail = baseUrls + "api/JobContentConfig_EnvControl/GetJobContentConfigEnvControls"; + getStageList = baseUrls + "api/FeedingStageConfig/GetFeedingStageConfigs"; + getStageDetail = baseUrls + "api/FeedingStageConfig_EnvControl/GetFeedingStageConfigEnvControls"; + qrCodeCreate = baseUrls + "api/WeChat/QrcodeCreateArea"; + startWork = baseUrls + "api/JobContent/JobLoginStart"; + stopWork = baseUrls + "api/JobContent/JobLoginFinish"; + thWarning = baseUrls + "api/WeChat/TH_Warning"; + checkAppNewVersion = baseUrls + "api/Upgrade/GetNewVersion"; + getSilkwormTHTemplate = baseUrls + "api/IntelligentControlProgram/GetIntelligentControlProgramItems"; + getBatchPlan = baseUrls + "api/FeedingBatchPlan/GetFeedingBatchPlans"; + startCultivate = baseUrls + "api/AreaFeedingRecord/AddStartFeedingRecord"; + stopCultivate = baseUrls + "api/AreaFeedingRecord/AddEndFeedingRecord"; + getAirConditionerModelList = baseUrls + "api/AirCondition/GetAirConditionList"; + getAirConditionerCommandList = baseUrls + "api/AirCondition/GetAirConditionInstruct"; + uploadAirConditioner = baseUrls + "api/AirCondition/AddAirConditionInstruct"; + getConfig = baseUrls + "api/Host/GetHostDeviceConfig"; + uploadConfig = baseUrls + "api/Host/SetHostDeviceConfig"; + checkDevOpsPassword = baseUrls + "api/AreaSecret/ValidControlOpsSecret"; + notice = baseUrls + "api/AreaNotice/Notice"; + getInfo = baseUrls + "api/Area/GetAreaDetailedInfo"; + getToken = baseUrls + "api/_Account/LoginJwt"; + faceCheck = baseUrls + "api/BaiduAI/FaceSearch"; + sos = baseUrls + "api/UpgradeFrp/GetNewVersion"; + renew = baseUrls + " "; + serverInfo = " "; + weather = baseUrls + "api/Weather/GetWeatherInfoForecasts"; + uploadHostInfo = baseUrls + "api/Host/UpdateHostInfo"; + uploadLog = baseUrls + ""; + } + + /** + * 获取作业列表 + */ + public static String getWorkList; + public static String getWorkDetail; + /** + * 获取作业列表 新 + */ + public static String getStageList; + public static String getStageDetail; + + /** + * 获取主机带参数的二维码 + */ + public static String qrCodeCreate; + /** + * 开始作业登记 + */ + public static String startWork; + /** + * 结束作业登记 + */ + public static String stopWork; + /** + * 温湿度预警 + */ + public static String thWarning; + /** + * 报警 + */ + public static String notice; + /** + * 检查更新 + */ + public static String checkAppNewVersion; + /** + * 获取蚕种温湿度模板 + */ + public static String getSilkwormTHTemplate; + /** + * 获取批次计划 + */ + public static String getBatchPlan; + /** + * 开始共育 + */ + public static String startCultivate; + /** + * 结束共育 + */ + public static String stopCultivate; + /** + * 获取空调型号列表 + */ + public static String getAirConditionerModelList; + /** + * 获取指定型号的空调指令 + */ + public static String getAirConditionerCommandList; + /** + * 上传新空调 + * 型号及指令列表 + */ + public static String uploadAirConditioner; + /** + * 获取配置信息 + */ + public static String getConfig; + /** + * 上传配置信息 + */ + public static String uploadConfig; + /** + * 验证运维密码 + */ + public static String checkDevOpsPassword; + /** + * 获取房间详细信息 + */ + public static String getInfo; + /** + * 获取Token + */ + public static String getToken; + /** + * 人脸检测 + */ + public static String faceCheck; + /** + * SOS + */ + public static String sos; + /** + * 服务-续费 todo + */ + public static String renew; + /** + * 服务-详情 todo + */ + public static String serverInfo; + /** + * 天气 + */ + public static String weather; + /** + * 上传主机信息 + */ + public static String uploadHostInfo; + /** + * 上传日志 todo + */ + public static String uploadLog; +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/XHttpManager.java b/app/src/main/java/com/example/iot_controlhost/utils/network/XHttpManager.java new file mode 100644 index 0000000..4d0672b --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/XHttpManager.java @@ -0,0 +1,56 @@ +package com.example.iot_controlhost.utils.network; + +import android.widget.SpinnerAdapter; + +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.RoomSetting; + +import org.xutils.http.RequestParams; + +/** + * @Author:BinJianXin + * @Date:2022/5/30/9:34 + * @Declaration: + */ +public class XHttpManager { + private static XHttpManager xHttpManager; + + public static XHttpManager getInstance() { + if (xHttpManager == null) { + synchronized (XHttpManager.class) { + if (xHttpManager == null) { + xHttpManager = new XHttpManager(); + } + } + } + return xHttpManager; + } + + /** + * 获得网络请求头 + * + * @param url 地址 + */ + public RequestParams getRequestParams(String url) { + return getRequestParams(url, false); + } + + /** + * 获得网络请求头 + * + * @param url 地址 + * @param needToken 是否需要token + */ + public RequestParams getRequestParams(String url, boolean needToken) { + RequestParams params = new RequestParams(url); + params.setReadTimeout(10000); + params.setConnectTimeout(10000); + params.addHeader("Content-Type", "application/json,charset=UTF-8"); + params.setUseCookie(false); + params.setAsJsonContent(true); + if (needToken) { + params.addHeader("Authorization", MMKVUtil.get(RoomSetting.TOKEN)); + } + return params; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/network/XHttpShorthand.java b/app/src/main/java/com/example/iot_controlhost/utils/network/XHttpShorthand.java new file mode 100644 index 0000000..560e60e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/network/XHttpShorthand.java @@ -0,0 +1,114 @@ +package com.example.iot_controlhost.utils.network; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.blankj.utilcode.util.GsonUtils; +import com.example.iot_controlhost.model.net.CommonResponse; +import com.example.iot_controlhost.utils.log.MyLog; + +import org.json.JSONException; +import org.xutils.common.Callback; + +import java.lang.reflect.ParameterizedType; + +/** + * @Author:BinJianXin + * @Date:2022/5/30/9:49 + * @Declaration:用于减少逻辑界面xhttp需要实现的方法数 + */ +public abstract class XHttpShorthand implements Callback.CommonCallback { + + public abstract void success(T result) throws JSONException; + + private Class clazz; + + public XHttpShorthand() { + try { + ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); + clazz = (Class) genericSuperclass.getActualTypeArguments()[0]; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public abstract void error(Throwable e); + + @Override + public void onSuccess(String result) { + MyLog.network("网络请求成功:" + result); + Message message = new Message(); + try { + CommonResponse temp = GsonUtils.fromJson(result, CommonResponse.class); + MyLog.network("已转为通用请求内容"); + if (temp != null) { +// MyLog.network("Code == 200"); + message.what = 0; + message.obj = GsonUtils.fromJson(result, clazz); + } else { +// MyLog.network("Code != 200"); + message.what = 2; + message.obj = temp; + } + } catch (Exception e) { + message.what = 1; + message.obj = e; + } + handler.sendMessage(message); + } + + public void onSuccessError(CommonResponse data) { + + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + MyLog.networkError("网络请求失败:错误消息:" + ex.getMessage() + + "\n本地化消息:" + ex.getLocalizedMessage() + + "\n原因:" + ex.getCause()); + Message message = new Message(); + message.what = 1; + message.obj = ex; + handler.sendMessage(message); + } + + @Override + public void onCancelled(CancelledException cex) { + MyLog.network("网络请求取消" + cex.getMessage()); + } + + @Override + public void onFinished() { + Message message = new Message(); + message.what = 3; + handler.sendMessage(message); + } + + public void onFinish() { + MyLog.network("网络请求完成"); + } + + /** + * 转回到主线程 + */ + public Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(@androidx.annotation.NonNull Message msg) { + super.handleMessage(msg); + try { + if (msg.what == 0) { + success((T) msg.obj); + } else if (msg.what == 1) { + error((Throwable) msg.obj); + } else if (msg.what == 2) { + onSuccessError((CommonResponse) msg.obj); + }else if(msg.what == 3){ + onFinish(); + } + } catch (Exception e) { + MyLog.networkError("网络请求错误:" + e.getMessage()); + } + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/old/DBOpenHelper.java b/app/src/main/java/com/example/iot_controlhost/utils/old/DBOpenHelper.java new file mode 100644 index 0000000..e38d4b0 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/old/DBOpenHelper.java @@ -0,0 +1,316 @@ +package com.example.iot_controlhost.utils.old; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import androidx.annotation.Nullable; + +import com.example.iot_controlhost.utils.MyUtil; + +public class DBOpenHelper extends SQLiteOpenHelper { + public DBOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { + super(context, name, factory, version); + } + + String[] names = {"HeatUpType", "wsd1Type", "wsd2Type", "nhType", "CO2Type", "DMWDType", "HWYKType", "JDQType", "GZType"}; + + @Override + public void onCreate(SQLiteDatabase db) { + //创建数据库sql语句并执行 + //保存日志记录的表 + String sql = "create table Logs(id integer primary key autoincrement,TAG varchar(20),level varchar(20),datetime TEXT," + + "msg varchar(1024),areacode varchar(20))"; + db.execSQL(sql); + //保存蚕房设置参数的表 + sql = "create table areas(id integer primary key autoincrement,RoomNum integer," + + "RoomId varchar(20),RoomName varchar(20), TargetHumidity varchar(20),TargetTemperature varchar(20)," + + "ventilator_Open_interval varchar(20),ventilator_Close_interval varchar(20),datetime TEXT," + + "flag int,Heating_Type int,Temperature_mode int,Humidity_mode int,ventilator_mode int,YXMS int," + + "Port_1 integer,Port_2 integer,Port_3 integer,Port_4 integer,Port_5 integer,Port_6 integer," + + "Port_7 integer,Port_8 integer,Port_9 integer,Port_10 integer,HeatUpType integer,wsd1Type integer," + + "wsd2Type integer,nhType integer,CO2Type integer,DMWDType integer,HWYKType integer,JDQType integer," + + "GZType integer,Temp_Warning varchar(20),Hum_Warning integer,CO2MAX integer,ControlType integer,QrcodeCreateArea varchar(100)," + + "DLJCType integer,AQCJType integer,SWWSDType integer,KTWDType integer,HeatUpType_Arr integer,AreaType integer,HeatingMaxGear integer,HeatUpModel integer,DeviceModel integer)"; + db.execSQL(sql); + //保存温湿度等环境历史数据的表 + sql = "create table historicaldata(id integer primary key autoincrement,Temperature_1 varchar(10)," + + "Temperature_2 varchar(10),Humidity_1 varchar(10),Humidity_2 varchar(10)," + + "AverageTemperature varchar(10),AverageHumidity varchar(10),Voltage varchar(10)," + + "Ampere varchar(10),RealTimePower varchar(10),TotalEnergy varchar(10),Heating_Gear integer," + + "Humidifier_Type integer, ventilator_Type integer,Air_Type integer,ventilator_Open_Time TEXT," + + "ventilator_Close_Time TEXT,Light TEXT,YXMS integer,datetime TEXT,areacode varchar(20))"; + db.execSQL(sql); + + //保存能耗历史数据的表 + sql = "create table TotalEnergyHistoricaldata(id integer primary key autoincrement,TotalEnergy varchar(10) ,datetime TEXT,areacode varchar(20))"; + db.execSQL(sql); + + //保存设备相关配置数据的表 + sql = "create table DevicesSetting(id integer primary key autoincrement,SensorCOMName integer,ControlCOMName integer)"; + db.execSQL(sql); + + //保存共育记录的表 + sql = "create table GongYuRecord(id integer primary key autoincrement,CanJiID varchar(50),CanJiName varchar(50),PinZhongName varchar(20),NumberCount varchar(10),StartTime varchar(20)," + + "EndtTime varchar(20),areacode varchar(20),CreateTime varchar(20),flag integer)"; + db.execSQL(sql); + + //保存智能模式下各蚕种所需要的温湿度标准的表 + sql = "create table Template(id integer primary key autoincrement,PinZhongName varchar(20),Day integer,TargetHumidity varchar(10),TargetTemperature varchar(10)," + + "ventilator_Open_interval varchar(10),ventilator_Close_interval varchar(10),areacode varchar(20),CreateTime varchar(20),flag integer)"; + db.execSQL(sql); + + //保存定时任务的表 + sql = "create table ScheduledTasks(id integer primary key autoincrement,HOUR integer,MINUTE integer,SECOND,DeviceType integer," + + "Execute integer,TaskNum integer,areacode varchar(20),CreateTime varchar(20),flag integer)"; + db.execSQL(sql); + + //插入系统初始化设置 + ContentValues cV = new ContentValues(); + cV.put("SensorCOMName", 0); //默认传感器采集端口 + cV.put("ControlCOMName", 1); //默认控制指令端口 + db.insert("DevicesSetting", null, cV); + + //插入房间的初始化数据 + for (int num = 1; num < 3; num++) { + ContentValues cValue = new ContentValues(); + cValue.put("RoomNum", num); + cValue.put("RoomId", ""); + cValue.put("RoomName", num + "号"); + cValue.put("TargetHumidity", 80); + cValue.put("TargetTemperature", 26); + cValue.put("Hum_Warning", 5); + cValue.put("Temp_Warning", 0.5); + cValue.put("CO2MAX", 1000); + cValue.put("ventilator_Open_interval", 1); + cValue.put("ventilator_Close_interval", 30); + if (num == 1) cValue.put("flag", 1); + else cValue.put("flag", 0); + cValue.put("Temperature_mode", 1); + cValue.put("Humidity_mode", 1); + cValue.put("ventilator_mode", 1); + cValue.put("YXMS", 3); + cValue.put("datetime", MyUtil.getDateTime()); + cValue.put("ControlType", 0); + cValue.put("wsd1Type", 1); + cValue.put("wsd2Type", 2); + cValue.put("DLJCType", 0); + cValue.put("AQCJType", 0); + cValue.put("SWWSDType", 0); + cValue.put("KTWDType", 0); + cValue.put("HeatUpType_Arr", 0); + cValue.put("HeatingMaxGear", 5); + cValue.put("HeatUpModel", 0); + cValue.put("DeviceModel", 0); + for (int i = 1; i < 11; i++) { + String name = "Port_" + i; + if (i == 1) + cValue.put(name, 4); + else if (i == 2) + cValue.put(name, 3); + else if (i == 3) + cValue.put(name, 1); + else if (i == 4) + cValue.put(name, 1); + else if (i == 5) { + cValue.put(name, 2); + } else { + cValue.put(name, 0); + } + } + for (int i = 0; i < names.length; i++) { + if (i == 0 | i == 1 | i == 2) + cValue.put(names[i], 1); + else cValue.put(names[i], 0); + } + } + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.d("TAG", "onUpgrade: 数据库更新了" + oldVersion + " -> " + newVersion); + //添加控制模式字段 + if (!checkColumnExists(db, "areas", "ControlType")) { + String sql = "alter table areas add ControlType int"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("ControlType", 0); + db.update("areas", cv, "", null); + } + + //调压模块地址 + if (!checkColumnExists(db, "areas", "HeatUpType_Arr")) { + String sql = "alter table areas add HeatUpType_Arr integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("HeatUpType_Arr", 0); + db.update("areas", cv, "", null); + } + + + //地暖控制模块型号 + if (!checkColumnExists(db, "areas", "HeatUpModel")) { + String sql = "alter table areas add HeatUpModel integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("HeatUpModel", 0); + db.update("areas", cv, "", null); + } + + //继电器模块型号 + if (!checkColumnExists(db, "areas", "DeviceModel")) { + String sql = "alter table areas add DeviceModel integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("DeviceModel", 0); + db.update("areas", cv, "", null); + } + + //QrcodeCreateArea + //添加区域二维码连接地址字段 + if (!checkColumnExists(db, "areas", "QrcodeCreateArea")) { + String sql = "alter table areas add QrcodeCreateArea varchar(100)"; + db.execSQL(sql); + } + + //地暖允许最大挡位字段 + if (!checkColumnExists(db, "areas", "HeatingMaxGear")) { + String sql = "alter table areas add HeatingMaxGear integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("HeatingMaxGear", 5); + db.update("areas", cv, "", null); + } + + if (!checkColumnExists(db, "TotalEnergyHistoricaldata", "TotalEnergy")) { + //保存温湿度等环境历史数据的表 + String sql = "create table TotalEnergyHistoricaldata(id integer primary key autoincrement,TotalEnergy varchar(20) ,datetime TEXT,areacode varchar(20))"; + db.execSQL(sql); + } + + if (!checkColumnExists(db, "ScheduledTasks", "id")) { + //保存定时任务的表 + String sql = "create table ScheduledTasks(id integer primary key autoincrement,HOUR integer,MINUTE integer,SECOND,DeviceType integer," + + "Execute integer,TaskNum integer,areacode varchar(20),CreateTime varchar(20),flag integer)"; + db.execSQL(sql); + } + + //保存设备相关配置参数的表 + if (!checkColumnExists(db, "DevicesSetting", "SensorCOMName")) { + //保存设备相关配置数据的表 + String sql = "create table DevicesSetting(id integer primary key autoincrement,SensorCOMName integer,ControlCOMName integer)"; + db.execSQL(sql); + //插入系统初始化设置 + ContentValues cV = new ContentValues(); + cV.put("SensorCOMName", 0); //默认传感器采集端口 + cV.put("ControlCOMName", 1); //默认控制指令端口 + db.insert("DevicesSetting", null, cV); + } + + //电流检测字段 + if (!checkColumnExists(db, "areas", "DLJCType")) { + String sql = "alter table areas add DLJCType integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("DLJCType", 0); + db.update("areas", cv, "", null); + } + + //氨气采集字段 + if (!checkColumnExists(db, "areas", "AQCJType")) { + String sql = "alter table areas add AQCJType integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("AQCJType", 0); + db.update("areas", cv, "", null); + } + + //室外温湿度采集字段 + if (!checkColumnExists(db, "areas", "SWWSDType")) { + String sql = "alter table areas add SWWSDType integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("SWWSDType", 0); + db.update("areas", cv, "", null); + } + + //空调温度采集字段 + if (!checkColumnExists(db, "areas", "KTWDType")) { + String sql = "alter table areas add KTWDType integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("KTWDType", 0); + db.update("areas", cv, "", null); + } + + //共育记录表 + if (!checkColumnExists(db, "GongYuRecord", "CanJiName")) { + //保存共育记录的表 + String sql = "create table GongYuRecord(id integer primary key autoincrement,CanJiID varchar(50),CanJiName varchar(50),PinZhongName varchar(20),NumberCount varchar(10),StartTime varchar(20)," + + "EndtTime varchar(20),areacode varchar(20),CreateTime varchar(20),flag integer)"; + db.execSQL(sql); + } + + //共育记录表 + if (!checkColumnExists(db, "GongYuRecord", "CanJiID")) { + //蚕季id + String sql = "alter table GongYuRecord add CanJiID varchar(50)"; + db.execSQL(sql); + } + + //保存智能模式下各蚕种所需要的温湿度标准的表 + if (!checkColumnExists(db, "Template", "PinZhongName")) { + //保存智能模式下各蚕种所需要的温湿度标准的表 + String sql = "create table Template(id integer primary key autoincrement,PinZhongName varchar(20),Day integer,TargetHumidity varchar(10),TargetTemperature varchar(10)," + + "ventilator_Open_interval varchar(10),ventilator_Close_interval varchar(10),areacode varchar(20),CreateTime varchar(20),flag integer)"; + db.execSQL(sql); + } + + //蚕房类别字段 + if (!checkColumnExists(db, "areas", "AreaType")) { + String sql = "alter table areas add AreaType integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("AreaType", 0); + db.update("areas", cv, "", null); + } + + //二氧化碳最大值字段 + if (!checkColumnExists(db, "areas", "CO2MAX")) { + String sql = "alter table areas add CO2MAX integer"; + db.execSQL(sql); + ContentValues cv = new ContentValues(); + cv.put("CO2MAX", 1000); + db.update("areas", cv, "", null); + } + + } + + /** + * 检查表里的某个字段是否存在 + * + * @param db + * @param tableName 表名 + * @param columnName 字段名 + * @return + */ + private static boolean checkColumnExists(SQLiteDatabase db, String tableName, String columnName) { + boolean result = false; + Cursor cursor = null; + try { + cursor = db.rawQuery("select * from " + tableName, null); + result = cursor != null && cursor.getColumnIndex(columnName) != -1; + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (null != cursor && !cursor.isClosed()) { + cursor.close(); + } + } + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/old/DataBaseUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/old/DataBaseUtil.java new file mode 100644 index 0000000..a0a165a --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/old/DataBaseUtil.java @@ -0,0 +1,45 @@ +package com.example.iot_controlhost.utils.old; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +public class DataBaseUtil { + DBOpenHelper dbsqLiteOpenHelper; + SQLiteDatabase db; + public DataBaseUtil(Context context){ + dbsqLiteOpenHelper = new DBOpenHelper(context,"database.db",null,22); + db = dbsqLiteOpenHelper.getWritableDatabase(); + } + public SQLiteDatabase InitDataBase() + { + return db; + } + + /** + * 查询 + * @param table 表名 + * @param columns 指定要查询的列,如果为空,返回所有 + * @param selection 查询条件 ,可以用占位符? + * @param selectionArgs where对应的条件值 + * @param groupBy 指定分组方式 + * @param having 指定having条件 + * @param orderBy 指定having条件 + * @return + */ + public Cursor Query(String table, String[] columns, String selection, String[] + selectionArgs, String groupBy, String having, String orderBy){ + Cursor cursor = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy); + return cursor; + } + + public Cursor Select(String sql){ + Cursor cursor = db.rawQuery(sql,null); + return cursor; + } + + public void close(){ + db.close(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/iot_controlhost/utils/old/OldSet.java b/app/src/main/java/com/example/iot_controlhost/utils/old/OldSet.java new file mode 100644 index 0000000..cfe57bf --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/old/OldSet.java @@ -0,0 +1,257 @@ +package com.example.iot_controlhost.utils.old; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.Cursor; + +import com.example.iot_controlhost.model.Device; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.model.controller.Controller; +import com.example.iot_controlhost.model.controller.Floor; +import com.example.iot_controlhost.model.sensor.FloorTSensor; +import com.example.iot_controlhost.model.sensor.GasSensor; +import com.example.iot_controlhost.model.sensor.THSensor; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorCO2SensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorFloorTSensorUtil; +import com.example.iot_controlhost.utils.database.dynamicSensor.IndoorTHSensorUtil; +import com.example.iot_controlhost.utils.timer.UVTaskUtil; +import com.example.iot_controlhost.utils.global.AutoModelSet; +import com.example.iot_controlhost.utils.global.HardwareSetting; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.global.RoomSensor; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.global.SpinnerList; + +/** + * @Description 旧版本配置导入 + * @Author DuanKaiji + * @CreateTime 2023年11月21日 11:36:04 + */ +public class OldSet { + /** + * 第一次旧版本到新版本转换 + */ + public static final String FIRST_TIME = "FIRST_TIME"; + + /** + * 地暖地址 + */ + public static String[] ADDRESS = {"不启用", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89"}; + + /** + * 新老配置兼容 + */ + @SuppressLint("Range") + public static void importOldSet(Context context) { + DataBaseUtil dataBaseUtil = new DataBaseUtil(context); + Cursor cursor = dataBaseUtil.Select("select * from areas where flag = 1"); + String roomId = null; + if (cursor.moveToNext()) { + //有配置,则说明此机器注册过 + HardwareSetting.setRegistered(); + int type = cursor.getInt(cursor.getColumnIndex("AreaType")); + if (0 != type || 1 != type) { + //分辨 +// continue; + } + //基本信息 + roomId = cursor.getString(cursor.getColumnIndex("RoomId")); + MMKVUtil.put(RoomSetting.ROOM_NAME, cursor.getString(cursor.getColumnIndex("RoomName"))); + MMKVUtil.put(RoomSetting.MODE, oldModeToNewMode(cursor.getInt(cursor.getColumnIndex("YXMS")))); + //自动模式配置项 + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_HEAT, SpinnerList.TEMPERATURE_HEAT[cursor.getInt(cursor.getColumnIndex("HeatUpType"))]);//默认自动模式均改为“分档位控制” + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE_MODE, SpinnerList.TEMPERATURE_MODE[1]); + MMKVUtil.put(AutoModelSet.TARGET_HUMIDITY, cursor.getDouble(cursor.getColumnIndex("TargetHumidity"))); + MMKVUtil.put(AutoModelSet.TARGET_TEMPERATURE, cursor.getDouble(cursor.getColumnIndex("TargetTemperature"))); + MMKVUtil.put(AutoModelSet.CYCLE_START, cursor.getInt(cursor.getColumnIndex("ventilator_Open_interval"))); + MMKVUtil.put(AutoModelSet.CYCLE_STOP, cursor.getInt(cursor.getColumnIndex("ventilator_Close_interval"))); + MMKVUtil.put(AutoModelSet.AUTO_TEMPERATURE, cursor.getInt(cursor.getColumnIndex("Temperature_mode")) == 1); + MMKVUtil.put(AutoModelSet.AUTO_HUMIDITY, cursor.getInt(cursor.getColumnIndex("Humidity_mode")) == 1); + MMKVUtil.put(AutoModelSet.AUTO_VENTILATOR, cursor.getInt(cursor.getColumnIndex("ventilator_mode")) == 1); + MMKVUtil.put(AutoModelSet.MAX_TEMPERATURE, AutoModelSet.getTargetTemperature() + cursor.getDouble(cursor.getColumnIndex("Temp_Warning"))); + MMKVUtil.put(AutoModelSet.MIN_TEMPERATURE, AutoModelSet.getTargetTemperature() - cursor.getDouble(cursor.getColumnIndex("Temp_Warning"))); + MMKVUtil.put(AutoModelSet.MAX_HUMIDITY, AutoModelSet.getTargetHumidity() + cursor.getInt(cursor.getColumnIndex("Hum_Warning"))); + MMKVUtil.put(AutoModelSet.MIN_HUMIDITY, AutoModelSet.getTargetHumidity() - cursor.getInt(cursor.getColumnIndex("Hum_Warning"))); + MMKVUtil.put(AutoModelSet.MAX_DENSITY_CO2, cursor.getInt(cursor.getColumnIndex("CO2MAX"))); + //传感器 + int temp = cursor.getInt(cursor.getColumnIndex("wsd1Type")); + if (temp != 0) { + THSensor thSensor1 = new THSensor("室内温湿度传感器1"); + thSensor1.setAddress(ADDRESS[temp]); + thSensor1.setEnable(true); + IndoorTHSensorUtil.insert(thSensor1); + } + temp = cursor.getInt(cursor.getColumnIndex("wsd2Type")); + if (temp != 0) { + THSensor thSensor2 = new THSensor("室内温湿度传感器2"); + thSensor2.setAddress(ADDRESS[temp]); + thSensor2.setEnable(true); + IndoorTHSensorUtil.insert(thSensor2); + } + temp = cursor.getInt(cursor.getColumnIndex("nhType")); + if (temp != 0) { + RoomSensor.consumptionSensor.setEnable(true); + RoomSensor.consumptionSensor.setAddress(ADDRESS[temp]); + } + temp = cursor.getInt(cursor.getColumnIndex("CO2Type")); + if (temp != 0) { + GasSensor gasSensor = new GasSensor("室内CO2传感器1"); + gasSensor.setAddress(ADDRESS[temp]); + gasSensor.setEnable(true); + IndoorCO2SensorUtil.insert(gasSensor); + } + temp = cursor.getInt(cursor.getColumnIndex("DMWDType")); + if (temp != 0) { + FloorTSensor floorTSensor = new FloorTSensor("地面温度传感器"); + floorTSensor.setAddress(ADDRESS[temp]); + floorTSensor.setEnable(true); + IndoorFloorTSensorUtil.insert(floorTSensor); + } + temp = cursor.getInt(cursor.getColumnIndex("GZType")); + if (temp != 0) { + RoomSensor.lightSensor.setEnable(true); + RoomSensor.lightSensor.setAddress(ADDRESS[temp]); + } + temp = cursor.getInt(cursor.getColumnIndex("SWWSDType")); + if (temp != 0) { + RoomSensor.outdoorTHSensor.setEnable(true); + RoomSensor.outdoorTHSensor.setAddress(ADDRESS[temp]); + } + //空调 + temp = cursor.getInt(cursor.getColumnIndex("HWYKType")); + if (temp != 0) { + RoomController.airConditionInfrared.setEnable(true); + RoomController.airConditionInfrared.setAddress(ADDRESS[temp]); + } + //地暖 + temp = cursor.getInt(cursor.getColumnIndex("HeatUpType_Arr")); + if (temp != 0) { + RoomController.floor.setEnable(true); + RoomController.floor.setAddress(ADDRESS[temp]); + RoomController.floor.setModel(RoomController.floor.getModels()[cursor.getInt(cursor.getColumnIndex("HeatUpModel"))]); + Floor.importDefaultPWMToFloor(); + } + int maxGear = cursor.getInt(cursor.getColumnIndex("HeatingMaxGear")); + RoomController.floor.setMaxGear(maxGear); + //继电器 + temp = cursor.getInt(cursor.getColumnIndex("JDQType")); + if (temp != 0) { + RoomController.relay.setEnable(true); + RoomController.relay.setAddress(ADDRESS[temp]); + RoomController.relay.setModel(RoomController.relay.getModels()[cursor.getInt(cursor.getColumnIndex("DeviceModel"))]); + int[] ports = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + ports[0] = cursor.getInt(cursor.getColumnIndex("Port_1")); + ports[1] = cursor.getInt(cursor.getColumnIndex("Port_2")); + ports[2] = cursor.getInt(cursor.getColumnIndex("Port_3")); + ports[3] = cursor.getInt(cursor.getColumnIndex("Port_4")); + ports[4] = cursor.getInt(cursor.getColumnIndex("Port_5")); + ports[5] = cursor.getInt(cursor.getColumnIndex("Port_6")); + ports[6] = cursor.getInt(cursor.getColumnIndex("Port_7")); + ports[7] = cursor.getInt(cursor.getColumnIndex("Port_8")); + //0不启用,1地暖,2空调,3加湿器,4高进换气扇,5低进换气扇,6红光灯,7白光灯,8匀风扇,9紫外灯 + for (int i = 0; i < 8; i++) { + if (ports[i] == 1) { + //之前为地暖,为古老版本,现以不需要 + } else if (ports[i] == 2) { + RoomController.airCondition.clearAndSetPort(i + 1); + } else if (ports[i] == 3) { + RoomController.humidifier.clearAndSetPort(i + 1); + } else if (ports[i] == 4) { + RoomController.intakeFan.clearAndSetPort(i + 1); + } else if (ports[i] == 5) { + RoomController.exhaustFan.clearAndSetPort(i + 1); + } else if (ports[i] == 6) { + RoomController.redLight.clearAndSetPort(i + 1); + } else if (ports[i] == 7) { + RoomController.whiteLight.clearAndSetPort(i + 1); + } else if (ports[i] == 8) { + RoomController.evenFan.clearAndSetPort(i + 1); + } else if (ports[i] == 9) { + RoomController.uvLight.clearAndSetPort(i + 1); + } + } + } + } + //控制器配置 + cursor = dataBaseUtil.Select("select * from DevicesSetting"); + Device.setControllerModel(Controller.CONTROLLER_MODEL[0]); + HardwareSetting.setWorkMode(HardwareSetting.CONTROLLER_WORK_MODE[0]); + if (cursor.moveToNext()) { + HardwareSetting.setSensorSerialCom(SpinnerList.SERIAL_ADDRESS[cursor.getInt(cursor.getColumnIndex("SensorCOMName"))]); + HardwareSetting.setControllerSerialCom(SpinnerList.SERIAL_ADDRESS[cursor.getInt(cursor.getColumnIndex("ControlCOMName"))]); + } + //消毒计划配置 + UVTaskUtil.removeAllUvTask(); + cursor = dataBaseUtil.Select("select * from ScheduledTasks where areacode='" + roomId + "' and flag=1 order by id asc"); + while (cursor.moveToNext()) { + int id = cursor.getInt(cursor.getColumnIndex("TaskNum")); + MyTask myTask = null; + for (MyTask temp : UVTaskUtil.getUvTask()) { + if (temp.getId() == id) { + myTask = temp; + break; + } + } + if (myTask == null) { + myTask = new MyTask(); + myTask.setId(id); + } + if (cursor.getInt(cursor.getColumnIndex("Execute")) == 1) { + myTask.setStartTimeHour(cursor.getInt(cursor.getColumnIndex("HOUR"))); + myTask.setStartTimeMinute(cursor.getInt(cursor.getColumnIndex("MINUTE"))); + UVTaskUtil.addUvTask(myTask); + } else { + myTask.setEndTimeHour(cursor.getInt(cursor.getColumnIndex("HOUR"))); + myTask.setEndTimeMinute(cursor.getInt(cursor.getColumnIndex("MINUTE"))); + UVTaskUtil.updateUvTask(myTask); + } + } + cursor.close(); + } + + /** + * 是否为第一次旧系统到新系统转换 + */ + public static boolean isFirstTime() { + boolean result = MMKVUtil.get(FIRST_TIME, true); + if (result) { + MMKVUtil.put(FIRST_TIME, false); + } + return result; + } + /** + * 旧系统模式转换为新系统模式 + * 1 智能模式,2自动模式,3手动模式 + * 转为 0 手动模式,1自动模式,2智能模式 + */ + public static int oldModeToNewMode(int oldMode) { + switch (oldMode) { + case 1: + return 2; + case 2: + return 1; + case 3: + return 0; + } + return -1; + } + + /** + * 新系统模式转换为旧系统模式 + * 0 手动模式,1自动模式,2智能模式 + * 转为 1 智能模式,2自动模式,3手动模式 + */ + public static int newModeToOldMode(int newMode) { + switch (newMode) { + case 0: + return 3; + case 1: + return 2; + case 2: + return 1; + } + return -1; + } +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/timer/ThemeTaskUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/timer/ThemeTaskUtil.java new file mode 100644 index 0000000..afd3f02 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/timer/ThemeTaskUtil.java @@ -0,0 +1,121 @@ +package com.example.iot_controlhost.utils.timer; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.receiver.ThemeReceiver; +import com.example.iot_controlhost.utils.global.RoomSetting; +import com.example.iot_controlhost.utils.log.MyLog; +import com.luckycatlabs.sunrisesunset.SunriseSunsetCalculator; +import com.luckycatlabs.sunrisesunset.dto.Location; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * 亮暗色调自动定时切换工具类 + */ +public class ThemeTaskUtil { + private static final int REQUEST_CODE_LIGHT = 62541; + private static final int REQUEST_CODE_NIGHT = 96547; + + /** + * 开始自动任务 + * + * @return 当前是否应该为暗色调 + */ + public static boolean resetTask() { + //设定新计划前先清除旧计划 + MyLog.auto("定时任务:正在重置亮暗色调定时任务"); + stop(); + double latitude = RoomSetting.getRoomCoordinateLatitude(); + double longitude = RoomSetting.getRoomCoordinateLongitude(); +// latitude = 27.07936; +// longitude = 102.35740; + Location location = new Location(latitude, longitude); + // 创建日出日落计算器对象 + SunriseSunsetCalculator calculator = new SunriseSunsetCalculator(location, TimeZone.getDefault()); + // 获取日出时间 + String sunrise = calculator.getOfficialSunriseForDate(Calendar.getInstance()); + // 获取日落时间 + String sunset = calculator.getOfficialSunsetForDate(Calendar.getInstance()); + + // 解析日出时间的小时和分钟 + String[] sunriseParts = sunrise.split(":"); + int sunriseHour = Integer.parseInt(sunriseParts[0]); + int sunriseMinute = Integer.parseInt(sunriseParts[1]); + + // 解析日落时间的小时和分钟 + String[] sunsetParts = sunset.split(":"); + int sunsetHour = Integer.parseInt(sunsetParts[0]); + int sunsetMinute = Integer.parseInt(sunsetParts[1]); + //测试时间 +// sunriseHour = 15; +// sunriseMinute = 23; +// sunsetHour = 15; +// sunsetMinute = 20; + + Context context = MyApp.getAppContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, ThemeReceiver.class); + + // 设置亮色调的时间 + Calendar openCalendar = Calendar.getInstance(); + openCalendar.set(Calendar.HOUR_OF_DAY, sunriseHour); + openCalendar.set(Calendar.MINUTE, sunriseMinute); + openCalendar.set(Calendar.SECOND, 0); + long openTimeInMillis = openCalendar.getTimeInMillis(); + MyLog.auto("定时任务:" + TimeUtils.date2String(new Date(openTimeInMillis)) + "时开启亮色调"); + // 如果开启时间在当前时间之前 + if (openCalendar.before(Calendar.getInstance())) { +// MyLog.auto("定时任务:开启亮色调在当前时间之前,不设定亮色调开启时间"); + } else { + intent.setAction(ThemeReceiver.THEME_LIGHT); + PendingIntent openPendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_LIGHT, intent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set(AlarmManager.RTC_WAKEUP, openTimeInMillis, openPendingIntent); + } + // 设置暗色调的时间 + Calendar closeCalendar = Calendar.getInstance(); + closeCalendar.set(Calendar.HOUR_OF_DAY, sunsetHour); + closeCalendar.set(Calendar.MINUTE, sunsetMinute); + closeCalendar.set(Calendar.SECOND, 0); + long closeTimeInMillis = closeCalendar.getTimeInMillis(); + MyLog.auto("定时任务:" + TimeUtils.date2String(new Date(closeTimeInMillis)) + "时开启暗模式"); + // 如果关闭时间在当前时间之前 + if (closeCalendar.before(Calendar.getInstance())) { +// MyLog.auto("定时任务:开启暗色调在当前时间之前,不设定暗色调开启时间"); + } else { + intent.setAction(ThemeReceiver.THEME_NIGHT); + PendingIntent closePendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_NIGHT, intent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.set(AlarmManager.RTC_WAKEUP, closeTimeInMillis, closePendingIntent); + } + + // 获取当前时间 + Calendar currentTime = Calendar.getInstance(); + int currentHour = currentTime.get(Calendar.HOUR_OF_DAY); // 获取当前小时 + int currentMinute = currentTime.get(Calendar.MINUTE); // 获取当前分钟 + // 判断当前时间是否在日出和日落之间(含边界) + boolean isBeforeSunrise = currentHour < sunriseHour || (currentHour == sunriseHour && currentMinute < sunriseMinute); + boolean isAfterSunset = currentHour > sunsetHour || (currentHour == sunsetHour && currentMinute >= sunsetMinute); + + return isBeforeSunrise || isAfterSunset; + } + /** + * 关闭定时任务 + */ + private static void stop() { + Context context = MyApp.getAppContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, ThemeReceiver.class); + PendingIntent openPendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_LIGHT, intent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.cancel(openPendingIntent); + PendingIntent closePendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_NIGHT, intent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.cancel(closePendingIntent); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/timer/TimerTaskUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/timer/TimerTaskUtil.java new file mode 100644 index 0000000..4c2e8a6 --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/timer/TimerTaskUtil.java @@ -0,0 +1,74 @@ +package com.example.iot_controlhost.utils.timer; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.receiver.TimerReceiver; +import com.example.iot_controlhost.utils.log.MyLog; + +import java.util.Calendar; +import java.util.Date; + +/** + * 每日定时任务工具类 + */ +public class TimerTaskUtil { + /** + * 每日启动时间-小时 + */ + private static final int DAILY_TIMER_HOUR = 0; + /** + * 每日启动时间-分钟 + */ + private static final int DAILY_TIMER_MINUTE = 30; + /** + * 请求码 + */ + private static final int REQUEST_CODE_DAILY_TIMER = 85412; + + /** + * 开始自动任务 + * + * @return 当前是否应该为亮色调(当前时间是否在日出和日落之间) + */ + public static void start() { + //设定新计划前先清除旧计划 + MyLog.auto("定时任务:正在重置每日定时任务"); + stop(); + Context context = MyApp.getAppContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, TimerReceiver.class); + // 设置定时时间 + Calendar openCalendar = Calendar.getInstance(); + openCalendar.set(Calendar.HOUR_OF_DAY, DAILY_TIMER_HOUR); + openCalendar.set(Calendar.MINUTE, DAILY_TIMER_MINUTE); + openCalendar.set(Calendar.SECOND, 0); + // 如果开启时间在当前时间之前 + if (openCalendar.before(Calendar.getInstance())) { + MyLog.auto("定时任务:计划在当前时间之前,将时间设定为明天的同一时间"); + openCalendar.add(Calendar.DAY_OF_YEAR, 1); + } + long openTimeInMillis = openCalendar.getTimeInMillis(); + MyLog.auto("定时任务:" + TimeUtils.date2String(new Date(openTimeInMillis)) + "时刷新每日计划"); + intent.setAction(TimerReceiver.DAILY_TIMER); + PendingIntent openPendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_DAILY_TIMER, intent, PendingIntent.FLAG_UPDATE_CURRENT); +// alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, openTimeInMillis, AlarmManager.INTERVAL_DAY, openPendingIntent); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, openTimeInMillis, openPendingIntent); + } + + /** + * 关闭定时任务 + */ + private static void stop() { + Context context = MyApp.getAppContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, TimerReceiver.class); + PendingIntent openPendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE_DAILY_TIMER, intent, PendingIntent.FLAG_UPDATE_CURRENT); + alarmManager.cancel(openPendingIntent); + } + +} diff --git a/app/src/main/java/com/example/iot_controlhost/utils/timer/UVTaskUtil.java b/app/src/main/java/com/example/iot_controlhost/utils/timer/UVTaskUtil.java new file mode 100644 index 0000000..eb44c5e --- /dev/null +++ b/app/src/main/java/com/example/iot_controlhost/utils/timer/UVTaskUtil.java @@ -0,0 +1,175 @@ +package com.example.iot_controlhost.utils.timer; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import com.blankj.utilcode.util.TimeUtils; +import com.example.iot_controlhost.MyApp; +import com.example.iot_controlhost.model.MyTask; +import com.example.iot_controlhost.receiver.UvTaskReceiver; +import com.example.iot_controlhost.utils.MMKVUtil; +import com.example.iot_controlhost.utils.global.RoomController; +import com.example.iot_controlhost.utils.log.MyLog; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * 消毒灯定时任务工具类 + */ +public class UVTaskUtil { + /** + * 消毒任务 + */ + public static final String UV_TASK = "UV_TASK"; + + public static boolean addUvTask(MyTask task) { + String json = MMKVUtil.get(UV_TASK, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + for (MyTask t : getUvTask()) { + if (hasConflict(task.getStartTimeHour(), task.getStartTimeMinute(), task.getEndTimeHour(), task.getEndTimeMinute(), + t.getStartTimeHour(), t.getStartTimeMinute(), t.getEndTimeHour(), t.getEndTimeMinute())) { + return false; + } + } + temp.add(task); + MMKVUtil.put(UV_TASK, new Gson().toJson(temp)); + startTask(task); + return true; + } + + public static boolean hasConflict(int startHour1, int startMinute1, int endHour1, int endMinute1, + int startHour2, int startMinute2, int endHour2, int endMinute2) { + // 计算第一个时间段的开始时间和结束时间的总分钟数 + int startTime1InMinutes = startHour1 * 60 + startMinute1; + int endTime1InMinutes = endHour1 * 60 + endMinute1; + + // 计算第二个时间段的开始时间和结束时间的总分钟数 + int startTime2InMinutes = startHour2 * 60 + startMinute2; + int endTime2InMinutes = endHour2 * 60 + endMinute2; + + // 检查是否存在冲突 + boolean conflict = (startTime1InMinutes >= startTime2InMinutes && startTime1InMinutes < endTime2InMinutes) || + (endTime1InMinutes > startTime2InMinutes && endTime1InMinutes <= endTime2InMinutes) || + (startTime2InMinutes >= startTime1InMinutes && startTime2InMinutes < endTime1InMinutes) || + (endTime2InMinutes > startTime1InMinutes && endTime2InMinutes <= endTime1InMinutes); + + return conflict; + } + + /** + * 更新定时任务 + * + * @param myTask 定时任务 + */ + public static void updateUvTask(MyTask myTask) { + String json = MMKVUtil.get(UV_TASK, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + for (int i = 0; i < temp.size(); i++) { + if (temp.get(i).getId() == myTask.getId()) { + temp.set(i, myTask); + break; + } + } + MMKVUtil.put(UV_TASK, new Gson().toJson(temp)); + } + + public static List getUvTask() { + String json = MMKVUtil.get(UV_TASK, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + return temp; + } + + public static void removeUvTask(MyTask myTask) { + String json = MMKVUtil.get(UV_TASK, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + temp.remove(myTask); + MMKVUtil.put(UV_TASK, new Gson().toJson(temp)); + //关闭定时器 + stopTask(myTask); + } + + public static void removeAllUvTask() { + MMKVUtil.put(UV_TASK, "[]"); + } + + /** + * 开始定时任务 + * + * @param task + */ + public static void startTask(MyTask task) { + if (!RoomController.uvLight.isAvailable()) { + MyLog.auto("消毒灯定时任务:消毒灯不可用,消毒任务启用无效"); + return; + } + Context context = MyApp.getAppContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, UvTaskReceiver.class); + + // 设置开启灯的时间 + Calendar openCalendar = Calendar.getInstance(); + openCalendar.set(Calendar.HOUR_OF_DAY, task.getStartTimeHour()); + openCalendar.set(Calendar.MINUTE, task.getStartTimeMinute()); + openCalendar.set(Calendar.SECOND, 0); + // 如果开启时间在当前时间之前,将开启时间设定为明天的同一时间 + if (openCalendar.before(Calendar.getInstance())) { + openCalendar.add(Calendar.DAY_OF_YEAR, 1); + } + long openTimeInMillis = openCalendar.getTimeInMillis(); + MyLog.auto("定时任务:" + TimeUtils.date2String(new Date(openTimeInMillis)) + "时开启消毒灯"); + intent.setAction(UvTaskReceiver.OPEN_LIGHT); + PendingIntent openPendingIntent = PendingIntent.getBroadcast(context, task.getId() * 3, intent, PendingIntent.FLAG_UPDATE_CURRENT); +// alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, openTimeInMillis, AlarmManager.INTERVAL_DAY, openPendingIntent); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, openTimeInMillis, openPendingIntent); + // 设置关闭灯的时间 + Calendar closeCalendar = Calendar.getInstance(); + closeCalendar.set(Calendar.HOUR_OF_DAY, task.getEndTimeHour()); + closeCalendar.set(Calendar.MINUTE, task.getEndTimeMinute()); + closeCalendar.set(Calendar.SECOND, 0); + // 如果关闭时间在当前时间之前,将关闭时间设定为明天的同一时间 + if (closeCalendar.before(Calendar.getInstance())) { + closeCalendar.add(Calendar.DAY_OF_YEAR, 1); + } + long closeTimeInMillis = closeCalendar.getTimeInMillis(); + MyLog.auto("定时任务:" + TimeUtils.date2String(new Date(closeTimeInMillis)) + "时关闭消毒灯"); + intent.setAction(UvTaskReceiver.CLOSE_LIGHT); + PendingIntent closePendingIntent = PendingIntent.getBroadcast(context, task.getId() * 3 + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT); +// alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, closeTimeInMillis, AlarmManager.INTERVAL_DAY, closePendingIntent); + alarmManager.setExact(AlarmManager.RTC_WAKEUP, closeTimeInMillis, closePendingIntent); + + } + + /** + * 关闭定时任务 + * + * @param myTask + */ + public static void stopTask(MyTask myTask) { + Context context = MyApp.getAppContext(); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(context, UvTaskReceiver.class); + + PendingIntent openPendingIntent = PendingIntent.getBroadcast(context, myTask.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent closePendingIntent = PendingIntent.getBroadcast(context, -myTask.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT); + + alarmManager.cancel(openPendingIntent); + alarmManager.cancel(closePendingIntent); + } + + public static void restartTask() { + List tasks = getUvTask(); + for (MyTask task : tasks) { + startTask(task); + } + } +} diff --git a/app/src/main/java/com/ys/mcu7502/Mcu7502.java b/app/src/main/java/com/ys/mcu7502/Mcu7502.java new file mode 100644 index 0000000..8787e91 --- /dev/null +++ b/app/src/main/java/com/ys/mcu7502/Mcu7502.java @@ -0,0 +1,96 @@ +package com.ys.mcu7502; + +/** + * Created by Administrator on 2018/1/27. + */ + +public class Mcu7502 { + static { + System.loadLibrary("mcu7502"); + } + /** + * @method open() + * @description 打开文件,开启看门狗功能必须先打开文件 + * @date: 20200104 + * @author: zouyuanhang + * @return 失败返回-1,其余代表成功 + */ + public static native int open();//60 + /** + * @method close() + * @description 关闭文件 + * @date: 20200104 + * @author: zouyuanhang + */ + public static native void close(); + /** + * @method enableWatchdog() + * @description 打开看门狗 + * @date: 20200104 + * @author: zouyuanhang + * @param path + * @return 返回值为7表示命令发送成功 + */ + public static native int enableWatchdog(String path); //返回值为7表示命令发送成功 + + /** + * @method disableWatchdog() + * @description 关闭看门狗 + * @date: 20200104 + * @author: zouyuanhang + * @return 失败返回-1,其余代表成功 + */ + public static native int disableWatchdog(); //返回值为7表示命令发送成功 + /** + * @method feetDog(int time) + * @description 设置喂狗的时间 + * @date: 20200104 + * @author: zouyuanhang + * @param time + * @return 返回值为7表示命令发送成功 + */ + public static native int feetDog(int time); //喂狗的时间,time大于等于10s,小于等于60s ,返回值为7表示命令发送成功 + /** + * @method setTime(int time) + * @description 设置下一次开机的时间 + * @date: 20200104 + * @author: zouyuanhang + * @param time + * @return 返回值为7表示命令发送成功 + */ + public static native int setTime(int time); //设置下一次开机的时间 ,以s为单位。大于等于60s ,返回值为7表示命令发送成功 + /** + * @method resetMaster() + * @description 重置 + * @date: 20200104 + * @author: zouyuanhang + * @return 返回值为7表示命令发送成功 + */ + public static native int resetMaster(); //返回值为7表示命令发送成功 + /** + * @method getWatchdogStatus() + * @description 返回值为看门狗状态 + * @date: 20200104 + * @author: zouyuanhang + * @return 大于0已经打开0表示关闭 + */ + public static native int getWatchdogStatus(); //返回值为看门狗状态,大于0表示已经打开0表示关闭 + /** + * @method getVersion() + * @description 获取版本号 + * @date: 20200104 + * @author: zouyuanhang + * @return 返回版本号 + */ + public static native int getVersion(); //返回值为int型变量,第一个字节未定义,第二个字节表示版本号第前两位,第三个字节表示版本号中间两位,第四个字节表示版本号的后两位 + + /** + * @method appWatchdogStatus() + * @description 查询3128喂狗状态 + * @date: 2020/3/28 + * @author: zouyuanhang + * @param * @param null + * @return + */ + public static native int appWatchdogStatus(); //rk3128 watchdog +} diff --git a/app/src/main/java/com/ys/ys.zip b/app/src/main/java/com/ys/ys.zip new file mode 100644 index 0000000..0b83228 Binary files /dev/null and b/app/src/main/java/com/ys/ys.zip differ diff --git a/app/src/main/res/drawable/bg_controller_close.xml b/app/src/main/res/drawable/bg_controller_close.xml new file mode 100644 index 0000000..3c5baf0 --- /dev/null +++ b/app/src/main/res/drawable/bg_controller_close.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_controller_open.xml b/app/src/main/res/drawable/bg_controller_open.xml new file mode 100644 index 0000000..27fe684 --- /dev/null +++ b/app/src/main/res/drawable/bg_controller_open.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_main_user_info.xml b/app/src/main/res/drawable/bg_main_user_info.xml new file mode 100644 index 0000000..40636ed --- /dev/null +++ b/app/src/main/res/drawable/bg_main_user_info.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_none.xml b/app/src/main/res/drawable/bg_none.xml new file mode 100644 index 0000000..bfda0aa --- /dev/null +++ b/app/src/main/res/drawable/bg_none.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/bg_process.xml b/app/src/main/res/drawable/bg_process.xml new file mode 100644 index 0000000..d4ff412 --- /dev/null +++ b/app/src/main/res/drawable/bg_process.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_work.xml b/app/src/main/res/drawable/bg_work.xml new file mode 100644 index 0000000..decda42 --- /dev/null +++ b/app/src/main/res/drawable/bg_work.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_work_blue.xml b/app/src/main/res/drawable/bg_work_blue.xml new file mode 100644 index 0000000..ce59bc1 --- /dev/null +++ b/app/src/main/res/drawable/bg_work_blue.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_work_orange.xml b/app/src/main/res/drawable/bg_work_orange.xml new file mode 100644 index 0000000..140bb6a --- /dev/null +++ b/app/src/main/res/drawable/bg_work_orange.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/forward.xml b/app/src/main/res/drawable/forward.xml new file mode 100644 index 0000000..48ab0fe --- /dev/null +++ b/app/src/main/res/drawable/forward.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/message.xml b/app/src/main/res/drawable/message.xml new file mode 100644 index 0000000..a0b9f2f --- /dev/null +++ b/app/src/main/res/drawable/message.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml new file mode 100644 index 0000000..25e0a79 --- /dev/null +++ b/app/src/main/res/layout/activity_debug.xml @@ -0,0 +1,406 @@ + + + + + + + +