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