commit 194033514fa1d4198f1e5f4522216e139066b0bc
Author: Developer <91611@user.local>
Date: Mon May 18 17:58:11 2026 +0800
init
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ac5aa98
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+build/
diff --git a/.metadata b/.metadata
new file mode 100644
index 0000000..a77bebb
--- /dev/null
+++ b/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
+ channel: "stable"
+
+project_type: plugin
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ - platform: android
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..41cc7d8
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..ba75c69
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..cb7f05a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# terra
+
+A new Flutter project.
+
+## Getting Started
+
+This project is a starting point for a Flutter
+[plug-in package](https://flutter.dev/developing-packages/),
+a specialized package that includes platform-specific implementation code for
+Android and/or iOS.
+
+For help getting started with Flutter development, view the
+[online documentation](https://flutter.dev/docs), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
+
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..a5744c1
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..161bdcd
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.cxx
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..7712747
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,60 @@
+group = "com.example.terra"
+version = "1.0"
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath("com.android.tools.build:gradle:7.3.0")
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ flatDir{
+ dirs project(':terra').file('libs')
+ }
+ }
+}
+
+apply plugin: "com.android.library"
+
+android {
+ if (project.android.hasProperty("namespace")) {
+ namespace = "com.example.terra"
+ }
+
+ compileSdk = 34
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ minSdk = 29
+ }
+
+ dependencies {
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("org.mockito:mockito-core:5.0.0")
+
+ implementation(name: 'terra',ext: 'aar')
+ implementation(name: 'RamanMatch',ext: 'aar')
+ }
+
+ testOptions {
+ unitTests.all {
+ testLogging {
+ events "passed", "skipped", "failed", "standardOut", "standardError"
+ outputs.upToDateWhen {false}
+ showStandardStreams = true
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/libs/RamanMatch.aar b/android/libs/RamanMatch.aar
new file mode 100644
index 0000000..ce2ca90
Binary files /dev/null and b/android/libs/RamanMatch.aar differ
diff --git a/android/libs/terra.aar b/android/libs/terra.aar
new file mode 100644
index 0000000..2e7ff02
Binary files /dev/null and b/android/libs/terra.aar differ
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..c90a7a9
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'terra'
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f16b198
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/android/src/main/java/com/example/terra/TerraPlugin.java b/android/src/main/java/com/example/terra/TerraPlugin.java
new file mode 100644
index 0000000..73fe39e
--- /dev/null
+++ b/android/src/main/java/com/example/terra/TerraPlugin.java
@@ -0,0 +1,974 @@
+package com.example.terra;
+
+import androidx.annotation.NonNull;
+
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.optics.terra.Device;
+import com.optics.terra.DeviceWrapper;
+import android.content.Context;
+import android.util.Log;
+
+import com.example.terra.utils.ArrayUtils;
+import com.ramanmatch.match.RamanMatch;
+import com.ramanmatch.match.MatchResult;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * TerraPlugin - 拉曼光谱仪 Flutter 通信插件
+ *
+ * 提供 Flutter 与拉曼光谱仪硬件设备之间的 MethodChannel 通信桥梁,
+ * 支持设备管理、参数配置、光谱采集、数据处理和算法匹配等功能。
+ */
+public class TerraPlugin implements FlutterPlugin, MethodCallHandler {
+
+ private static final String TAG = "TerraPlugin";
+ private static final String CHANNEL_NAME = "terra";
+
+ /** 默认激光功率 */
+ private static final int DEFAULT_LASER_POWER = 500;
+ /** 默认积分时间 (ms) */
+ private static final int DEFAULT_INTEGRATION_TIME = 500;
+ /** 消荧光因子,数字越小削得越狠 */
+ private static final int FLUORESCENCE_REMOVAL_FACTOR = 15;
+ /** 光谱饱和阈值 */
+ private static final double SATURATION_THRESHOLD = 65535.0;
+ /** 波数起始截取值 */
+ private static final double WAVE_NUMBER_START = 200.0;
+
+ /** 设备 SN 码与算法激活码、激光系数的映射表 */
+ private static final Map DEVICE_CONFIG_MAP;
+
+ //SN,算法激活码,激光系数
+ static {
+ Map map = new HashMap<>();
+ map.put("B7BP340105", new String[]{"2bb7b4a2206a90bea48fe999cace47ca", "0080080016002500"});
+ map.put("B7BP340101", new String[]{"10a1cf036a325b3e747dccefcf57ce84", "0081075016002300"});
+ map.put("B7BP230208", new String[]{"8bec16f120a49cd9987cd7f4dab60510", "0100100030004800"});
+ map.put("B7BP230209", new String[]{"0eb603282ef9d0f73a47a548c9cb8c9e", "0020023006801400"});
+ DEVICE_CONFIG_MAP = Collections.unmodifiableMap(map);
+ }
+
+ private MethodChannel channel;
+ private Context context;
+ private Device device;
+
+ /** 200 波数对应的像素位置索引 */
+ private int startPxIndexForWaveNumber = 0;
+ /** 当前积分时间 (ms) */
+ private int integrationTime = DEFAULT_INTEGRATION_TIME;
+ /** 波数数组 */
+ private double[] waveNumber = null;
+
+ /** USB 操作单线程执行器,确保所有调用串行化 */
+ private volatile ExecutorService usbExecutor = Executors.newSingleThreadExecutor();
+
+ /** 普通 USB 调用超时(5 秒) */
+ private static final int USB_CALL_TIMEOUT_MS = 5000;
+ /** 光谱采集超时(60 秒,支持深度模式长时间采集) */
+ private static final int SPECTRUM_TIMEOUT_MS = 60000;
+
+ // ==================== 生命周期方法 ====================
+
+ @Override
+ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
+ channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), CHANNEL_NAME);
+ channel.setMethodCallHandler(this);
+ context = flutterPluginBinding.getApplicationContext();
+ }
+
+ @Override
+ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
+ channel.setMethodCallHandler(null);
+ }
+
+ /** Flutter MethodChannel 方法调用分发 */
+ @Override
+ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
+ switch (call.method) {
+ // --- 平台信息 ---
+ case "getPlatformVersion":
+ result.success("Android " + android.os.Build.VERSION.RELEASE);
+ break;
+
+ // --- 设备管理 ---
+ case "openAllDevices":
+ handleOpenAllDevices(result);
+ break;
+ case "closeAllDevices":
+ handleCloseAllDevices(result);
+ break;
+ case "close":
+ handleCloseDevice(result);
+ break;
+ case "getDeviceSn":
+ handleGetDeviceSn(result);
+ break;
+ case "getIndex":
+ handleGetIndex(result);
+ break;
+ case "isConnect":
+ handleIsConnect(result);
+ break;
+ case "openDebug":
+ handleOpenDebug();
+ result.success(true);
+ break;
+ case "checkUsbPermission":
+ handleCheckUsbPermission(result);
+ break;
+
+ // --- CCD 制冷控制 ---
+ case "setCCDTECOn":
+ safeBooleanCall(result, () -> device.setCCDTECOn());
+ break;
+ case "setCCDTECOff":
+ safeBooleanCall(result, () -> device.setCCDTECOff());
+ break;
+ case "setCCDTECTemperature":
+ safeBooleanCall(result, () -> device.setCCDTECTemperature(getIntArg(call, "temperature")));
+ break;
+ case "getCCDTECState":
+ result.success(device.getCCDTECState());
+ break;
+
+ // --- 参数配置 ---
+ case "setIntegrationTime":
+ handleSetIntegrationTime(call, result);
+ break;
+ case "setAverageTimes":
+ handleSetAverageTimes(call, result);
+ break;
+ case "setTriggerMode":
+ handleSetTriggerMode(call, result);
+ break;
+
+ // --- 光谱数据获取 ---
+ case "getSpectrum":
+ result.success(device.getSpectrum());
+ break;
+ case "getResetSpectrum":
+ result.success(device.getResetSpectrum());
+ break;
+ case "getWavelength":
+ result.success(device.getWavelength());
+ break;
+ case "getWaveNumber":
+ result.success(device.getWaveNumber());
+ break;
+ case "getXvalue":
+ result.success(ArrayUtils.InterpolX);
+ break;
+
+ // --- 脉冲参数控制 ---
+ case "setLampDelayTime":
+ safeBooleanCall(result, () -> device.setLampDelayTime(getDoubleArg(call, "delayTime")));
+ break;
+ case "setLampWidth":
+ safeBooleanCall(result, () -> device.setLampWidth(getDoubleArg(call, "width")));
+ break;
+ case "setLampInterval":
+ safeBooleanCall(result, () -> device.setLampInterval(getDoubleArg(call, "interval")));
+ break;
+ case "getLampDelayTime":
+ safeDoubleCall(result, () -> device.getLampDelayTime());
+ break;
+ case "getLampWidth":
+ safeDoubleCall(result, () -> device.getLampWidth());
+ break;
+ case "getLampInterval":
+ safeDoubleCall(result, () -> device.getLampInterval());
+ break;
+
+ // --- 激发波长与激光控制 ---
+ case "setExcitedWaveLength":
+ safeBooleanCall(result, () -> device.setExcitedWaveLength(getIntArg(call, "excitedWaveLength")));
+ break;
+ case "setLaserPower":
+ int power = getIntArg(call, "power");
+ Log.i(TAG, "setLaserPower 请求: power=" + power);
+ if (power < 0 || power > 2000) {
+ Log.e(TAG, "setLaserPower 参数超出范围 (0-2000): " + power + ",拒绝执行");
+ result.success(false);
+ break;
+ }
+ safeBooleanCall(result, "setLaserPower", () -> device.setLaserPower(power));
+ break;
+ case "setLaserOn":
+ Log.i(TAG, "收到 setLaserOn 请求");
+ safeBooleanCall(result, () -> {
+ boolean is_tec_open = device.isTECOpen();
+ Log.i(TAG, "TEC 是否打开:" + is_tec_open);
+ if (!is_tec_open) {
+ boolean set_tec_res = device.setTECOn();
+ Log.i(TAG, "TEC 打开结果:" + set_tec_res);
+ }
+
+ boolean res = device.setLaserOn();
+ Log.i(TAG, "device.setLaserOn() 返回:" + res);
+ return res;
+ });
+ break;
+ case "setLaserOff":
+ Log.i(TAG, "收到 setLaserOff 请求");
+ safeBooleanCall(result, () -> {
+ boolean res = device.setLaserOff();
+ Log.i(TAG, "device.setLaserOff() 返回:" + res);
+ return res;
+ });
+ break;
+ case "setLaserPowerOn":
+ safeBooleanCall(result, () -> device.setLaserPowerOn());
+ break;
+ case "setLaserPowerOff":
+ safeBooleanCall(result, () -> device.setLaserPowerOff());
+ break;
+
+ // --- 激光校正系数 ---
+ case "getLaserPowerCorrectCoefficient":
+ handleGetLaserPowerCorrectCoefficient(result);
+ break;
+ case "setLaserPowerCorrectCoefficient":
+ handleSetLaserPowerCorrectCoefficient(call, result);
+ break;
+
+ // --- 硬件开关控制 ---
+ case "setTECOn":
+ safeBooleanCall(result, () -> device.setTECOn());
+ break;
+ case "setTECOff":
+ safeBooleanCall(result, () -> device.setTECOff());
+ break;
+ case "setMainBoardOn":
+ safeBooleanCall(result, () -> device.setMainBoardOn());
+ break;
+ case "setMainBoardOff":
+ safeBooleanCall(result, () -> device.setMainBoardOff());
+ break;
+ case "isMainBoardOpen":
+ safeBooleanCall(result, () -> device.isMainBoardOpen());
+ break;
+ case "isTECOpen":
+ safeBooleanCall(result, () -> device.isTECOpen());
+ break;
+
+ // --- 光谱数据处理 ---
+ case "waveletSmooth":
+ handleWaveletSmooth(call, result);
+ break;
+ case "removeFluorescence":
+ handleRemoveFluorescence(call, result);
+ break;
+ case "lineInterpolation":
+ handleLineInterpolation(call, result);
+ break;
+ case "getRamanSpectrum":
+ handleGetRamanSpectrum(result);
+ break;
+ case "findPeaks":
+ handleFindPeaks(call, result);
+ break;
+
+ // --- 算法匹配与校准 ---
+ case "ramanMatchRegister":
+ handleRamanMatchRegister(result);
+ break;
+ case "ramanMatchCalcSimilarity":
+ handleRamanMatchCalcSimilarity(call, result);
+ break;
+ case "calibration":
+ handleCalibration(call, result);
+ break;
+ case "getYJCorrectCoefficient":
+ result.success(device.getYJCorrectCoefficient());
+ break;
+ case "setYJCorrectCoefficient":
+ handleSetYJCorrectCoefficient(call, result);
+ break;
+
+ // --- 其他校准信息查询 ---
+ case "getYAxisCorrectCoefficient":
+ result.success(device.getYAxisCorrectCoefficient());
+ break;
+ case "getCorrectForDetectorNonlinear":
+ result.success(device.getCorrectForDetectorNonlinear());
+ break;
+ case "getWavelengthCalibrationCoefficients":
+ result.success(device.getWavelengthCalibrationCoefficients());
+ break;
+ case "getFpgaVersion":
+ result.success(device.getFpgaVersion());
+ break;
+ case "getSlit":
+ result.success(device.getSlit());
+ break;
+ case "getBadPoints":
+ result.success(device.getBadPoints());
+ break;
+ case "setLampEnable":
+ safeBooleanCall(result, () -> device.setLampEnable((Boolean) ((Map) call.arguments()).get("enable")));
+ break;
+
+ // --- MD5 加密 ---
+ case "bytesToMD5":
+ String sn = (String) ((Map) call.arguments()).get("sn");
+ result.success(computeMD5(sn));
+ break;
+
+ default:
+ result.notImplemented();
+ break;
+ }
+ }
+
+ // ==================== 设备管理处理 ====================
+
+ /** 打开所有已连接的设备,并初始化第一个设备的默认参数 */
+ private void handleOpenAllDevices(Result result) {
+ try {
+ List deviceList = DeviceWrapper.openAllDevices(context);
+ device = deviceList.get(0);
+ device.setCallBack(state -> {
+ if (state) {
+ initDeviceDefaults();
+ }
+ });
+ result.success(deviceList.size());
+ } catch (Exception e) {
+ result.success(0);
+ }
+ }
+
+ /** 初始化设备默认参数(激光功率、积分时间、波数起始索引、激光校正系数) */
+ private void initDeviceDefaults() {
+ device.setLaserPower(DEFAULT_LASER_POWER);
+ device.setIntegrationTime(integrationTime);
+ waveNumber = device.getWaveNumber();
+ startPxIndexForWaveNumber = findWaveNumberStartIndex(waveNumber);
+
+ // 设置激光校正系数
+ String sn = device.getSerialNumber();
+ String laserCoefficient = getLaserPowerCoefficient(sn);
+ Log.i(TAG, "初始化设备参数:SN=" + sn + ", 激光校正系数:" + laserCoefficient);
+
+ if (laserCoefficient != null && !laserCoefficient.isEmpty() && laserCoefficient.length() == 16) {
+ try {
+ device.setLaserPowerCorrectCoefficient(laserCoefficient);
+ Log.i(TAG, "激光校正系数设置成功:" + laserCoefficient);
+ } catch (Exception e) {
+ Log.e(TAG, "设置激光校正系数失败:" + e.getMessage(), e);
+ }
+ } else {
+ Log.w(TAG, "激光校正系数为空或格式错误,跳过设置");
+ }
+ }
+
+ /**
+ * 查找波数数组中 >= 200 波数的起始索引位置
+ *
+ * @param waveNumber 波数数组
+ * @return 起始索引(目标位置的前一个),未找到返回 0
+ */
+ private int findWaveNumberStartIndex(double[] waveNumber) {
+ for (int i = 0; i < waveNumber.length; i++) {
+ if (waveNumber[i] >= WAVE_NUMBER_START) {
+ return i - 1;
+ }
+ }
+ return 0;
+ }
+
+ /** 关闭所有设备并释放引用 */
+ private void handleCloseAllDevices(Result result) {
+ try {
+ DeviceWrapper.closeAllDevices();
+ device = null;
+ result.success(true);
+ } catch (Exception e) {
+ result.success(false);
+ }
+ }
+
+ /** 获取设备序列号 */
+ private void handleGetDeviceSn(Result result) {
+ try {
+ result.success(device.getSerialNumber());
+ } catch (Exception e) {
+ result.success("");
+ }
+ }
+
+ /** 检查设备是否已连接 */
+ private void handleIsConnect(Result result) {
+ try {
+ result.success(device.isConnect());
+ } catch (Exception e) {
+ result.success(false);
+ }
+ }
+
+ /** 关闭单个设备 */
+ private void handleCloseDevice(Result result) {
+ try {
+ if (device != null) {
+ device.close();
+ device = null;
+ }
+ result.success(true);
+ } catch (Exception e) {
+ result.success(false);
+ }
+ }
+
+ /** 获取设备索引 */
+ private void handleGetIndex(Result result) {
+ try {
+ result.success(device != null ? device.getIndex() : -1);
+ } catch (Exception e) {
+ result.success(-1);
+ }
+ }
+
+ /** 开启 SDK 调试日志 */
+ private void handleOpenDebug() {
+ DeviceWrapper.openDebug();
+ }
+
+ /** 检查 USB 权限(SDK 的 PermissionCallBack 接口在 AAR 中不可用,此处占位) */
+ private void handleCheckUsbPermission(Result result) {
+ Map response = new HashMap<>();
+ response.put("deviceIndex", 0);
+ response.put("state", device != null && device.isConnect());
+ result.success(response);
+ }
+
+ // ==================== 参数配置处理 ====================
+
+ /** 设置积分时间 */
+ private void handleSetIntegrationTime(MethodCall call, Result result) {
+ result.success(device.setIntegrationTime(getDoubleArg(call, "integrationTime")));
+ }
+
+ /** 设置平均次数(设备方法无返回值,固定返回 true) */
+ private void handleSetAverageTimes(MethodCall call, Result result) {
+ device.setAverageTimes(getIntArg(call, "averageTimes"));
+ result.success(true);
+ }
+
+ /** 设置触发模式 */
+ private void handleSetTriggerMode(MethodCall call, Result result) {
+ result.success(device.setTriggerMode(getIntArg(call, "triggerMode")));
+ }
+
+ // ==================== 激光校正系数处理 ====================
+
+ /** 获取激光校正系数 */
+ private void handleGetLaserPowerCorrectCoefficient(Result result) {
+ try {
+ double[] coefficient = device.getLaserPowerCorrectCoefficient();
+ result.success(coefficient);
+ } catch (Exception e) {
+ Log.e(TAG, "获取激光校正系数失败:" + e.getMessage(), e);
+ result.success(new double[]{});
+ }
+ }
+
+ /**
+ * 设置激光校正系数
+ * @param call 调用参数,包含 calibrationCoefficient 字符串
+ * @param result 回调结果
+ */
+ private void handleSetLaserPowerCorrectCoefficient(MethodCall call, Result result) {
+ try {
+ String calibrationCoefficient = (String) ((Map) call.arguments()).get("calibrationCoefficient");
+ if (calibrationCoefficient == null || calibrationCoefficient.length() != 16) {
+ Log.e(TAG, "激光校正系数格式错误,必须为 16 位字符串");
+ result.success(false);
+ return;
+ }
+ device.setLaserPowerCorrectCoefficient(calibrationCoefficient);
+ result.success(true);
+ } catch (Exception e) {
+ Log.e(TAG, "设置激光校正系数失败:" + e.getMessage(), e);
+ result.success(false);
+ }
+ }
+
+ // ==================== 光谱数据处理 ====================
+
+ /** 小波平滑处理 */
+ private void handleWaveletSmooth(MethodCall call, Result result) {
+ try {
+ double[] spectrum = getDoubleArrayArg(call, "spectrum");
+ result.success(device.waveletSmooth(spectrum));
+ } catch (Exception e) {
+ result.success(new double[]{});
+ }
+ }
+
+ /** 消荧光处理 */
+ private void handleRemoveFluorescence(MethodCall call, Result result) {
+ try {
+ Map arguments = call.arguments();
+ double[] spectrum = getDoubleArrayArg(call, "spectrum");
+ int side = FLUORESCENCE_REMOVAL_FACTOR;
+ if (arguments != null && arguments.containsKey("side")) {
+ side = ((Number) arguments.get("side")).intValue();
+ }
+ result.success(device.removeFluorescence(spectrum, side));
+ } catch (Exception e) {
+ result.success(new double[]{});
+ }
+ }
+
+ /** 线性插值处理 */
+ private void handleLineInterpolation(MethodCall call, Result result) {
+ try {
+ double[] xDataRaw = getDoubleArrayArg(call, "xDataRaw");
+ double[] yDataRaw = getDoubleArrayArg(call, "yDataRaw");
+ double[] xData = getDoubleArrayArg(call, "xData");
+ result.success(device.lineInterpolation(xDataRaw, yDataRaw, xData));
+ } catch (Exception e) {
+ result.success(new double[]{});
+ }
+ }
+
+ /**
+ * 获取经过完整处理流程的拉曼光谱
+ * 在独立线程中执行,带 30 秒超时保护
+ */
+ private void handleGetRamanSpectrum(Result result) {
+ runWithTimeout(result, "getRamanSpectrum", this::acquireAndProcessSpectrum, SPECTRUM_TIMEOUT_MS, new double[]{});
+ }
+
+ /** 寻峰处理 */
+ private void handleFindPeaks(MethodCall call, Result result) {
+ Map arguments = call.arguments();
+ double[] spectrum = getDoubleArrayArg(call, "spectrum");
+ int minIndicesBetweenPeaks = (int) arguments.get("minIndicesBetweenPeaks");
+ double baseline = (double) arguments.get("baseline");
+ result.success(device.findPeaks(spectrum, minIndicesBetweenPeaks, baseline));
+ }
+
+ // ==================== 算法匹配与校准处理 ====================
+
+ /** 注册检测算法(根据设备 SN 码获取激活码) */
+ private void handleRamanMatchRegister(Result result) {
+ String sn = device.getSerialNumber();
+ String activationCode = getActivationCode(sn);
+ Log.i(TAG, "算法注册:sn=" + sn + ", code=" + activationCode);
+ result.success(RamanMatch.register(device, activationCode));
+ }
+
+ /** 光谱相似度比对 */
+ @SuppressWarnings("unchecked")
+ private void handleRamanMatchCalcSimilarity(MethodCall call, Result result) {
+ Map> arguments = call.arguments();
+ double[] spectrum = arrayListToDoubleArray(arguments.get("spectrum"));
+ double[] libSpectrum = arrayListToDoubleArray(arguments.get("libSpectrumList"));
+ MatchResult matchResult = RamanMatch.getInstance().calcSimilarity(spectrum, libSpectrum);
+ result.success(matchResult.getSimilarity());
+ }
+
+ /** 光谱校准 */
+ @SuppressWarnings("unchecked")
+ private void handleCalibration(MethodCall call, Result result) {
+ try {
+ Map arguments = call.arguments();
+ double[] spectrum = (double[]) arguments.get("spectrum");
+ double[] yjCoeff = arrayListToDoubleArray((ArrayList) arguments.get("YJCoeff"));
+ double[] calibrationResult = RamanMatch.getInstance().calibration(spectrum, yjCoeff);
+ if (calibrationResult == null) {
+ Log.e(TAG, "校准失败:RamanMatch.calibration 返回 null");
+ result.success(new double[]{});
+ } else {
+ result.success(calibrationResult);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "校准异常:" + e.getMessage(), e);
+ result.success(new double[]{});
+ }
+ }
+
+ /** 设置乙腈定标系数 */
+ private void handleSetYJCorrectCoefficient(MethodCall call, Result result) {
+ try {
+ Map arguments = call.arguments();
+ Object coffesObj = arguments.get("coffes");
+ double[] coffes;
+ if (coffesObj instanceof double[]) {
+ // 直接是 double 数组
+ coffes = (double[]) coffesObj;
+ } else if (coffesObj instanceof ArrayList) {
+ // ArrayList 转换为 double 数组
+ coffes = arrayListToDoubleArray((ArrayList) coffesObj);
+ } else {
+ Log.e(TAG, "coffes 参数类型错误:" + coffesObj.getClass().getName());
+ result.success(false);
+ return;
+ }
+ result.success(device.setYJCorrectCoefficient(coffes));
+ } catch (Exception e) {
+ Log.e(TAG, "设置乙腈定标系数失败:" + e.getMessage(), e);
+ result.success(false);
+ }
+ }
+
+ // ==================== 光谱采集核心流程 ====================
+
+ /**
+ * 执行完整的拉曼光谱采集与处理流程
+ * 流程:关激光 -> 采集背景 -> 开激光 -> 等待稳定 -> 采集样本 -> 关激光 -> 饱和检测 -> 扣除背景 -> 插值 -> Y 轴校正 -> 消荧光 -> 平滑 -> 取整
+ *
+ * @return 处理完成的光谱数据(取整后),饱和时返回空数组
+ */
+ private double[] acquireAndProcessSpectrum() {
+ // 1. 确保激光关闭后采集背景光谱
+ device.setLaserOff();
+ double[] bgSpectrum = device.getResetSpectrum();
+
+ // 2. 开激光
+ boolean laserOnRes = device.setLaserOn();
+ Log.i(TAG, "开激光结果:" + laserOnRes);
+
+ // 3. 等待激光功率稳定(2 秒)
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.w(TAG, "激光稳定等待被中断");
+ }
+
+ // 4. 采集样本光谱
+ double[] sampleSpectrum = device.getResetSpectrum();
+
+ // 5. 关激光
+ device.setLaserOff();
+
+ // 6. 饱和检测
+ if (isSpectrumSaturated(sampleSpectrum)) {
+ Log.w(TAG, "光谱饱和,数据无效");
+ return new double[]{};
+ }
+
+ // 7. 扣除背景
+ double[] spectrum = subtractBackground(sampleSpectrum, bgSpectrum);
+
+ // 8. 插值到波数 200~3200
+ spectrum = device.lineInterpolation(waveNumber, spectrum, ArrayUtils.InterpolX);
+
+ // 9. Y 轴校正
+ spectrum = device.yAxisCorrect(waveNumber, spectrum);
+
+ // 10. 消荧光
+ spectrum = device.removeFluorescence(spectrum, FLUORESCENCE_REMOVAL_FACTOR);
+
+ // 11. 平滑
+ spectrum = device.waveletSmooth(spectrum);
+
+ // 12. 取整
+ roundInPlace(spectrum);
+
+ return spectrum;
+ }
+
+ /**
+ * 扣除背景光谱(样本 - 背景)
+ *
+ * @param sample 样本光谱
+ * @param background 背景光谱
+ * @return 扣除背景后的光谱数据
+ */
+ private double[] subtractBackground(double[] sample, double[] background) {
+ int length = Math.min(background.length, sample.length);
+ double[] result = new double[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = sample[i] - background[i];
+ }
+ return result;
+ }
+
+ /** 对数组每个元素四舍五入取整(原地修改) */
+ private void roundInPlace(double[] array) {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = Math.round(array[i]);
+ }
+ }
+
+ /**
+ * 判断光谱是否饱和(200 波数之后是否存在>=65535 的值)
+ *
+ * @param spectrumRaw 原始光谱数据
+ * @return true 表示已饱和
+ */
+ private boolean isSpectrumSaturated(double[] spectrumRaw) {
+ if (startPxIndexForWaveNumber <= 0) {
+ return false;
+ }
+ for (int i = startPxIndexForWaveNumber; i < spectrumRaw.length; i++) {
+ if (spectrumRaw[i] >= SATURATION_THRESHOLD) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // ==================== 安全调用包装(带超时保护) ====================
+
+ /**
+ * 超时后放弃当前 executor 并重建
+ * 因为 bulkTransfer 是 JNI native 调用,Thread.interrupt() 无法中断它,
+ * 被阻塞的线程会永远持有 synchronized 锁。
+ * 唯一办法是抛弃 executor 创建新的,让后续操作在新线程中执行。
+ */
+ private synchronized void abandonExecutor(String reason) {
+ Log.w(TAG, reason + ":放弃当前 executor 并重建");
+ try {
+ usbExecutor.shutdownNow();
+ } catch (Exception ignored) {}
+ usbExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * 带超时保护的设备操作包装器
+ *
+ * @param result Flutter 回调
+ * @param action 设备操作
+ */
+ private void safeBooleanCall(Result result, BooleanAction action) {
+ safeBooleanCall(result, "", action);
+ }
+
+ /**
+ * 带超时保护的设备操作包装器(带操作名称日志)
+ *
+ * @param result Flutter 回调
+ * @param actionName 操作名称(用于日志)
+ * @param action 设备操作
+ */
+ private void safeBooleanCall(Result result, String actionName, BooleanAction action) {
+ final ExecutorService currentExecutor = usbExecutor;
+ Future future = currentExecutor.submit(() -> {
+ boolean success = action.execute();
+ if (!actionName.isEmpty()) {
+ Log.i(TAG, actionName + " 执行结果:" + success);
+ }
+ return success;
+ });
+
+ try {
+ boolean success = future.get(USB_CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ result.success(success);
+ } catch (TimeoutException e) {
+ abandonExecutor(actionName + " 超时");
+ result.success(false);
+ } catch (Exception e) {
+ Log.e(TAG, (actionName.isEmpty() ? "safeBooleanCall" : actionName) + " 执行失败:" + e.getMessage(), e);
+ result.success(false);
+ }
+ }
+
+ /**
+ * 通用 double 结果的安全调用包装,异常时返回空数组
+ *
+ * @param result Flutter 回调
+ * @param action 设备操作
+ */
+ private void safeDoubleCall(Result result, DoubleAction action) {
+ try {
+ result.success(action.execute());
+ } catch (Exception e) {
+ result.success(new double[]{});
+ }
+ }
+
+ /**
+ * 带超时保护的 double 数组结果调用包装器
+ *
+ * @param result Flutter 回调
+ * @param actionName 操作名称(用于日志)
+ * @param action 设备操作
+ */
+ private void safeDoubleArrayCall(Result result, String actionName, DoubleArrayAction action) {
+ final ExecutorService currentExecutor = usbExecutor;
+ Future future = currentExecutor.submit(() -> {
+ double[] arr = action.execute();
+ if (arr == null) return new double[]{};
+ return arr;
+ });
+
+ try {
+ result.success(future.get(USB_CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (TimeoutException e) {
+ abandonExecutor(actionName + " 超时");
+ result.success(new double[]{});
+ } catch (Exception e) {
+ Log.e(TAG, actionName + " 执行失败:" + e.getMessage(), e);
+ result.success(new double[]{});
+ }
+ }
+
+ /**
+ * 执行一个带超时的操作(任意返回类型)
+ * 超时后放弃 executor 并重建,确保后续调用不会被阻塞线程影响
+ */
+ private void runWithTimeout(Result result, String actionName, Callable action, int timeoutMs, T fallback) {
+ final ExecutorService currentExecutor = usbExecutor;
+ Future future = currentExecutor.submit(action);
+
+ try {
+ T value = future.get(timeoutMs, TimeUnit.MILLISECONDS);
+ result.success(value);
+ } catch (TimeoutException e) {
+ abandonExecutor(actionName + " 超时");
+ result.success(fallback);
+ } catch (Exception e) {
+ Log.e(TAG, actionName + " 执行失败:" + e.getMessage(), e);
+ result.success(fallback);
+ }
+ }
+
+ /** 返回 boolean 的设备操作接口 */
+ @FunctionalInterface
+ private interface BooleanAction {
+ boolean execute();
+ }
+
+ /** 返回 double 的设备操作接口 */
+ @FunctionalInterface
+ private interface DoubleAction {
+ double execute();
+ }
+
+ /** 返回 double[] 的设备操作接口 */
+ @FunctionalInterface
+ private interface DoubleArrayAction {
+ double[] execute();
+ }
+
+ // ==================== 参数提取工具方法 ====================
+
+ /** 从 MethodCall 参数中提取 double 值 */
+ private double getDoubleArg(MethodCall call, String key) {
+ Map arguments = call.arguments();
+ return ((Number) arguments.get(key)).doubleValue();
+ }
+
+ /** 从 MethodCall 参数中提取 int 值 */
+ private int getIntArg(MethodCall call, String key) {
+ Map arguments = call.arguments();
+ return ((Number) arguments.get(key)).intValue();
+ }
+
+ /**
+ * 从 MethodCall 参数中提取 double 数组,兼容 ArrayList 和原生数组
+ *
+ * @param call MethodCall 对象
+ * @param key 参数名
+ * @return double 数组
+ */
+ private double[] getDoubleArrayArg(MethodCall call, String key) {
+ Map arguments = call.arguments();
+ return toDoubleArray(arguments.get(key));
+ }
+
+ // ==================== 类型转换工具方法 ====================
+
+ /**
+ * 将 Object 转换为 double 数组,兼容 ArrayList 和原生数组两种类型
+ *
+ * @param obj 待转换对象
+ * @return double 数组
+ * @throws IllegalArgumentException 类型不支持或包含非 Number 元素时抛出
+ */
+ private double[] toDoubleArray(Object obj) {
+ if (obj instanceof ArrayList) {
+ ArrayList> list = (ArrayList>) obj;
+ double[] result = new double[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ result[i] = ((Number) list.get(i)).doubleValue();
+ }
+ return result;
+ } else if (obj.getClass().isArray()) {
+ int length = java.lang.reflect.Array.getLength(obj);
+ double[] result = new double[length];
+ for (int i = 0; i < length; i++) {
+ result[i] = ((Number) java.lang.reflect.Array.get(obj, i)).doubleValue();
+ }
+ return result;
+ }
+ throw new IllegalArgumentException("Provided object is neither an ArrayList nor an array");
+ }
+
+ /** 将 ArrayList转换为 double 原生数组 */
+ private double[] arrayListToDoubleArray(ArrayList list) {
+ double[] result = new double[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ result[i] = list.get(i);
+ }
+ return result;
+ }
+
+ // ==================== 加密与激活码 ====================
+
+ /**
+ * 计算字符串的 MD5 哈希值
+ *
+ * @param input 输入字符串
+ * @return MD5 十六进制字符串,异常时返回 null
+ */
+ public static String computeMD5(String input) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] digest = md.digest(input.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b & 0xFF));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * 根据设备 SN 码获取算法激活码
+ *
+ * @param sn 设备序列号
+ * @return 对应的激活码,未注册的 SN 返回空字符串
+ */
+ public String getActivationCode(String sn) {
+ String[] config = DEVICE_CONFIG_MAP.getOrDefault(sn, new String[]{"", ""});
+ return config[0];
+ }
+
+ /**
+ * 根据设备 SN 码获取激光校正系数
+ *
+ * @param sn 设备序列号
+ * @return 对应的激光校正系数(16 位字符串),未注册的 SN 返回空字符串
+ */
+ public String getLaserPowerCoefficient(String sn) {
+ String[] config = DEVICE_CONFIG_MAP.getOrDefault(sn, new String[]{"", ""});
+ return config[1];
+ }
+}
diff --git a/android/src/main/java/com/example/terra/utils/ArrayUtils.java b/android/src/main/java/com/example/terra/utils/ArrayUtils.java
new file mode 100644
index 0000000..cdb9711
--- /dev/null
+++ b/android/src/main/java/com/example/terra/utils/ArrayUtils.java
@@ -0,0 +1,217 @@
+package com.example.terra.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public final class ArrayUtils {
+
+ public static final double[] InterpolX = {200.0, 202.0, 204.0, 206.0, 208.0, 210.0, 212.0, 214.0, 216.0, 218.0, 220.0, 222.0, 224.0, 226.0, 228.0, 230.0, 232.0, 234.0, 236.0, 238.0, 240.0, 242.0, 244.0, 246.0, 248.0, 250.0, 252.0, 254.0, 256.0, 258.0, 260.0, 262.0, 264.0, 266.0, 268.0, 270.0, 272.0, 274.0, 276.0, 278.0, 280.0, 282.0, 284.0, 286.0, 288.0, 290.0, 292.0, 294.0, 296.0, 298.0, 300.0, 302.0, 304.0, 306.0, 308.0, 310.0, 312.0, 314.0, 316.0, 318.0, 320.0, 322.0, 324.0, 326.0, 328.0, 330.0, 332.0, 334.0, 336.0, 338.0, 340.0, 342.0, 344.0, 346.0, 348.0, 350.0, 352.0, 354.0, 356.0, 358.0, 360.0, 362.0, 364.0, 366.0, 368.0, 370.0, 372.0, 374.0, 376.0, 378.0, 380.0, 382.0, 384.0, 386.0, 388.0, 390.0, 392.0, 394.0, 396.0, 398.0, 400.0, 402.0, 404.0, 406.0, 408.0, 410.0, 412.0, 414.0, 416.0, 418.0, 420.0, 422.0, 424.0, 426.0, 428.0, 430.0, 432.0, 434.0, 436.0, 438.0, 440.0, 442.0, 444.0, 446.0, 448.0, 450.0, 452.0, 454.0, 456.0, 458.0, 460.0, 462.0, 464.0, 466.0, 468.0, 470.0, 472.0, 474.0, 476.0, 478.0, 480.0, 482.0, 484.0, 486.0, 488.0, 490.0, 492.0, 494.0, 496.0, 498.0, 500.0, 502.0, 504.0, 506.0, 508.0, 510.0, 512.0, 514.0, 516.0, 518.0, 520.0, 522.0, 524.0, 526.0, 528.0, 530.0, 532.0, 534.0, 536.0, 538.0, 540.0, 542.0, 544.0, 546.0, 548.0, 550.0, 552.0, 554.0, 556.0, 558.0, 560.0, 562.0, 564.0, 566.0, 568.0, 570.0, 572.0, 574.0, 576.0, 578.0, 580.0, 582.0, 584.0, 586.0, 588.0, 590.0, 592.0, 594.0, 596.0, 598.0, 600.0, 602.0, 604.0, 606.0, 608.0, 610.0, 612.0, 614.0, 616.0, 618.0, 620.0, 622.0, 624.0, 626.0, 628.0, 630.0, 632.0, 634.0, 636.0, 638.0, 640.0, 642.0, 644.0, 646.0, 648.0, 650.0, 652.0, 654.0, 656.0, 658.0, 660.0, 662.0, 664.0, 666.0, 668.0, 670.0, 672.0, 674.0, 676.0, 678.0, 680.0, 682.0, 684.0, 686.0, 688.0, 690.0, 692.0, 694.0, 696.0, 698.0, 700.0, 702.0, 704.0, 706.0, 708.0, 710.0, 712.0, 714.0, 716.0, 718.0, 720.0, 722.0, 724.0, 726.0, 728.0, 730.0, 732.0, 734.0, 736.0, 738.0, 740.0, 742.0, 744.0, 746.0, 748.0, 750.0, 752.0, 754.0, 756.0, 758.0, 760.0, 762.0, 764.0, 766.0, 768.0, 770.0, 772.0, 774.0, 776.0, 778.0, 780.0, 782.0, 784.0, 786.0, 788.0, 790.0, 792.0, 794.0, 796.0, 798.0, 800.0, 802.0, 804.0, 806.0, 808.0, 810.0, 812.0, 814.0, 816.0, 818.0, 820.0, 822.0, 824.0, 826.0, 828.0, 830.0, 832.0, 834.0, 836.0, 838.0, 840.0, 842.0, 844.0, 846.0, 848.0, 850.0, 852.0, 854.0, 856.0, 858.0, 860.0, 862.0, 864.0, 866.0, 868.0, 870.0, 872.0, 874.0, 876.0, 878.0, 880.0, 882.0, 884.0, 886.0, 888.0, 890.0, 892.0, 894.0, 896.0, 898.0, 900.0, 902.0, 904.0, 906.0, 908.0, 910.0, 912.0, 914.0, 916.0, 918.0, 920.0, 922.0, 924.0, 926.0, 928.0, 930.0, 932.0, 934.0, 936.0, 938.0, 940.0, 942.0, 944.0, 946.0, 948.0, 950.0, 952.0, 954.0, 956.0, 958.0, 960.0, 962.0, 964.0, 966.0, 968.0, 970.0, 972.0, 974.0, 976.0, 978.0, 980.0, 982.0, 984.0, 986.0, 988.0, 990.0, 992.0, 994.0, 996.0, 998.0, 1000.0, 1002.0, 1004.0, 1006.0, 1008.0, 1010.0, 1012.0, 1014.0, 1016.0, 1018.0, 1020.0, 1022.0, 1024.0, 1026.0, 1028.0, 1030.0, 1032.0, 1034.0, 1036.0, 1038.0, 1040.0, 1042.0, 1044.0, 1046.0, 1048.0, 1050.0, 1052.0, 1054.0, 1056.0, 1058.0, 1060.0, 1062.0, 1064.0, 1066.0, 1068.0, 1070.0, 1072.0, 1074.0, 1076.0, 1078.0, 1080.0, 1082.0, 1084.0, 1086.0, 1088.0, 1090.0, 1092.0, 1094.0, 1096.0, 1098.0, 1100.0, 1102.0, 1104.0, 1106.0, 1108.0, 1110.0, 1112.0, 1114.0, 1116.0, 1118.0, 1120.0, 1122.0, 1124.0, 1126.0, 1128.0, 1130.0, 1132.0, 1134.0, 1136.0, 1138.0, 1140.0, 1142.0, 1144.0, 1146.0, 1148.0, 1150.0, 1152.0, 1154.0, 1156.0, 1158.0, 1160.0, 1162.0, 1164.0, 1166.0, 1168.0, 1170.0, 1172.0, 1174.0, 1176.0, 1178.0, 1180.0, 1182.0, 1184.0, 1186.0, 1188.0, 1190.0, 1192.0, 1194.0, 1196.0, 1198.0, 1200.0, 1202.0, 1204.0, 1206.0, 1208.0, 1210.0, 1212.0, 1214.0, 1216.0, 1218.0, 1220.0, 1222.0, 1224.0, 1226.0, 1228.0, 1230.0, 1232.0, 1234.0, 1236.0, 1238.0, 1240.0, 1242.0, 1244.0, 1246.0, 1248.0, 1250.0, 1252.0, 1254.0, 1256.0, 1258.0, 1260.0, 1262.0, 1264.0, 1266.0, 1268.0, 1270.0, 1272.0, 1274.0, 1276.0, 1278.0, 1280.0, 1282.0, 1284.0, 1286.0, 1288.0, 1290.0, 1292.0, 1294.0, 1296.0, 1298.0, 1300.0, 1302.0, 1304.0, 1306.0, 1308.0, 1310.0, 1312.0, 1314.0, 1316.0, 1318.0, 1320.0, 1322.0, 1324.0, 1326.0, 1328.0, 1330.0, 1332.0, 1334.0, 1336.0, 1338.0, 1340.0, 1342.0, 1344.0, 1346.0, 1348.0, 1350.0, 1352.0, 1354.0, 1356.0, 1358.0, 1360.0, 1362.0, 1364.0, 1366.0, 1368.0, 1370.0, 1372.0, 1374.0, 1376.0, 1378.0, 1380.0, 1382.0, 1384.0, 1386.0, 1388.0, 1390.0, 1392.0, 1394.0, 1396.0, 1398.0, 1400.0, 1402.0, 1404.0, 1406.0, 1408.0, 1410.0, 1412.0, 1414.0, 1416.0, 1418.0, 1420.0, 1422.0, 1424.0, 1426.0, 1428.0, 1430.0, 1432.0, 1434.0, 1436.0, 1438.0, 1440.0, 1442.0, 1444.0, 1446.0, 1448.0, 1450.0, 1452.0, 1454.0, 1456.0, 1458.0, 1460.0, 1462.0, 1464.0, 1466.0, 1468.0, 1470.0, 1472.0, 1474.0, 1476.0, 1478.0, 1480.0, 1482.0, 1484.0, 1486.0, 1488.0, 1490.0, 1492.0, 1494.0, 1496.0, 1498.0, 1500.0, 1502.0, 1504.0, 1506.0, 1508.0, 1510.0, 1512.0, 1514.0, 1516.0, 1518.0, 1520.0, 1522.0, 1524.0, 1526.0, 1528.0, 1530.0, 1532.0, 1534.0, 1536.0, 1538.0, 1540.0, 1542.0, 1544.0, 1546.0, 1548.0, 1550.0, 1552.0, 1554.0, 1556.0, 1558.0, 1560.0, 1562.0, 1564.0, 1566.0, 1568.0, 1570.0, 1572.0, 1574.0, 1576.0, 1578.0, 1580.0, 1582.0, 1584.0, 1586.0, 1588.0, 1590.0, 1592.0, 1594.0, 1596.0, 1598.0, 1600.0, 1602.0, 1604.0, 1606.0, 1608.0, 1610.0, 1612.0, 1614.0, 1616.0, 1618.0, 1620.0, 1622.0, 1624.0, 1626.0, 1628.0, 1630.0, 1632.0, 1634.0, 1636.0, 1638.0, 1640.0, 1642.0, 1644.0, 1646.0, 1648.0, 1650.0, 1652.0, 1654.0, 1656.0, 1658.0, 1660.0, 1662.0, 1664.0, 1666.0, 1668.0, 1670.0, 1672.0, 1674.0, 1676.0, 1678.0, 1680.0, 1682.0, 1684.0, 1686.0, 1688.0, 1690.0, 1692.0, 1694.0, 1696.0, 1698.0, 1700.0, 1702.0, 1704.0, 1706.0, 1708.0, 1710.0, 1712.0, 1714.0, 1716.0, 1718.0, 1720.0, 1722.0, 1724.0, 1726.0, 1728.0, 1730.0, 1732.0, 1734.0, 1736.0, 1738.0, 1740.0, 1742.0, 1744.0, 1746.0, 1748.0, 1750.0, 1752.0, 1754.0, 1756.0, 1758.0, 1760.0, 1762.0, 1764.0, 1766.0, 1768.0, 1770.0, 1772.0, 1774.0, 1776.0, 1778.0, 1780.0, 1782.0, 1784.0, 1786.0, 1788.0, 1790.0, 1792.0, 1794.0, 1796.0, 1798.0, 1800.0, 1802.0, 1804.0, 1806.0, 1808.0, 1810.0, 1812.0, 1814.0, 1816.0, 1818.0, 1820.0, 1822.0, 1824.0, 1826.0, 1828.0, 1830.0, 1832.0, 1834.0, 1836.0, 1838.0, 1840.0, 1842.0, 1844.0, 1846.0, 1848.0, 1850.0, 1852.0, 1854.0, 1856.0, 1858.0, 1860.0, 1862.0, 1864.0, 1866.0, 1868.0, 1870.0, 1872.0, 1874.0, 1876.0, 1878.0, 1880.0, 1882.0, 1884.0, 1886.0, 1888.0, 1890.0, 1892.0, 1894.0, 1896.0, 1898.0, 1900.0, 1902.0, 1904.0, 1906.0, 1908.0, 1910.0, 1912.0, 1914.0, 1916.0, 1918.0, 1920.0, 1922.0, 1924.0, 1926.0, 1928.0, 1930.0, 1932.0, 1934.0, 1936.0, 1938.0, 1940.0, 1942.0, 1944.0, 1946.0, 1948.0, 1950.0, 1952.0, 1954.0, 1956.0, 1958.0, 1960.0, 1962.0, 1964.0, 1966.0, 1968.0, 1970.0, 1972.0, 1974.0, 1976.0, 1978.0, 1980.0, 1982.0, 1984.0, 1986.0, 1988.0, 1990.0, 1992.0, 1994.0, 1996.0, 1998.0, 2000.0, 2002.0, 2004.0, 2006.0, 2008.0, 2010.0, 2012.0, 2014.0, 2016.0, 2018.0, 2020.0, 2022.0, 2024.0, 2026.0, 2028.0, 2030.0, 2032.0, 2034.0, 2036.0, 2038.0, 2040.0, 2042.0, 2044.0, 2046.0, 2048.0, 2050.0, 2052.0, 2054.0, 2056.0, 2058.0, 2060.0, 2062.0, 2064.0, 2066.0, 2068.0, 2070.0, 2072.0, 2074.0, 2076.0, 2078.0, 2080.0, 2082.0, 2084.0, 2086.0, 2088.0, 2090.0, 2092.0, 2094.0, 2096.0, 2098.0, 2100.0, 2102.0, 2104.0, 2106.0, 2108.0, 2110.0, 2112.0, 2114.0, 2116.0, 2118.0, 2120.0, 2122.0, 2124.0, 2126.0, 2128.0, 2130.0, 2132.0, 2134.0, 2136.0, 2138.0, 2140.0, 2142.0, 2144.0, 2146.0, 2148.0, 2150.0, 2152.0, 2154.0, 2156.0, 2158.0, 2160.0, 2162.0, 2164.0, 2166.0, 2168.0, 2170.0, 2172.0, 2174.0, 2176.0, 2178.0, 2180.0, 2182.0, 2184.0, 2186.0, 2188.0, 2190.0, 2192.0, 2194.0, 2196.0, 2198.0, 2200.0, 2202.0, 2204.0, 2206.0, 2208.0, 2210.0, 2212.0, 2214.0, 2216.0, 2218.0, 2220.0, 2222.0, 2224.0, 2226.0, 2228.0, 2230.0, 2232.0, 2234.0, 2236.0, 2238.0, 2240.0, 2242.0, 2244.0, 2246.0, 2248.0, 2250.0, 2252.0, 2254.0, 2256.0, 2258.0, 2260.0, 2262.0, 2264.0, 2266.0, 2268.0, 2270.0, 2272.0, 2274.0, 2276.0, 2278.0, 2280.0, 2282.0, 2284.0, 2286.0, 2288.0, 2290.0, 2292.0, 2294.0, 2296.0, 2298.0, 2300.0, 2302.0, 2304.0, 2306.0, 2308.0, 2310.0, 2312.0, 2314.0, 2316.0, 2318.0, 2320.0, 2322.0, 2324.0, 2326.0, 2328.0, 2330.0, 2332.0, 2334.0, 2336.0, 2338.0, 2340.0, 2342.0, 2344.0, 2346.0, 2348.0, 2350.0, 2352.0, 2354.0, 2356.0, 2358.0, 2360.0, 2362.0, 2364.0, 2366.0, 2368.0, 2370.0, 2372.0, 2374.0, 2376.0, 2378.0, 2380.0, 2382.0, 2384.0, 2386.0, 2388.0, 2390.0, 2392.0, 2394.0, 2396.0, 2398.0, 2400.0, 2402.0, 2404.0, 2406.0, 2408.0, 2410.0, 2412.0, 2414.0, 2416.0, 2418.0, 2420.0, 2422.0, 2424.0, 2426.0, 2428.0, 2430.0, 2432.0, 2434.0, 2436.0, 2438.0, 2440.0, 2442.0, 2444.0, 2446.0, 2448.0, 2450.0, 2452.0, 2454.0, 2456.0, 2458.0, 2460.0, 2462.0, 2464.0, 2466.0, 2468.0, 2470.0, 2472.0, 2474.0, 2476.0, 2478.0, 2480.0, 2482.0, 2484.0, 2486.0, 2488.0, 2490.0, 2492.0, 2494.0, 2496.0, 2498.0, 2500.0, 2502.0, 2504.0, 2506.0, 2508.0, 2510.0, 2512.0, 2514.0, 2516.0, 2518.0, 2520.0, 2522.0, 2524.0, 2526.0, 2528.0, 2530.0, 2532.0, 2534.0, 2536.0, 2538.0, 2540.0, 2542.0, 2544.0, 2546.0, 2548.0, 2550.0, 2552.0, 2554.0, 2556.0, 2558.0, 2560.0, 2562.0, 2564.0, 2566.0, 2568.0, 2570.0, 2572.0, 2574.0, 2576.0, 2578.0, 2580.0, 2582.0, 2584.0, 2586.0, 2588.0, 2590.0, 2592.0, 2594.0, 2596.0, 2598.0, 2600.0, 2602.0, 2604.0, 2606.0, 2608.0, 2610.0, 2612.0, 2614.0, 2616.0, 2618.0, 2620.0, 2622.0, 2624.0, 2626.0, 2628.0, 2630.0, 2632.0, 2634.0, 2636.0, 2638.0, 2640.0, 2642.0, 2644.0, 2646.0, 2648.0, 2650.0, 2652.0, 2654.0, 2656.0, 2658.0, 2660.0, 2662.0, 2664.0, 2666.0, 2668.0, 2670.0, 2672.0, 2674.0, 2676.0, 2678.0, 2680.0, 2682.0, 2684.0, 2686.0, 2688.0, 2690.0, 2692.0, 2694.0, 2696.0, 2698.0, 2700.0, 2702.0, 2704.0, 2706.0, 2708.0, 2710.0, 2712.0, 2714.0, 2716.0, 2718.0, 2720.0, 2722.0, 2724.0, 2726.0, 2728.0, 2730.0, 2732.0, 2734.0, 2736.0, 2738.0, 2740.0, 2742.0, 2744.0, 2746.0, 2748.0, 2750.0, 2752.0, 2754.0, 2756.0, 2758.0, 2760.0, 2762.0, 2764.0, 2766.0, 2768.0, 2770.0, 2772.0, 2774.0, 2776.0, 2778.0, 2780.0, 2782.0, 2784.0, 2786.0, 2788.0, 2790.0, 2792.0, 2794.0, 2796.0, 2798.0, 2800.0, 2802.0, 2804.0, 2806.0, 2808.0, 2810.0, 2812.0, 2814.0, 2816.0, 2818.0, 2820.0, 2822.0, 2824.0, 2826.0, 2828.0, 2830.0, 2832.0, 2834.0, 2836.0, 2838.0, 2840.0, 2842.0, 2844.0, 2846.0, 2848.0, 2850.0, 2852.0, 2854.0, 2856.0, 2858.0, 2860.0, 2862.0, 2864.0, 2866.0, 2868.0, 2870.0, 2872.0, 2874.0, 2876.0, 2878.0, 2880.0, 2882.0, 2884.0, 2886.0, 2888.0, 2890.0, 2892.0, 2894.0, 2896.0, 2898.0, 2900.0, 2902.0, 2904.0, 2906.0, 2908.0, 2910.0, 2912.0, 2914.0, 2916.0, 2918.0, 2920.0, 2922.0, 2924.0, 2926.0, 2928.0, 2930.0, 2932.0, 2934.0, 2936.0, 2938.0, 2940.0, 2942.0, 2944.0, 2946.0, 2948.0, 2950.0, 2952.0, 2954.0, 2956.0, 2958.0, 2960.0, 2962.0, 2964.0, 2966.0, 2968.0, 2970.0, 2972.0, 2974.0, 2976.0, 2978.0, 2980.0, 2982.0, 2984.0, 2986.0, 2988.0, 2990.0, 2992.0, 2994.0, 2996.0, 2998.0, 3000.0, 3002.0, 3004.0, 3006.0, 3008.0, 3010.0, 3012.0, 3014.0, 3016.0, 3018.0, 3020.0, 3022.0, 3024.0, 3026.0, 3028.0, 3030.0, 3032.0, 3034.0, 3036.0, 3038.0, 3040.0, 3042.0, 3044.0, 3046.0, 3048.0, 3050.0, 3052.0, 3054.0, 3056.0, 3058.0, 3060.0, 3062.0, 3064.0, 3066.0, 3068.0, 3070.0, 3072.0, 3074.0, 3076.0, 3078.0, 3080.0, 3082.0, 3084.0, 3086.0, 3088.0, 3090.0, 3092.0, 3094.0, 3096.0, 3098.0, 3100.0, 3102.0, 3104.0, 3106.0, 3108.0, 3110.0, 3112.0, 3114.0, 3116.0, 3118.0, 3120.0, 3122.0, 3124.0, 3126.0, 3128.0, 3130.0, 3132.0, 3134.0, 3136.0, 3138.0, 3140.0, 3142.0, 3144.0, 3146.0, 3148.0, 3150.0, 3152.0, 3154.0, 3156.0, 3158.0, 3160.0, 3162.0, 3164.0, 3166.0, 3168.0, 3170.0, 3172.0, 3174.0, 3176.0, 3178.0, 3180.0, 3182.0, 3184.0, 3186.0, 3188.0, 3190.0, 3192.0, 3194.0, 3196.0, 3198.0, 3200.0};
+
+ public static String arrayList2String(ArrayList> list) {
+ return list.toString();
+ }
+
+ public static String doubleArray2String(double[] temp) {
+ return Arrays.toString(temp);
+ }
+
+ public static double[] string2DoubleArray(String str) {
+ if (str == null || "".equals(str)) {
+ return null;
+ }
+ str = str.substring(str.indexOf("[") + 1, str.lastIndexOf("]") == -1 ? 0 : str.lastIndexOf("]"));
+ if (str.equals("")) {
+ return new double[0];
+ }
+ String[] arrayStr = str.split(",");
+ double[] arrayDouble = new double[arrayStr.length];
+ for (int i = 0; i < arrayStr.length; i++) {
+ arrayDouble[i] = Double.parseDouble(arrayStr[i].trim());
+ }
+ return arrayDouble;
+ }
+
+ public static double[] ArrayList2doubleArray(ArrayList list) {
+ if (list == null) {
+ return null;
+ }
+ double[] arrayDouble = new double[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ arrayDouble[i] = list.get(i);
+ }
+ return arrayDouble;
+ }
+
+ public static int[] ArrayList2intArray(ArrayList list) {
+ if (list == null) {
+ return null;
+ }
+ int[] arrayInt = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ arrayInt[i] = list.get(i);
+ }
+ return arrayInt;
+ }
+
+ public static ArrayList doubleArray2ArrayList(double[] temp) {
+ ArrayList list = new ArrayList<>();
+ for (double d : temp) {
+ list.add(d);
+ }
+ return list;
+ }
+
+ public static double[] intArray2doubleArray(int[] temp) {
+ if (temp == null) {
+ return null;
+ }
+ double[] arrayDouble = new double[temp.length];
+ for (int i = 0; i < temp.length; i++) {
+ arrayDouble[i] = temp[i];
+ }
+ return arrayDouble;
+ }
+
+ public static int[] doubleArray2intArray(double[] temp) {
+ if (temp == null) {
+ return null;
+ }
+ int[] arrayInt = new int[temp.length];
+ for (int i = 0; i < temp.length; i++) {
+ arrayInt[i] = (int) Math.round(temp[i]);
+ }
+ return arrayInt;
+ }
+
+ public static int[] string2intArray(String str) {
+ if (str == null || "".equals(str)) {
+ return null;
+ }
+ str = str.substring(str.indexOf("[") + 1, str.lastIndexOf("]") == -1 ? 0 : str.lastIndexOf("]"));
+ if (str.equals("")) {
+ return new int[0];
+ }
+ String[] arrayStr = str.split(",");
+ int[] arrayDouble = new int[arrayStr.length];
+ for (int i = 0; i < arrayStr.length; i++) {
+ arrayDouble[i] = Integer.parseInt(arrayStr[i].trim());
+ }
+ return arrayDouble;
+ }
+
+ public static String intArray2String(int[] temp) {
+ return Arrays.toString(temp);
+ }
+
+ public static int[] ArrayList2intArray(List list) {
+ if (list == null) {
+ return null;
+ }
+ int[] arrayInt = new int[list.size()];
+ for (int i = 0; i < list.size(); i++) {
+ arrayInt[i] = list.get(i);
+ }
+ return arrayInt;
+ }
+
+ /**
+ * 求数组平均值
+ *
+ * @param arrs 数组
+ * @return 平均值
+ */
+ public static double aveForArray(double[] arrs) {
+ if(arrs == null){
+ return 0;
+ }
+ int length = arrs.length;
+ double sum = 0;
+ for (double arr : arrs) {
+ sum += arr;
+ }
+ return sum / length;
+ }
+
+ public static double getMax(double[] arrs) {
+ if(arrs == null){
+ return 0;
+ }
+ double max = 0;
+ for (double arr : arrs) {
+ max = Math.max(max,arr);
+ }
+ return max;
+ }
+
+ public static String bytesToHexString(byte[] src) {
+ StringBuilder stringBuilder = new StringBuilder();
+ if (src == null || src.length <= 0) {
+ return null;
+ }
+ for (byte b : src) {
+ int v = b & 0xFF;
+ String hv = Integer.toHexString(v);
+ if (hv.length() < 2) {
+ stringBuilder.append(0);
+ }
+ stringBuilder.append(hv);
+ }
+ return stringBuilder.toString();
+ }
+
+ public static byte[] int2bytes(int num, int length) {
+ if (length < 1 || length > 4) {
+ length = 4;
+ }
+ byte[] result = new byte[length];
+ if (length == 1) {
+ result[0] = (byte) (num & 0xff);
+ } else if (length == 2) {
+ result[0] = (byte) ((num >> 8) & 0xff);
+ result[1] = (byte) (num & 0xff);
+ } else if (length == 3) {
+ result[0] = (byte) ((num >> 16) & 0xff);
+ result[1] = (byte) ((num >> 8) & 0xff);
+ result[2] = (byte) (num & 0xff);
+ } else {
+ result[0] = (byte) ((num >> 24) & 0xff);
+ result[1] = (byte) ((num >> 16) & 0xff);
+ result[2] = (byte) ((num >> 8) & 0xff);
+ result[3] = (byte) (num & 0xff);
+ }
+ return result;
+ }
+
+ public static int bytes2int(byte[] bytes) {
+ if (bytes == null || bytes.length == 0) {
+ return 0;
+ }
+ int result = 0;
+ if (bytes.length == 1) {
+ result = (bytes[0] & 0xff);
+ } else if (bytes.length == 2) {
+ int a = (bytes[0] & 0xff) << 8;
+ int b = (bytes[1] & 0xff);
+ result = a | b;
+ } else if (bytes.length == 3) {
+ int a = (bytes[0] & 0xff) << 16;
+ int b = (bytes[1] & 0xff) << 8;
+ int c = (bytes[2] & 0xff);
+ result = a | b | c;
+ } else {
+ int a = (bytes[0] & 0xff) << 24;
+ int b = (bytes[1] & 0xff) << 16;
+ int c = (bytes[2] & 0xff) << 8;
+ int d = (bytes[3] & 0xff);
+ result = a | b | c | d;
+ }
+ return result;
+ }
+
+ public static double toFixed(double data, int n){
+ double order = Math.pow(10, n);
+ return Math.round(data * order) / order;
+ }
+
+}
diff --git a/android/src/test/java/com/example/terra/TerraPluginTest.java b/android/src/test/java/com/example/terra/TerraPluginTest.java
new file mode 100644
index 0000000..46254bd
--- /dev/null
+++ b/android/src/test/java/com/example/terra/TerraPluginTest.java
@@ -0,0 +1,29 @@
+package com.example.terra;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import org.junit.Test;
+
+/**
+ * This demonstrates a simple unit test of the Java portion of this plugin's implementation.
+ *
+ * Once you have built the plugin's example app, you can run these tests from the command
+ * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or
+ * you can run them directly from IDEs that support JUnit such as Android Studio.
+ */
+
+public class TerraPluginTest {
+ @Test
+ public void onMethodCall_getPlatformVersion_returnsExpectedValue() {
+ TerraPlugin plugin = new TerraPlugin();
+
+ final MethodCall call = new MethodCall("getPlatformVersion", null);
+ MethodChannel.Result mockResult = mock(MethodChannel.Result.class);
+ plugin.onMethodCall(call, mockResult);
+
+ verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE);
+ }
+}
diff --git a/docs/sdk_apis.md b/docs/sdk_apis.md
new file mode 100644
index 0000000..b9c4208
--- /dev/null
+++ b/docs/sdk_apis.md
@@ -0,0 +1,593 @@
+# Terra SDK 接口文档
+
+
+## Terra SDK
+
+Terra 是设备的名称,包含了光谱仪、激光器、拉曼相关接口。
+
+## 接口列表
+
+| 类名称 | 描述 |
+|--------|------|
+| DeviceWrapper | 设备管理器 |
+| Device | 设备 |
+
+## 调用示例
+
+**step1: 获取 USB 设备**
+```java
+List allDevices = DeviceWrapper.openAllDevices(mContext);
+```
+
+**step2: 设备初始化**,初始化完成后通过回调方式通知客户端
+```java
+device.setCallBack(new Device.CallBack() {
+ @Override
+ public void onDeviceStatue(boolean state) {
+ Log.d(TAG, "state = " + state );
+ String sn = "";
+ if(state){
+ sn = device.getSerialNumber();
+ }
+ //...
+ }
+});
+```
+
+**step3: 调用设备**
+```java
+double[] spectrum = device.getSpectrum();
+```
+
+## DeviceWrapper
+
+获取或关闭所有设备
+
+### Methods
+
+#### openAllDevices
+```java
+/**
+ * 打开所有设备
+ *
+ * @param context Application context
+ * @return 挂在的所有设备
+ */
+public static List openAllDevices(Context context);
+```
+
+#### closeAllDevices
+```java
+/**
+ * 关闭所有设备
+ */
+public static void closeAllDevices();
+```
+
+#### openDebug
+```java
+/**
+ * 开启日志输出
+ */
+public static void openDebug();
+```
+
+#### checkUsbPermission
+```java
+/**
+ * 检测是否有 USB 权限
+ */
+public static void checkUsbPermission(Context context, PermissionCallBack permissionCallBack) {
+ UsbConnect.checkUsbPermission(context,permissionCallBack);
+}
+```
+
+#### PermissionCallBack
+```java
+/**
+ * USB 权限状态回调
+ */
+public interface PermissionCallBack{
+ /**
+ * USB 权限状态
+ * @param deviceIndex 设备索引
+ * @param state true:有权限
+ */
+ public void onPermissionStatue(int deviceIndex, boolean state);
+}
+```
+## Device
+
+Device 是封装了设备提供的接口
+
+### Methods
+
+`CallBack` `setCallBack` `close` `getIndex` `isConnect` `getSerialNumber` `setIntegrationTime` `setAverageTimes` `setTriggerMode` `getSpectrum` `getResetSpectrum` `getWavelength` `getWaveNumber` `setCCDTECOn` `setCCDTECOff` `setCCDTECTemperature` `setLampEnable` `setLampDelayTime` `setLampWidth` `setLampInterval` `getLampDelayTime` `getLampWidth` `getLampInterval` `setExcitedWaveLength` `setLaserPower` `setLaserOn` `setLaserOff` `setLaserPowerOn` `setLaserPowerOff` `setTECOn` `setTECOff` `setMainBoardOn` `setMainBoardOff` `isMainBoardOpen` `isTECOpen` `waveletSmooth` `removeFluorescence` `lineInterpolation` `findPeaks` `getSlit` `getBadPoints` `getYJCorrectCoefficient` `getYAxisCorrectCoefficient` `getCorrectForDetectorNonlinear` `getWavelengthCalibrationCoefficients` `getLaserPowerCorrectCoefficient` `getFpgaVersion` `setYJCorrectCoefficient` `setLaserPowerCorrectCoefficient`
+
+#### CallBack
+```java
+/**
+ * 设备状态回调接口
+ */
+public interface CallBack{
+ /**
+ * 设备状态回调
+ * @param state 设备状态
+ * true:连接成功
+ * false:连接失败
+ */
+ void onDeviceStatue(boolean state);
+}
+```
+
+#### setCallBack
+```java
+/**
+ * 设置回调函数
+ * @param callBack 回调
+ */
+public void setCallBack(CallBack callBack);
+```
+
+#### close
+```java
+/**
+ * 关闭设备
+ */
+public void close();
+```
+
+#### getIndex
+```java
+/**
+ * 设备编号
+ * @return 编号
+ */
+public int getIndex();
+```
+
+#### isConnect
+```java
+/**
+ * 设备是否连接成功
+ */
+public boolean isConnect();
+```
+#### getSerialNumber
+```java
+/**
+ * 获取设备序列号
+ * @return 设备序列号
+ */
+public String getSerialNumber();
+```
+
+#### setIntegrationTime
+```java
+/**
+ * 设置设备积分时间
+ * @param integrationTime 积分时间
+ * @return 是否设置成功
+ */
+public boolean setIntegrationTime(double integrationTime);
+```
+
+#### setAverageTimes
+```java
+/**
+ * 设置平均次数
+ * @param averageTimes 平均次数
+ */
+public void setAverageTimes(int averageTimes);
+```
+
+#### setTriggerMode
+```java
+/**
+ * 设置触发模式
+ * @param triggerMode 触发模式
+ * 0: Free Running
+ * 1: Hardware Level
+ * 2: Synchronous
+ * 3: Hardware Edge
+ * 4: Edge And Free
+ * @return 是否设置成功
+ */
+public boolean setTriggerMode(int triggerMode);
+```
+
+#### getSpectrum
+```java
+/**
+ * 获取光谱(读取当前光谱,非平均过后的光谱)
+ * @return 光谱数据
+ */
+public double[] getSpectrum();
+```
+
+#### getResetSpectrum
+```java
+/**
+ * 获取光谱(读取当前光谱,受用户积分时间)
+ * @return 光谱数据
+ */
+public double[] getResetSpectrum();
+```
+
+#### getWavelength
+```java
+/**
+ * 获取波长
+ * @return 波长数据
+ */
+public double[] getWavelength();
+```
+
+#### getWaveNumber
+```java
+/**
+ * 获取波数
+ * @return 波数数据
+ */
+public double[] getWaveNumber();
+```
+
+#### setCCDTECOn
+```java
+/**
+ * 开 CCD 制冷
+ * @return 是否关闭成功
+ */
+public boolean setCCDTECOn();
+```
+#### setCCDTECOff
+```java
+/**
+ * 关 CCD 制冷
+ * @return 是否关闭成功
+ */
+public boolean setCCDTECOff();
+```
+
+#### setCCDTECTemperature
+```java
+/**
+ * 设置制冷的目标温度 (-20~40)
+ * @return 是否设置成功
+ */
+public boolean setCCDTECTemperature(int temperature);
+```
+
+#### getCCDTECState
+```java
+/**
+ * 获取探测器制冷状态,返回 16 个字节:
+ * 第一个字节是 TEC 当前温度符号位(0:零上,1:零下),单位
+ * 第二个字节是 TEC 当前温度整数位
+ * 第三个字节是 TEC 当前温度小数位
+ * 第四个字节是 PID 参数 P 高八位
+ * 第五个字节是 PID 参数 P 低八位
+ * 第六个字节是 PID 参数 I
+ * 第七个字节是 PID 参数 D
+ * 第八个字节是 TEC 电流符号位(0:正电流,1:负电流),单位 mA
+ * 第九个字节是 TEC 电流高八位
+ * 第十个字节是 TEC 电流低八位
+ * 第十一个字节是报警是否位(0 表示没有,1 表示报警,2 表示 TEC 没有)
+ * 第十二个字节 TEC 目标温度符号位(0:零上,1:零下),单位
+ * 第十三个字节 TEC 目标温度整数位
+ * 第十四个字节 LD 电流高八位(目前是 PD 管放大器的电压)
+ * 第十五个字节 LD 电流低八位
+ * 第十六个字节上电状态(0 表示没上电,1 表示上电)
+ * @return 当前状态信息
+ */
+public byte[] getCCDTECState();
+```
+
+#### setLampEnable
+```java
+/**
+ * Lamp 控制
+ * @param enable 控制 true:开,false
+ * @return 是否设置成功
+ */
+public boolean setLampEnable(boolean enable);
+```
+
+#### setLampDelayTime
+```java
+/**
+ * 设置灯泡的延迟时间(微秒)
+ * @param delayTime 延迟时间
+ * @return 是否设置成功
+ */
+public boolean setLampDelayTime(double delayTime);
+```
+
+#### setLampWidth
+```java
+/**
+ * 设置灯泡脉宽(微秒)
+ * @param width 脉宽
+ * @return 是否设置成功
+ */
+public boolean setLampWidth(double width);
+```
+
+#### setLampInterval
+```java
+/**
+ * 设置灯泡的周期(微秒)
+ * @param interval 周期
+ * @return 是否设置成功
+ */
+public boolean setLampInterval(double interval);
+```
+
+#### getLampDelayTime
+```java
+/**
+ * 获取延迟时间(微秒)
+ * @return 延迟时间
+ */
+public double getLampDelayTime();
+```
+#### getLampWidth
+```java
+/**
+ * 获取灯泡脉宽(微秒)
+ * @return 灯泡脉宽
+ */
+public double getLampWidth();
+```
+
+#### getLampInterval
+```java
+/**
+ * 获取灯泡的周期(微秒)
+ * @return 灯泡周期
+ */
+public double getLampInterval();
+```
+
+#### setExcitedWaveLength
+```java
+/**
+ * 设置激发波长
+ * @param excitedWaveLength 激发波长
+ * @return 是否设置成功
+ */
+public boolean setExcitedWaveLength(int excitedWaveLength);
+```
+
+#### setLaserPower
+```java
+/**
+ * 设置激光功率
+ * @param power 激光功率
+ * @return 是否设置成功
+ */
+public boolean setLaserPower(int power);
+```
+
+#### setLaserOn
+```java
+/**
+ * 开激光
+ * @return 是否设置成功
+ */
+public boolean setLaserOn();
+```
+
+#### setLaserOff
+```java
+/**
+ * 关激光
+ * @return 是否设置成功
+ */
+public boolean setLaserOff();
+```
+
+#### setLaserPowerOn
+```java
+/**
+ * 开激光器电源
+ * @return 是否设置成功
+ */
+public boolean setLaserPowerOn();
+```
+
+#### setLaserPowerOff
+```java
+/**
+ * 关激光器电源
+ * @return 是否设置成功
+ */
+public boolean setLaserPowerOff();
+```
+
+#### setTECOn
+```java
+/**
+ * 开 TEC
+ * @return 是否设置成功
+ */
+public boolean setTECOn();
+```
+
+#### setTECOff
+```java
+/**
+ * 关 TEC
+ * @return 是否设置成功
+ */
+public boolean setTECOff();
+```
+#### setMainBoardOn
+```java
+/**
+ * 开主板
+ * @return 是否设置成功
+ */
+public double setMainBoardOn();
+```
+
+#### setMainBoardOff
+```java
+/**
+ * 关主板
+ * @return 是否设置成功
+ */
+public double setMainBoardOff();
+```
+
+#### isMainBoardOpen
+```java
+/**
+ * 主板是否开成功,在最后一次操作时判断
+ * @return 是否开成功
+ */
+public boolean isMainBoardOpen();
+```
+
+#### isTECOpen
+```java
+/**
+ * 获取 TEC 状态,true:已开
+ * @return TEC 状态
+ */
+public boolean isTECOpen();
+```
+
+#### waveletSmooth
+```java
+/**
+ * 小波平滑
+ * @param spectrum 光谱
+ * @return 平滑后的光谱
+ */
+public double[] waveletSmooth(double[] spectrum);
+```
+
+#### removeFluorescence
+```java
+/**
+ * 去荧光
+ * @param spectrum 光谱
+ * @return 去荧光后的光谱
+ */
+public double[] removeFluorescence(double[] spectrum, int side);
+```
+
+#### lineInterpolation
+```java
+/**
+ * 线性插值
+ * @param xDataRaw 原 X 轴数据
+ * @param yDataRaw 原 Y 轴数据
+ * @param xData 插值后的 X 轴数据
+ * @return yData 插值后的 Y 轴数据
+ */
+public double[] lineInterpolation(double[] xDataRaw, double[] yDataRaw, double[] xData);
+```
+
+#### findPeaks
+```java
+/**
+ * 寻峰
+ * @param spectrum 光谱
+ * @param minIndicesBetweenPeaks 最小峰间距(值为 2,实际可能需要 5)
+ * @param baseline 基线
+ * @return 返回找到的峰的位置
+ */
+public static int[] findPeaks(double[] spectrum, int minIndicesBetweenPeaks, double baseline);
+```
+
+#### getSlit
+```java
+/**
+ * 获取狭缝
+ * @return 狭缝大小
+ */
+public String getSlit();
+```
+
+#### getBadPoints
+```java
+/**
+ * 获取坏点
+ * @return 坏点
+ */
+public int[] getBadPoints();
+```
+#### getYJCorrectCoefficient
+```java
+/**
+ * 获取波长校准系数
+ * @return 波长校准系数
+ */
+public double[] getYJCorrectCoefficient();
+```
+
+#### getYAxisCorrectCoefficient
+```java
+/**
+ * 获取 Y 轴校准系数
+ * @return Y 轴系数
+ */
+public double[] getYAxisCorrectCoefficient();
+```
+
+#### getCorrectForDetectorNonlinear
+```java
+/**
+ * 获取探测器非线性校正系数
+ * @return 探测器非线性校正系数
+ */
+public double[] getCorrectForDetectorNonlinear();
+```
+
+#### getWavelengthCalibrationCoefficients
+```java
+/**
+ * 获取波长校准系数
+ * @return 波长校准系数
+ */
+public double[] getWavelengthCalibrationCoefficients();
+```
+
+#### getLaserPowerCorrectCoefficient
+```java
+/**
+ * 获取功率校准系数
+ * @return 功率校准系数
+ */
+public double[] getLaserPowerCorrectCoefficient();
+```
+
+#### getFpgaVersion
+```java
+/**
+ * 获取 FPGA 版本
+ * @return FPGA 版本
+ */
+public double[] getFpgaVersion();
+```
+
+#### setYJCorrectCoefficient
+```java
+/**
+ * 设置波长校准系数
+ * @param coffes 波长校准系数
+ * @return 是否设置成功
+ */
+public boolean setYJCorrectCoefficient(double[] coffes);
+```
+
+#### setLaserPowerCorrectCoefficient
+```java
+/**
+ * 设置功率校准系数
+ * @param calibrationCoefficient 功率校准系数(格式:16 位字符串,4 个数据,每个数据 4 位,如 0070030008001400)
+ */
+public void setLaserPowerCorrectCoefficient(String calibrationCoefficient);
+```
\ No newline at end of file
diff --git a/example/.gitignore b/example/.gitignore
new file mode 100644
index 0000000..29a3a50
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1,43 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 0000000..ada019d
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,16 @@
+# terra_example
+
+Demonstrates how to use the terra plugin.
+
+## Getting Started
+
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+For help getting started with Flutter development, view the
+[online documentation](https://docs.flutter.dev/), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/example/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/example/android/.gitignore b/example/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/example/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
new file mode 100644
index 0000000..738b159
--- /dev/null
+++ b/example/android/app/build.gradle
@@ -0,0 +1,58 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file("local.properties")
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader("UTF-8") { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
+if (flutterVersionCode == null) {
+ flutterVersionCode = "1"
+}
+
+def flutterVersionName = localProperties.getProperty("flutter.versionName")
+if (flutterVersionName == null) {
+ flutterVersionName = "1.0"
+}
+
+android {
+ namespace = "com.example.terra_example"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.example.terra_example"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+ minSdk = 29
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutterVersionCode.toInteger()
+ versionName = flutterVersionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..319eb46
--- /dev/null
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/java/com/example/terra_example/MainActivity.java b/example/android/app/src/main/java/com/example/terra_example/MainActivity.java
new file mode 100644
index 0000000..194b7dd
--- /dev/null
+++ b/example/android/app/src/main/java/com/example/terra_example/MainActivity.java
@@ -0,0 +1,6 @@
+package com.example.terra_example;
+
+import io.flutter.embedding.android.FlutterActivity;
+
+public class MainActivity extends FlutterActivity {
+}
diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
new file mode 100644
index 0000000..d2ffbff
--- /dev/null
+++ b/example/android/build.gradle
@@ -0,0 +1,18 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = "../build"
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
new file mode 100644
index 0000000..3b5b324
--- /dev/null
+++ b/example/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e1ca574
--- /dev/null
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
new file mode 100644
index 0000000..536165d
--- /dev/null
+++ b/example/android/settings.gradle
@@ -0,0 +1,25 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }()
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "7.3.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.7.10" apply false
+}
+
+include ":app"
diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart
new file mode 100644
index 0000000..4b46600
--- /dev/null
+++ b/example/integration_test/plugin_integration_test.dart
@@ -0,0 +1,25 @@
+// This is a basic Flutter integration test.
+//
+// Since integration tests run in a full Flutter application, they can interact
+// with the host side of a plugin implementation, unlike Dart unit tests.
+//
+// For more information about Flutter integration tests, please see
+// https://docs.flutter.dev/cookbook/testing/integration/introduction
+
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'package:terra/terra.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('getPlatformVersion test', (WidgetTester tester) async {
+ final Terra plugin = Terra();
+ final String? version = await plugin.getPlatformVersion();
+ // The version string depends on the host platform running the test, so
+ // just assert that some non-empty string is returned.
+ expect(version?.isNotEmpty, true);
+ });
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
new file mode 100644
index 0000000..56c0fe3
--- /dev/null
+++ b/example/lib/main.dart
@@ -0,0 +1,130 @@
+import 'package:flutter/material.dart';
+import 'dart:async';
+
+import 'package:flutter/services.dart';
+import 'package:terra/terra.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatefulWidget {
+ const MyApp({super.key});
+
+ @override
+ State createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ String _platformVersion = 'Unknown';
+ final _terraPlugin = Terra();
+
+ @override
+ void initState() {
+ super.initState();
+ initPlatformState();
+ }
+
+ // Platform messages are asynchronous, so we initialize in an async method.
+ Future initPlatformState() async {
+ String platformVersion;
+ // Platform messages may fail, so we use a try/catch PlatformException.
+ // We also handle the message potentially returning null.
+ try {
+ platformVersion =
+ await _terraPlugin.getPlatformVersion() ?? 'Unknown platform version';
+ } on PlatformException {
+ platformVersion = 'Failed to get platform version.';
+ }
+
+ // If the widget was removed from the tree while the asynchronous platform
+ // message was in flight, we want to discard the reply rather than calling
+ // setState to update our non-existent appearance.
+ if (!mounted) return;
+
+ setState(() {
+ _platformVersion = platformVersion;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ home: Scaffold(
+ appBar: AppBar(
+ title: const Text('Plugin example app'),
+ ),
+ body: Center(
+ child: Column(
+ children: [
+ Text('Running on: $_platformVersion\n'),
+ ElevatedButton(
+ onPressed: () async {
+ var deviceCount = await Terra.openAllDevices();
+ print("设备数量:$deviceCount");
+ },
+ child: const Text("连接设备")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var closeResult = await Terra.closeAllDevices();
+ print("关闭设备结果:$closeResult");
+ },
+ child: const Text("关闭所有设备")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var sn = await Terra.getDeviceSn();
+ print("设备SN:$sn");
+ },
+ child: const Text("获取设备SN")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var res = await Terra.setIntegrationTime(10.00);
+ print("设置积分时间结果:$res");
+ },
+ child: const Text("设置积分时间")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var res = await Terra.setAverageTimes(3);
+ print("设置平均次数结果:$res");
+ },
+ child: const Text("设置平均次数")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var res = await Terra.setTriggerMode(0);
+ print("设置触发模式:$res");
+ },
+ child: const Text("设置触发模式")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var res = await Terra.isConnect();
+ print("是否连接:$res");
+ },
+ child: const Text("是否连接")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var spectrum = await Terra.getRamanSpectrum();
+ print("光谱:$spectrum");
+ },
+ child: const Text("获取光谱(处理后的光谱)")
+ ),
+ ElevatedButton(
+ onPressed: () async {
+ var registerRes = await Terra.ramanMatchRegister();
+ print("注册结果:$registerRes");
+ },
+ child: const Text("算法注册")
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/pubspec.lock b/example/pubspec.lock
new file mode 100644
index 0000000..fc5a185
--- /dev/null
+++ b/example/pubspec.lock
@@ -0,0 +1,283 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.13.1"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.2"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.19.1"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.0.9"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.3.3"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "7.0.1"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_driver:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.2"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ fuchsia_remote_debug_protocol:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ integration_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "11.0.2"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.10"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.2"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.12.19"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.13.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.17.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.9.1"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.6"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.8"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.0.5"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.10.2"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ sync_http:
+ dependency: transitive
+ description:
+ name: sync_http
+ sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.3.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.2.2"
+ terra:
+ dependency: "direct main"
+ description:
+ path: ".."
+ relative: true
+ source: path
+ version: "0.0.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.7.10"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "046d3928e16fa4dc46e8350415661755ab759d9fc97fc21b5ab295f71e4f0499"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "15.1.0"
+ webdriver:
+ dependency: transitive
+ description:
+ name: webdriver
+ sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.0"
+sdks:
+ dart: ">=3.9.0 <4.0.0"
+ flutter: ">=3.18.0-18.0.pre.54"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
new file mode 100644
index 0000000..6c58e00
--- /dev/null
+++ b/example/pubspec.yaml
@@ -0,0 +1,85 @@
+name: terra_example
+description: "Demonstrates how to use the terra plugin."
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+environment:
+ sdk: '>=3.4.3 <4.0.0'
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+ terra:
+ # When depending on this package from a real application you should use:
+ # terra: ^x.y.z
+ # See https://dart.dev/tools/pub/dependencies#version-constraints
+ # The example app is bundled with the plugin so we use a path dependency on
+ # the parent directory to use the current plugin's version.
+ path: ../
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.6
+
+dev_dependencies:
+ integration_test:
+ sdk: flutter
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^3.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
+
+ # To add assets to your application, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart
new file mode 100644
index 0000000..7a6b7ad
--- /dev/null
+++ b/example/test/widget_test.dart
@@ -0,0 +1,27 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:terra_example/main.dart';
+
+void main() {
+ testWidgets('Verify Platform version', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that platform version is retrieved.
+ expect(
+ find.byWidgetPredicate(
+ (Widget widget) => widget is Text &&
+ widget.data!.startsWith('Running on:'),
+ ),
+ findsOneWidget,
+ );
+ });
+}
diff --git a/lib/terra.dart b/lib/terra.dart
new file mode 100644
index 0000000..37d2d11
--- /dev/null
+++ b/lib/terra.dart
@@ -0,0 +1,390 @@
+import 'package:flutter/services.dart';
+
+import 'terra_platform_interface.dart';
+
+class Terra {
+ Future getPlatformVersion() {
+ return TerraPlatform.instance.getPlatformVersion();
+ }
+
+ static const MethodChannel _channel = MethodChannel('terra');
+
+ // ==================== 设备管理 ====================
+
+ /// 打开所有设备
+ static Future openAllDevices() async {
+ var deviceCount = await _channel.invokeMethod("openAllDevices");
+ return deviceCount;
+ }
+
+ /// 关闭所有设备
+ static Future closeAllDevices() async {
+ return await _channel.invokeMethod("closeAllDevices");
+ }
+
+ /// 关闭当前设备
+ static Future closeDevice() async {
+ return await _channel.invokeMethod("close");
+ }
+
+ /// 获取设备SN
+ static Future getDeviceSn() async {
+ return await _channel.invokeMethod("getDeviceSn");
+ }
+
+ /// 获取设备索引
+ static Future getDeviceIndex() async {
+ return await _channel.invokeMethod("getIndex");
+ }
+
+ /// 设备是否连接成功
+ static Future isConnect() async {
+ return await _channel.invokeMethod("isConnect");
+ }
+
+ /// 开启 SDK 调试日志
+ static Future openDebug() async {
+ return await _channel.invokeMethod("openDebug");
+ }
+
+ /// 检查 USB 权限
+ static Future