From 194033514fa1d4198f1e5f4522216e139066b0bc Mon Sep 17 00:00:00 2001 From: Developer <91611@user.local> Date: Mon, 18 May 2026 17:58:11 +0800 Subject: [PATCH] init --- .gitignore | 29 + .metadata | 30 + CHANGELOG.md | 3 + LICENSE | 1 + README.md | 15 + analysis_options.yaml | 4 + android/.gitignore | 9 + android/build.gradle | 60 ++ android/libs/RamanMatch.aar | Bin 0 -> 38509 bytes android/libs/terra.aar | Bin 0 -> 108081 bytes android/settings.gradle | 1 + android/src/main/AndroidManifest.xml | 3 + .../java/com/example/terra/TerraPlugin.java | 974 ++++++++++++++++++ .../com/example/terra/utils/ArrayUtils.java | 217 ++++ .../com/example/terra/TerraPluginTest.java | 29 + docs/sdk_apis.md | 593 +++++++++++ example/.gitignore | 43 + example/README.md | 16 + example/analysis_options.yaml | 28 + example/android/.gitignore | 13 + example/android/app/build.gradle | 58 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 + .../example/terra_example/MainActivity.java | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + example/android/build.gradle | 18 + example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + example/android/settings.gradle | 25 + .../plugin_integration_test.dart | 25 + example/lib/main.dart | 130 +++ example/pubspec.lock | 283 +++++ example/pubspec.yaml | 85 ++ example/test/widget_test.dart | 27 + lib/terra.dart | 390 +++++++ lib/terra_method_channel.dart | 17 + lib/terra_platform_interface.dart | 29 + pubspec.yaml | 70 ++ test/terra_method_channel_test.dart | 27 + test/terra_test.dart | 29 + 49 files changed, 3414 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/build.gradle create mode 100644 android/libs/RamanMatch.aar create mode 100644 android/libs/terra.aar create mode 100644 android/settings.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/java/com/example/terra/TerraPlugin.java create mode 100644 android/src/main/java/com/example/terra/utils/ArrayUtils.java create mode 100644 android/src/test/java/com/example/terra/TerraPluginTest.java create mode 100644 docs/sdk_apis.md create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/android/.gitignore create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/java/com/example/terra_example/MainActivity.java create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/integration_test/plugin_integration_test.dart create mode 100644 example/lib/main.dart create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml create mode 100644 example/test/widget_test.dart create mode 100644 lib/terra.dart create mode 100644 lib/terra_method_channel.dart create mode 100644 lib/terra_platform_interface.dart create mode 100644 pubspec.yaml create mode 100644 test/terra_method_channel_test.dart create mode 100644 test/terra_test.dart 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 0000000000000000000000000000000000000000..ce2ca90bda2b19d6b9aecedfe7dd1c738c624602 GIT binary patch literal 38509 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�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 r(F|((@ucHv6h<-tqzbH(y3ve literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 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> checkUsbPermission() async { + final result = await _channel.invokeMethod("checkUsbPermission"); + return Map.from(result as Map); + } + + // ==================== 参数配置 ==================== + + /// 设置积分时间 + static Future setIntegrationTime(double integrationTime) async { + Map args = {"integrationTime": integrationTime}; + return await _channel.invokeMethod("setIntegrationTime", args); + } + + /// 设置平均次数 + static Future setAverageTimes(int averageTimes) async { + Map args = {"averageTimes": averageTimes}; + return await _channel.invokeMethod("setAverageTimes", args); + } + + /// 设置触发模式 + /// 0: Free Running + /// 1: Hardware Level + /// 2: Synchronous + /// 3: Hardware Edge + /// 4: Edge And Free + static Future setTriggerMode(int triggerMode) async { + Map args = {"triggerMode": triggerMode}; + return await _channel.invokeMethod("setTriggerMode", args); + } + + // ==================== 光谱数据获取 ==================== + + /// 获取光谱(获取当前光谱,可能拿到缓存光谱) + static Future> getSpectrum() async { + return await _channel.invokeMethod("getSpectrum"); + } + + /// 获取光谱(获取光谱前会重新设置积分时间) + static Future> getResetSpectrum() async { + return await _channel.invokeMethod("getResetSpectrum"); + } + + /// 获取波长 + static Future> getWavelength() async { + return await _channel.invokeMethod("getWavelength"); + } + + /// 获取波数 + static Future> getWaveNumber() async { + return await _channel.invokeMethod("getWaveNumber"); + } + + /// 获取处理完的光谱 + static Future> getRamanSpectrum() async { + List spectrum = await _channel.invokeMethod("getRamanSpectrum"); + return spectrum; + } + + /// 获取插值 X 轴数据(200~3200,步长 2) + static Future> getXvalue() async { + return await _channel.invokeMethod("getXvalue"); + } + + // ==================== CCD 制冷控制 ==================== + + /// 开 CCD 制冷 + static Future setCCDTECOn() async { + return await _channel.invokeMethod("setCCDTECOn"); + } + + /// 关 CCD 制冷 + static Future setCCDTECOff() async { + return await _channel.invokeMethod("setCCDTECOff"); + } + + /// 设置 CCD 制冷目标温度(-20 ~ 40) + static Future setCCDTECTemperature(int temperature) async { + Map args = {"temperature": temperature}; + return await _channel.invokeMethod("setCCDTECTemperature", args); + } + + /// 获取 CCD 制冷状态(返回 16 字节状态数组) + static Future> getCCDTECState() async { + final result = await _channel.invokeMethod("getCCDTECState"); + return List.from(result as List); + } + + // ==================== 灯泡/脉冲控制 ==================== + + /// 设置灯泡延迟时间(微秒) + static Future setLampDelayTime(double delayTime) async { + return await _channel.invokeMethod("setLampDelayTime", {"delayTime": delayTime}); + } + + /// 设置灯泡脉宽(微秒) + static Future setLampWidth(double width) async { + return await _channel.invokeMethod("setLampWidth", {"width": width}); + } + + /// 设置灯泡周期(微秒) + static Future setLampInterval(double interval) async { + return await _channel.invokeMethod("setLampInterval", {"interval": interval}); + } + + /// 获取灯泡延迟时间(微秒) + static Future> getLampDelayTime() async { + final result = await _channel.invokeMethod("getLampDelayTime"); + return List.from(result as List); + } + + /// 获取灯泡脉宽(微秒) + static Future> getLampWidth() async { + final result = await _channel.invokeMethod("getLampWidth"); + return List.from(result as List); + } + + /// 获取灯泡周期(微秒) + static Future> getLampInterval() async { + final result = await _channel.invokeMethod("getLampInterval"); + return List.from(result as List); + } + + /// 开关灯泡控制 + static Future setLampEnable(bool enable) async { + return await _channel.invokeMethod("setLampEnable", {"enable": enable}); + } + + // ==================== 激光控制 ==================== + + /// 设置激发波长 + static Future setExcitedWaveLength(int excitedWaveLength) async { + return await _channel.invokeMethod("setExcitedWaveLength", {"excitedWaveLength": excitedWaveLength}); + } + + /// 设置激光功率(范围 0-2000) + static Future setLaserPower(double power) async { + if (power < 0 || power > 2000) { + print('[Terra] setLaserPower 参数超出范围 (0-2000): $power,拒绝执行'); + return false; + } + Map args = {"power": power}; + print('[Terra] setLaserPower 发送: power=$power'); + return await _channel.invokeMethod("setLaserPower", args) + .timeout(const Duration(seconds: 5), onTimeout: () { + print('[Terra] setLaserPower 超时,设备可能未正确响应'); + return false; + }); + } + + /// 开激光 + static Future setLaserOn() async { + return await _channel.invokeMethod("setLaserOn"); + } + + /// 关激光 + static Future setLaserOff() async { + return await _channel.invokeMethod("setLaserOff"); + } + + /// 开激光器电源 + static Future setLaserPowerOn() async { + return await _channel.invokeMethod("setLaserPowerOn"); + } + + /// 关激光器电源 + static Future setLaserPowerOff() async { + return await _channel.invokeMethod("setLaserPowerOff"); + } + + // ==================== 硬件开关 ==================== + + /// 开 TEC + static Future setTECOn() async { + return await _channel.invokeMethod("setTECOn"); + } + + /// 关 TEC + static Future setTECOff() async { + return await _channel.invokeMethod("setTECOff"); + } + + /// 开主板 + static Future setMainBoardOn() async { + return await _channel.invokeMethod("setMainBoardOn"); + } + + /// 关主板 + static Future setMainBoardOff() async { + return await _channel.invokeMethod("setMainBoardOff"); + } + + /// 主板是否已打开 + static Future isMainBoardOpen() async { + return await _channel.invokeMethod("isMainBoardOpen"); + } + + /// TEC 是否已打开 + static Future isTECOpen() async { + return await _channel.invokeMethod("isTECOpen"); + } + + // ==================== 光谱数据处理 ==================== + + /// 小波平滑 + static Future> waveletSmooth(List spectrum) async { + return await _channel.invokeMethod("waveletSmooth", {"spectrum": spectrum}); + } + + /// 消荧光(side 为消荧光因子,默认 15) + static Future> removeFluorescence(List spectrum, {int side = 15}) async { + return await _channel.invokeMethod("removeFluorescence", {"spectrum": spectrum, "side": side}); + } + + /// 线性插值 + static Future> lineInterpolation( + List xDataRaw, + List yDataRaw, + List xData, + ) async { + return await _channel.invokeMethod("lineInterpolation", { + "xDataRaw": xDataRaw, + "yDataRaw": yDataRaw, + "xData": xData, + }); + } + + /// 寻峰 + static Future> findPeaks( + List spectrum, + int minIndicesBetweenPeaks, + double baseline, + ) async { + Map args = { + "spectrum": spectrum, + "minIndicesBetweenPeaks": minIndicesBetweenPeaks, + "baseline": baseline, + }; + return await _channel.invokeMethod("findPeaks", args); + } + + // ==================== 校准系数 ==================== + + /// 获取激光校正系数 + static Future> getLaserPowerCorrectCoefficient() async { + return await _channel.invokeMethod("getLaserPowerCorrectCoefficient"); + } + + /// 设置激光校正系数 + /// calibrationCoefficient 格式:16 位字符串,4 个数据,每个数据 4 位 + /// 如 "0070030008001400" + static Future setLaserPowerCorrectCoefficient(String calibrationCoefficient) async { + Map args = {"calibrationCoefficient": calibrationCoefficient}; + return await _channel.invokeMethod("setLaserPowerCorrectCoefficient", args); + } + + /// 获取乙腈校准数据 + static Future> getYJCorrectCoefficient() async { + return await _channel.invokeMethod("getYJCorrectCoefficient"); + } + + /// 设置乙腈定标系数 + static Future setYJCorrectCoefficient(List coffes) async { + Map> args = {"coffes": coffes}; + return await _channel.invokeMethod("setYJCorrectCoefficient", args); + } + + /// 获取 Y 轴校准系数 + static Future> getYAxisCorrectCoefficient() async { + final result = await _channel.invokeMethod("getYAxisCorrectCoefficient"); + return List.from(result as List); + } + + /// 获取探测器非线性校正系数 + static Future> getCorrectForDetectorNonlinear() async { + final result = await _channel.invokeMethod("getCorrectForDetectorNonlinear"); + return List.from(result as List); + } + + /// 获取波长校准系数 + static Future> getWavelengthCalibrationCoefficients() async { + final result = await _channel.invokeMethod("getWavelengthCalibrationCoefficients"); + return List.from(result as List); + } + + /// 获取 FPGA 版本 + static Future> getFpgaVersion() async { + final result = await _channel.invokeMethod("getFpgaVersion"); + return List.from(result as List); + } + + /// 获取狭缝大小 + static Future getSlit() async { + return await _channel.invokeMethod("getSlit"); + } + + /// 获取坏点 + static Future> getBadPoints() async { + final result = await _channel.invokeMethod("getBadPoints"); + return List.from(result as List); + } + + // ==================== 算法匹配与校准 ==================== + + /// 算法注册 + static Future ramanMatchRegister() async { + return await _channel.invokeMethod("ramanMatchRegister"); + } + + /// 光谱匹配,返回相似度 + static Future ramanMatchCalcSimilarity( + List spectrum, + List libSpectrumList, + ) async { + Map> args = { + "spectrum": spectrum, + "libSpectrumList": libSpectrumList, + }; + return await _channel.invokeMethod("ramanMatchCalcSimilarity", args); + } + + /// 校准 + static Future> calibration( + List spectrum, + List YJCoeff, + ) async { + Map> args = { + "spectrum": spectrum, + "YJCoeff": YJCoeff, + }; + return await _channel.invokeMethod("calibration", args); + } + + // ==================== 其他 ==================== + + /// SN 码 MD5 加密 + static Future bytesToMD5(String sn) async { + Map args = {"sn": sn}; + return await _channel.invokeMethod("bytesToMD5", args); + } +} diff --git a/lib/terra_method_channel.dart b/lib/terra_method_channel.dart new file mode 100644 index 0000000..3815a6f --- /dev/null +++ b/lib/terra_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'terra_platform_interface.dart'; + +/// An implementation of [TerraPlatform] that uses method channels. +class MethodChannelTerra extends TerraPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('terra'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/lib/terra_platform_interface.dart b/lib/terra_platform_interface.dart new file mode 100644 index 0000000..fe3b01d --- /dev/null +++ b/lib/terra_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'terra_method_channel.dart'; + +abstract class TerraPlatform extends PlatformInterface { + /// Constructs a TerraPlatform. + TerraPlatform() : super(token: _token); + + static final Object _token = Object(); + + static TerraPlatform _instance = MethodChannelTerra(); + + /// The default instance of [TerraPlatform] to use. + /// + /// Defaults to [MethodChannelTerra]. + static TerraPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [TerraPlatform] when + /// they register themselves. + static set instance(TerraPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..2a338c0 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,70 @@ +name: terra +description: "A new Flutter project." +version: 0.0.1 +homepage: + +environment: + sdk: '>=3.4.3 <4.0.0' + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + 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: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.example.terra + pluginClass: TerraPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, 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 in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/terra_method_channel_test.dart b/test/terra_method_channel_test.dart new file mode 100644 index 0000000..464f110 --- /dev/null +++ b/test/terra_method_channel_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:terra/terra_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelTerra platform = MethodChannelTerra(); + const MethodChannel channel = MethodChannel('terra'); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/test/terra_test.dart b/test/terra_test.dart new file mode 100644 index 0000000..be5c489 --- /dev/null +++ b/test/terra_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:terra/terra.dart'; +import 'package:terra/terra_platform_interface.dart'; +import 'package:terra/terra_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockTerraPlatform + with MockPlatformInterfaceMixin + implements TerraPlatform { + + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final TerraPlatform initialPlatform = TerraPlatform.instance; + + test('$MethodChannelTerra is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + Terra terraPlugin = Terra(); + MockTerraPlatform fakePlatform = MockTerraPlatform(); + TerraPlatform.instance = fakePlatform; + + expect(await terraPlugin.getPlatformVersion(), '42'); + }); +}