commit da0b10a71471f202c5307380ae3a97af05176900
Author: 王梦远 <1063331231@qq.com>
Date: Mon Jul 28 21:12:26 2025 +0800
first commit
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8af972c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+/gradlew text eol=lf
+*.bat text eol=crlf
+*.jar binary
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2065bc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..f461217
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'java'
+ id 'org.springframework.boot' version '3.5.3'
+ id 'io.spring.dependency-management' version '1.1.7'
+}
+
+group = 'com.iflytop'
+version = '0.0.1-SNAPSHOT'
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+}
+
+repositories {
+ maven { url 'https://maven.aliyun.com/repository/public' }
+ maven { url 'https://maven.aliyun.com/repository/central' }
+ maven { url 'https://maven.aliyun.com/repository/spring' }
+}
+
+dependencies {
+ implementation 'org.springframework.boot:spring-boot-starter'// Spring Boot 核心依赖,包含自动配置、日志、Spring Context 等基础功能
+ implementation 'org.springframework.boot:spring-boot-starter-web'// 用于构建 REST API 的 Web 功能,包含 Spring MVC、Tomcat 等
+ implementation 'org.springframework.boot:spring-boot-starter-websocket:3.5.3' // WebSocket 支持
+ implementation 'org.springframework.statemachine:spring-statemachine-core:4.0.1'// Spring 状态机(有限状态机)核心包,用于处理状态转换逻辑
+ implementation 'org.springframework.boot:spring-boot-starter-validation:3.5.3' // Bean 校验支持(如 @Valid、@NotNull 等注解)
+ implementation 'org.springframework.boot:spring-boot-starter-aop'// AOP(面向切面编程)支持,用于实现日志、权限校验等横切逻辑
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'// OpenAPI 文档生成器,用于生成 Swagger UI 接口文档
+ implementation 'org.freemarker:freemarker:2.3.34'// 模板引擎 FreeMarker,可用于生成动态内容(如生成文件或 Web 页面)
+ implementation 'org.xerial:sqlite-jdbc:3.50.3.0'// SQLite JDBC 驱动,提供对 SQLite 数据库的连接支持
+ implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.5' // MyBatis Spring Boot 启动器,简化 MyBatis 配置
+ implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.12'// MyBatis Plus 启动器,对 MyBatis 的增强支持(简化 CRUD、分页等)
+ implementation 'com.baomidou:mybatis-plus-generator:3.5.12'// MyBatis Plus 代码生成器,用于自动生成实体类、Mapper、Service 等
+ implementation 'com.baomidou:mybatis-plus-jsqlparser:3.5.12'// MyBatis Plus SQL 解析器,MyBatis Plus 的内部依赖(用于条件构造器)
+ implementation 'ch.qos.logback:logback-core:1.5.16' // Logback 核心库,用于日志输出
+ implementation 'cn.hutool:hutool-all:5.8.39'// Hutool 工具包,提供各种常用工具类,如日期处理、IO、字符串等
+ implementation 'com.github.xiaoymin:knife4j-openapi3-jakarta-spring-boot-starter:4.5.0'// Knife4j Swagger 增强工具,提供更友好的 API 文档界面
+ implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' // Jakarta 标准注解包(如 @PostConstruct、@Resource 等)
+ implementation 'com.fazecast:jSerialComm:2.11.2'// 串口通信库 jSerialComm,用于与硬件设备通过串口通信
+ implementation 'com.opencsv:opencsv:5.11.2'// CSV 解析器 OpenCSV,用于读写 CSV 文件
+ implementation 'org.java-websocket:Java-WebSocket:1.6.0'
+ implementation fileTree(dir: 'lib', include: '*.jar')// 引入本地 lib 目录下的所有 jar 包
+
+ compileOnly 'org.projectlombok:lombok' // Lombok 提供简化 Java 开发的注解(如 @Getter @Setter 等),编译期依赖
+ annotationProcessor 'org.projectlombok:lombok'
+
+
+ testImplementation 'org.springframework.boot:spring-boot-starter-test'// 单元测试支持,包含 JUnit、Mockito 等常用测试库
+ testRuntimeOnly 'org.junit.platform:junit-platform-launcher'// JUnit 平台运行时,用于在测试时启动 JUnit Platform(例如在 IDE 或构建工具中运行测试)
+}
+
+bootJar {
+ launchScript {
+ properties 'spring.profiles.active': 'prod'
+ }
+}
+
+tasks.named('test') {
+ useJUnitPlatform()
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..1b33c55
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..04bedef
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://mirrors.aliyun.com/gradle/distributions/v8.14.3/gradle-8.14.3-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..23d15a9
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..db3a6ac
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/lib/modbus4j-3.1.0.jar b/lib/modbus4j-3.1.0.jar
new file mode 100644
index 0000000..ab89c85
Binary files /dev/null and b/lib/modbus4j-3.1.0.jar differ
diff --git a/lib/opencv-420.jar b/lib/opencv-420.jar
new file mode 100644
index 0000000..90f60bc
Binary files /dev/null and b/lib/opencv-420.jar differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..bdf8337
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'hand-acid'
diff --git a/src/main/java/com/iflytop/handacid/HandAcidApplication.java b/src/main/java/com/iflytop/handacid/HandAcidApplication.java
new file mode 100644
index 0000000..c42d596
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/HandAcidApplication.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+@SpringBootApplication
+public class HandAcidApplication {
+
+ public static void main(String[] args) {
+ SpringApplication app = new SpringApplication(HandAcidApplication.class);
+ ConfigurableApplicationContext ctx = app.run(args);
+ ConfigurableEnvironment env = ctx.getEnvironment();
+ String port = env.getProperty("local.server.port", env.getProperty("server.port", "8080"));
+ System.out.println("应用已启动");
+ System.out.println("访问地址 → http://localhost:" + port + "/");
+ System.out.println("文档地址 → http://localhost:" + port + "/doc.html");
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/command/control/StopAllMotorCommand.java b/src/main/java/com/iflytop/handacid/app/command/control/StopAllMotorCommand.java
new file mode 100644
index 0000000..57598fd
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/command/control/StopAllMotorCommand.java
@@ -0,0 +1,28 @@
+package com.iflytop.handacid.app.command.control;
+
+import com.iflytop.handacid.app.common.annotation.CommandMapping;
+import com.iflytop.handacid.app.core.command.BaseCommandHandler;
+import com.iflytop.handacid.app.model.dto.CommandDTO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * 停止所有电机
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+@CommandMapping("stop_all_motor")
+public class StopAllMotorCommand extends BaseCommandHandler {
+
+ @Override
+ public CompletableFuture handle(CommandDTO commandDTO) {
+
+ return runAsync(() -> {
+ });
+ }
+}
+
diff --git a/src/main/java/com/iflytop/handacid/app/common/annotation/CheckedRunnable.java b/src/main/java/com/iflytop/handacid/app/common/annotation/CheckedRunnable.java
new file mode 100644
index 0000000..98985de
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/annotation/CheckedRunnable.java
@@ -0,0 +1,6 @@
+package com.iflytop.handacid.app.common.annotation;
+
+@FunctionalInterface
+public interface CheckedRunnable {
+ void run() throws Exception;
+}
diff --git a/src/main/java/com/iflytop/handacid/app/common/annotation/CommandDebugMapping.java b/src/main/java/com/iflytop/handacid/app/common/annotation/CommandDebugMapping.java
new file mode 100644
index 0000000..a320d87
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/annotation/CommandDebugMapping.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.app.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface CommandDebugMapping {
+ String value();
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/common/annotation/CommandMapping.java b/src/main/java/com/iflytop/handacid/app/common/annotation/CommandMapping.java
new file mode 100644
index 0000000..f401bdb
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/annotation/CommandMapping.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.app.common.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface CommandMapping {
+ String value();
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/common/constant/CommandStatus.java b/src/main/java/com/iflytop/handacid/app/common/constant/CommandStatus.java
new file mode 100644
index 0000000..5d6d3d1
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/constant/CommandStatus.java
@@ -0,0 +1,47 @@
+package com.iflytop.handacid.app.common.constant;
+
+public class CommandStatus {
+ /**
+ * 普通消息反馈
+ */
+ public static final String SEND = "send";
+
+ /**
+ * 收到指令
+ */
+ public static final String RECEIVE = "receive";
+ /**
+ * 开始执行指令
+ */
+ public static final String START = "start";
+ /**
+ * 等待执行指令
+ */
+ public static final String WAIT = "wait";
+ /**
+ * 执行成功
+ */
+ public static final String SUCCESS = "success";
+ /**
+ * 执行失败
+ */
+ public static final String FAIL = "fail";
+ /**
+ * 指令处理完毕反馈
+ */
+ public static final String FINISH = "finish";
+
+
+ /**
+ * 设备指令 发送设备指令
+ */
+ public static final String DEVICE_SEND = "device_send";
+ /**
+ * 设备指令 收到设备指令反馈
+ */
+ public static final String DEVICE_RESULT = "device_result";
+ /**
+ * 设备指令 收到设备指令反馈 错误
+ */
+ public static final String DEVICE_ERROR = "device_error";
+}
diff --git a/src/main/java/com/iflytop/handacid/app/common/enums/ModuleStateCode.java b/src/main/java/com/iflytop/handacid/app/common/enums/ModuleStateCode.java
new file mode 100644
index 0000000..aba956b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/enums/ModuleStateCode.java
@@ -0,0 +1,12 @@
+
+package com.iflytop.handacid.app.common.enums;
+
+/**
+ * 多模块枚举类型
+ */
+public enum ModuleStateCode {
+ ADD,
+ PRE,
+ DRAIN,
+ IDLE,
+}
diff --git a/src/main/java/com/iflytop/handacid/app/common/enums/MultipleModuleCode.java b/src/main/java/com/iflytop/handacid/app/common/enums/MultipleModuleCode.java
new file mode 100644
index 0000000..93ae2c1
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/enums/MultipleModuleCode.java
@@ -0,0 +1,11 @@
+package com.iflytop.handacid.app.common.enums;
+
+/**
+ * 多模块枚举类型
+ */
+public enum MultipleModuleCode {
+ MODULE_1,
+ MODULE_2,
+ MODULE_3,
+ MODULE_4,
+}
diff --git a/src/main/java/com/iflytop/handacid/app/common/enums/UserRole.java b/src/main/java/com/iflytop/handacid/app/common/enums/UserRole.java
new file mode 100644
index 0000000..b9c0525
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/enums/UserRole.java
@@ -0,0 +1,7 @@
+package com.iflytop.handacid.app.common.enums;
+
+public enum UserRole {
+ ADMIN,
+ DEVELOPER,
+ USER;
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/common/handler/GlobalExceptionHandler.java b/src/main/java/com/iflytop/handacid/app/common/handler/GlobalExceptionHandler.java
new file mode 100644
index 0000000..de042ed
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/handler/GlobalExceptionHandler.java
@@ -0,0 +1,82 @@
+package com.iflytop.handacid.app.common.handler;
+
+import com.iflytop.handacid.common.exception.AppException;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.common.result.ResultCode;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+ public Result> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
+ String method = request.getMethod();
+ String url = request.getRequestURL().toString();
+ String allowed = ex.getSupportedHttpMethods() != null
+ ? ex.getSupportedHttpMethods().toString()
+ : "[]";
+ String msg = String.format(
+ "请求方法 '%s' 不支持 (URL: %s)。支持的方法有:%s",
+ method, url, allowed);
+ log.error(msg, ex);
+ return Result.failed(ResultCode.METHOD_NOT_ALLOWED.getCode(), msg);
+ }
+
+ // 1) JSON Body 校验失败
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public Result> handleBodyValid(MethodArgumentNotValidException ex) {
+ String msg = ex.getBindingResult().getFieldErrors().stream()
+ .map(f -> f.getField() + ": " + f.getDefaultMessage())
+ .collect(Collectors.joining("; "));
+ return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg);
+ }
+
+ // 2) 方法级参数校验失败(PathVariable/RequestParam)
+ @ExceptionHandler(ConstraintViolationException.class)
+ public Result> handleParamValid(ConstraintViolationException ex) {
+ String msg = ex.getConstraintViolations().stream()
+ .map(v -> {
+ String path = v.getPropertyPath().toString();
+ String field = path.substring(path.lastIndexOf('.') + 1);
+ return field + ": " + v.getMessage();
+ })
+ .collect(Collectors.joining("; "));
+ return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg);
+ }
+
+ // 3) 表单绑定失败
+ @ExceptionHandler(BindException.class)
+ public Result> handleBind(BindException ex) {
+ String msg = ex.getFieldErrors().stream()
+ .map(f -> f.getField() + ": " + f.getDefaultMessage())
+ .collect(Collectors.joining("; "));
+ return Result.failed(ResultCode.INVALID_PARAMETER.getCode(), msg);
+ }
+
+ @ExceptionHandler(Exception.class)
+ public Result> handleException(Exception ex, HttpServletRequest request) {
+ // 获取请求方法和请求路径
+ String method = request.getMethod();
+ String url = request.getRequestURL().toString();
+
+ if (ex instanceof AppException ae) {
+ log.warn("AppException at {} {}:", method, url, ae);
+ return Result.failed(ae.getResultCode());
+ }
+
+ // 在日志中打印出请求地址
+ log.error("Unhandled exception at {} {}:", method, url, ex);
+ return Result.failed(ResultCode.SYSTEM_ERROR.getCode(), ex.getMessage());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/common/handler/MyMetaObjectHandler.java b/src/main/java/com/iflytop/handacid/app/common/handler/MyMetaObjectHandler.java
new file mode 100644
index 0000000..d61cecc
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/handler/MyMetaObjectHandler.java
@@ -0,0 +1,36 @@
+package com.iflytop.handacid.app.common.handler;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * mybatis-plus 字段自动填充
+ */
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+ /**
+ * 新增填充创建时间
+ *
+ * @param metaObject 元数据
+ */
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
+ this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
+ }
+
+ /**
+ * 更新填充更新时间
+ *
+ * @param metaObject 元数据
+ */
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/common/utils/CommandUtil.java b/src/main/java/com/iflytop/handacid/app/common/utils/CommandUtil.java
new file mode 100644
index 0000000..4dc9e8d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/utils/CommandUtil.java
@@ -0,0 +1,23 @@
+package com.iflytop.handacid.app.common.utils;
+
+
+import com.iflytop.handacid.app.core.command.CommandFuture;
+
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class CommandUtil {
+
+ public static void wait(CommandFuture... futures) throws Exception {
+ wait(120L, futures);
+ }
+
+ public static void wait(Long timeout, CommandFuture... futures) throws Exception {
+ CompletableFuture>[] responseFutures = Arrays.stream(futures)
+ .map(CommandFuture::getResponseFuture)
+ .toArray(CompletableFuture[]::new);
+ CompletableFuture.allOf(responseFutures)
+ .get(timeout, TimeUnit.SECONDS);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/common/utils/LambdaUtil.java b/src/main/java/com/iflytop/handacid/app/common/utils/LambdaUtil.java
new file mode 100644
index 0000000..03ff029
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/common/utils/LambdaUtil.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.app.common.utils;
+
+
+import com.iflytop.handacid.app.common.annotation.CheckedRunnable;
+
+public class LambdaUtil {
+ public static Runnable unchecked(CheckedRunnable runnable) {
+ return () -> {
+ try {
+ runnable.run();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/config/AsyncConfig.java b/src/main/java/com/iflytop/handacid/app/config/AsyncConfig.java
new file mode 100644
index 0000000..2f7e4fe
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/config/AsyncConfig.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid.app.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+@Configuration
+public class AsyncConfig {
+ @Bean("commandTaskExecutor")
+ public ThreadPoolTaskExecutor taskExecutor() {
+ ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
+ exec.setCorePoolSize(10);
+ exec.setMaxPoolSize(20);
+ exec.setQueueCapacity(50);
+ exec.setThreadNamePrefix("cmd-exec-");
+ exec.setWaitForTasksToCompleteOnShutdown(true);
+ exec.setAwaitTerminationSeconds(30);
+ exec.initialize();
+ return exec;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/controller/AuthController.java b/src/main/java/com/iflytop/handacid/app/controller/AuthController.java
new file mode 100644
index 0000000..3f8ab7b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/AuthController.java
@@ -0,0 +1,56 @@
+package com.iflytop.handacid.app.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.iflytop.handacid.app.core.state.DeviceState;
+import com.iflytop.handacid.app.model.dto.LoginDTO;
+import com.iflytop.handacid.common.enums.EnableStatus;
+import com.iflytop.handacid.common.model.entity.User;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.common.result.ResultCode;
+import com.iflytop.handacid.common.service.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Objects;
+
+/**
+ * 认证控制
+ */
+@Tag(name = "\uD83D\uDD11认证")
+@RestController
+@RequestMapping("/api/auth")
+@RequiredArgsConstructor
+@Slf4j
+public class AuthController {
+ private final UserService userService;
+ private final DeviceState deviceState;
+
+ @Operation(summary = "账号密码登录")
+ @PostMapping("/login")
+ public Result login(@Valid @RequestBody LoginDTO loginDTO) {
+ User user = userService.getOne(new LambdaQueryWrapper<>(User.class).eq(User::getUsername, loginDTO.getUsername()));
+ if (user != null && !Objects.equals(user.getDeleted(), EnableStatus.ENABLE) && user.getPassword().equals(loginDTO.getPassword())) {
+ deviceState.setCurrentUser(user);
+ user.setPassword(null);
+ return Result.success(user);
+ }
+ return Result.failed(ResultCode.INVALID_CREDENTIALS);
+ }
+
+ @Operation(summary = "用户登出")
+ @PostMapping("/logout")
+ public Result logout() {
+ deviceState.setCurrentUser(null);
+ return Result.success();
+ }
+
+ @Operation(summary = "获取当前登录用户")
+ @GetMapping("/current")
+ public Result current() {
+ return Result.success(deviceState.getCurrentUser());
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/controller/CmdController.java b/src/main/java/com/iflytop/handacid/app/controller/CmdController.java
new file mode 100644
index 0000000..b860955
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/CmdController.java
@@ -0,0 +1,67 @@
+package com.iflytop.handacid.app.controller;
+
+import cn.hutool.json.JSONUtil;
+import com.iflytop.handacid.app.common.constant.CommandStatus;
+import com.iflytop.handacid.app.core.command.CommandHandler;
+import com.iflytop.handacid.app.core.command.CommandHandlerRegistry;
+import com.iflytop.handacid.app.model.dto.CommandDTO;
+import com.iflytop.handacid.app.websocket.server.DebugGenerator;
+import com.iflytop.handacid.app.websocket.server.WebSocketSender;
+import com.iflytop.handacid.common.result.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.CompletableFuture;
+
+@Tag(name = "前端业务指令")
+@RestController
+@RequestMapping("/api/cmd")
+@RequiredArgsConstructor
+@Slf4j
+public class CmdController {
+ private final CommandHandlerRegistry registry;
+ private final WebSocketSender webSocketService;
+
+ @Operation(summary = "前端统一调用一个接口")
+ @PostMapping
+ public Result> controlMethod(@Valid @RequestBody CommandDTO commandDTO){
+ String commandId = commandDTO.getCommandId();
+ String command = commandDTO.getCommand();
+ try {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到业务指令请求,开始处理"));
+ CommandHandler commandHandler = registry.getCommandHandler(command);
+ if (commandHandler == null) {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的业务指令"));
+ log.error("未找到对应的业务指令");
+ return Result.failed();
+ }
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "业务指令开始执行"));
+ log.info("业务指令开始执行");
+ CompletableFuture future = commandHandler.handle(commandDTO);
+ future.whenComplete((v, ex) -> {
+ if (ex != null) {
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行业务指令发生异常", ex.getMessage()));
+ log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), ex);
+ } else {
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "业务指令执行成功"));
+ log.info("业务指令执行成功");
+ }
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "业务指令执行结束"));
+ log.info("业务指令执行结束");
+ });
+ } catch (Exception e) {
+ log.error("执行业务指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), e);
+ return Result.failed(e.getMessage());
+ }
+ return Result.success();
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/controller/CmdDebugController.java b/src/main/java/com/iflytop/handacid/app/controller/CmdDebugController.java
new file mode 100644
index 0000000..ac12647
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/CmdDebugController.java
@@ -0,0 +1,67 @@
+package com.iflytop.handacid.app.controller;
+
+import cn.hutool.json.JSONUtil;
+import com.iflytop.handacid.app.common.constant.CommandStatus;
+import com.iflytop.handacid.app.core.command.CommandDebugHandlerRegistry;
+import com.iflytop.handacid.app.core.command.CommandHandler;
+import com.iflytop.handacid.app.model.dto.CommandDTO;
+import com.iflytop.handacid.app.websocket.server.DebugGenerator;
+import com.iflytop.handacid.app.websocket.server.WebSocketSender;
+import com.iflytop.handacid.common.result.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.CompletableFuture;
+
+@Tag(name = "前端调试指令")
+@RestController
+@RequestMapping("/api/debug/cmd")
+@RequiredArgsConstructor
+@Slf4j
+public class CmdDebugController {
+ private final CommandDebugHandlerRegistry registry;
+ private final WebSocketSender webSocketService;
+
+ @Operation(summary = "前端调试指令")
+ @PostMapping
+ public Result> controlMethod(@Valid @RequestBody CommandDTO commandDTO) {
+ String commandId = commandDTO.getCommandId();
+ String command = commandDTO.getCommand();
+ try {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.RECEIVE, "已收到调试指令请求,开始处理"));
+ CommandHandler commandHandler = registry.getCommandHandler(command);
+ if (commandHandler == null) {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandId, command, CommandStatus.DEVICE_ERROR, "未找到对应的调试指令"));
+ log.error("未找到对应的调试指令");
+ return Result.failed();
+ }
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.START, "调试指令开始执行"));
+ log.info("调试指令开始执行");
+ CompletableFuture future = commandHandler.handle(commandDTO);
+ future.whenComplete((v, ex) -> {
+ if (ex != null) {
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FAIL, "执行调试指令发生异常", ex.getMessage()));
+ log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), ex);
+ } else {
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.SUCCESS, "调试指令执行成功"));
+ log.info("调试指令执行成功");
+ }
+ webSocketService.pushCMDResponse(DebugGenerator.generateJson(commandId, command, CommandStatus.FINISH, "调试指令执行结束"));
+ log.info("调试指令执行结束");
+ });
+ } catch (Exception e) {
+ log.error("执行调试指令发生异常: {}", JSONUtil.toJsonStr(commandDTO), e);
+ return Result.failed(e.getMessage());
+ }
+ return Result.success();
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/controller/SolutionsController.java b/src/main/java/com/iflytop/handacid/app/controller/SolutionsController.java
new file mode 100644
index 0000000..d2b68a1
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/SolutionsController.java
@@ -0,0 +1,53 @@
+package com.iflytop.handacid.app.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.iflytop.handacid.common.base.BasePageQuery;
+import com.iflytop.handacid.common.model.entity.Solutions;
+import com.iflytop.handacid.common.result.PageResult;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.common.service.SolutionsService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+@Tag(name = "\uD83D\uDCA7溶液接口")
+@RestController
+@RequestMapping("/api/solutions")
+@RequiredArgsConstructor
+@Slf4j
+public class SolutionsController {
+
+ private final SolutionsService solutionsService;
+
+ @Operation(summary = "分页查询溶液")
+ @PostMapping("/list")
+ public PageResult list(@RequestBody BasePageQuery query) {
+ return PageResult.success(solutionsService.page(new Page<>(query.getPageNum(), query.getPageSize())));
+ }
+
+ @Operation(summary = "添加溶液")
+ @PostMapping("")
+ public Result add(@Valid @RequestBody Solutions solutions) {
+ return solutionsService.save(solutions) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "修改溶液")
+ @PutMapping("")
+ public Result update(@Valid @RequestBody Solutions solutions) {
+ return solutionsService.updateById(solutions) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "删除溶液")
+ @DeleteMapping("/{ids}")
+ public Result delete(@PathVariable String ids) {
+ boolean success = solutionsService.removeBatchByIds(
+ Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
+ );
+ return success ? Result.success() : Result.failed();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/controller/SystemConfigController.java b/src/main/java/com/iflytop/handacid/app/controller/SystemConfigController.java
new file mode 100644
index 0000000..c63d5f7
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/SystemConfigController.java
@@ -0,0 +1,53 @@
+package com.iflytop.handacid.app.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.iflytop.handacid.common.base.BasePageQuery;
+import com.iflytop.handacid.common.model.entity.SystemConfig;
+import com.iflytop.handacid.common.result.PageResult;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.common.service.SystemConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+@Tag(name = "\uD83D\uDEE0\uFE0F系统配置接口")
+@RestController
+@RequestMapping("/api/system-config")
+@RequiredArgsConstructor
+@Slf4j
+public class SystemConfigController {
+
+ private final SystemConfigService systemConfigService;
+
+ @Operation(summary = "分页查询系统配置")
+ @PostMapping("/list")
+ public PageResult list(@RequestBody BasePageQuery query) {
+ return PageResult.success(systemConfigService.page(new Page<>(query.getPageNum(), query.getPageSize())));
+ }
+
+ @Operation(summary = "添加配置项")
+ @PostMapping("")
+ public Result add(@Valid @RequestBody SystemConfig config) {
+ return systemConfigService.save(config) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "更新配置项")
+ @PutMapping("")
+ public Result update(@Valid @RequestBody SystemConfig config) {
+ return systemConfigService.updateById(config) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "删除配置项")
+ @DeleteMapping("/{ids}")
+ public Result delete(@PathVariable String ids) {
+ boolean success = systemConfigService.removeBatchByIds(
+ Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
+ );
+ return success ? Result.success() : Result.failed();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/controller/SystemController.java b/src/main/java/com/iflytop/handacid/app/controller/SystemController.java
new file mode 100644
index 0000000..863a67a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/SystemController.java
@@ -0,0 +1,45 @@
+package com.iflytop.handacid.app.controller;
+
+import com.iflytop.handacid.app.core.state.DeviceState;
+import com.iflytop.handacid.app.model.dto.TimeSetDTO;
+import com.iflytop.handacid.app.model.vo.TimeResponseVO;
+import com.iflytop.handacid.app.service.SystemService;
+import com.iflytop.handacid.common.result.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.Instant;
+
+@Tag(name = "⚙\uFE0F系统")
+@RestController
+@RequestMapping("/api/sys")
+@RequiredArgsConstructor
+@Slf4j
+public class SystemController {
+ private final SystemService systemService;
+ private final DeviceState deviceState;
+
+ @Operation(summary = "获取当前设备状态")
+ @GetMapping("/device-status")
+ public Result> getDeviceStatus() {
+ return Result.success(deviceState.toJSON());
+ }
+
+ @Operation(summary = "设置系统时间")
+ @PostMapping("/set-datetime")
+ public Result> setDatetime(@Valid @RequestBody TimeSetDTO timeSetDTO) {
+ systemService.setSystemTime(timeSetDTO.getEpochMilli());
+ return Result.success();
+ }
+
+ @Operation(summary = "获取当前系统时间")
+ @GetMapping("/get-datetime")
+ public Result getDatetime() {
+ return Result.success(new TimeResponseVO(Instant.now().toEpochMilli()));
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/controller/SystemLogController.java b/src/main/java/com/iflytop/handacid/app/controller/SystemLogController.java
new file mode 100644
index 0000000..aba0789
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/SystemLogController.java
@@ -0,0 +1,53 @@
+package com.iflytop.handacid.app.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.iflytop.handacid.common.base.BasePageQuery;
+import com.iflytop.handacid.common.model.entity.SystemLog;
+import com.iflytop.handacid.common.result.PageResult;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.common.service.SystemLogService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+@Tag(name = "\uD83D\uDCDD系统日志接口")
+@RestController
+@RequestMapping("/api/system-log")
+@RequiredArgsConstructor
+@Slf4j
+public class SystemLogController {
+
+ private final SystemLogService systemLogService;
+
+ @Operation(summary = "分页查询系统日志")
+ @PostMapping("/list")
+ public PageResult list(@RequestBody BasePageQuery query) {
+ return PageResult.success(systemLogService.page(new Page<>(query.getPageNum(), query.getPageSize())));
+ }
+
+ @Operation(summary = "添加日志")
+ @PostMapping("")
+ public Result add(@Valid @RequestBody SystemLog logEntry) {
+ return systemLogService.save(logEntry) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "更新日志")
+ @PutMapping("")
+ public Result update(@Valid @RequestBody SystemLog logEntry) {
+ return systemLogService.updateById(logEntry) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "删除日志")
+ @DeleteMapping("/{ids}")
+ public Result delete(@PathVariable String ids) {
+ boolean success = systemLogService.removeBatchByIds(
+ Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
+ );
+ return success ? Result.success() : Result.failed();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/controller/TestController.java b/src/main/java/com/iflytop/handacid/app/controller/TestController.java
new file mode 100644
index 0000000..01f1387
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/TestController.java
@@ -0,0 +1,45 @@
+package com.iflytop.handacid.app.controller;
+
+import com.iflytop.handacid.app.common.enums.MultipleModuleCode;
+import com.iflytop.handacid.app.core.state.DeviceState;
+import com.iflytop.handacid.app.service.TestService;
+import com.iflytop.handacid.common.result.Result;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 测试用
+ */
+@Tag(name = "⭐测试用")
+@RestController
+@RequestMapping("/api/test")
+@RequiredArgsConstructor
+@Slf4j
+public class TestController {
+ private final DeviceState deviceState;
+
+ @Operation(summary = "启动虚拟模式")
+ @PostMapping("/virtual")
+ public Result> changeVirtualMode() {
+ deviceState.setVirtual(true);
+ return Result.success();
+ }
+
+ @Operation(summary = "启动虚拟模式并且自检完毕")
+ @PostMapping("/virtual-finish")
+ public Result> selfTestFinish() {
+ deviceState.setVirtual(true);
+ return Result.success();
+ }
+
+ @Operation(summary = "设置模拟环境湿度")
+ @PostMapping("/set-humidity")
+ public Result> setHumidity(MultipleModuleCode heatModule, double humidity) {
+ return Result.success();
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/controller/UserController.java b/src/main/java/com/iflytop/handacid/app/controller/UserController.java
new file mode 100644
index 0000000..79137da
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/controller/UserController.java
@@ -0,0 +1,55 @@
+package com.iflytop.handacid.app.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.iflytop.handacid.common.base.BasePageQuery;
+import com.iflytop.handacid.common.model.entity.User;
+import com.iflytop.handacid.common.result.PageResult;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.common.service.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+@Tag(name = "\uD83D\uDC64用户接口")
+@RestController
+@RequestMapping("/api/user")
+@RequiredArgsConstructor
+@Slf4j
+public class UserController {
+
+ private final UserService userService;
+
+ @Operation(summary = "分页查询用户列表")
+ @PostMapping("/list")
+ public PageResult list(@RequestBody BasePageQuery query) {
+ Page page = new Page<>(query.getPageNum(), query.getPageSize());
+ return PageResult.success(userService.page(page));
+ }
+
+ @Operation(summary = "添加用户")
+ @PostMapping("")
+ public Result add(@Valid @RequestBody User user) {
+ return userService.save(user) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "修改用户")
+ @PutMapping("")
+ public Result update(@Valid @RequestBody User user) {
+ return userService.updateById(user) ? Result.success() : Result.failed();
+ }
+
+ @Operation(summary = "删除用户")
+ @DeleteMapping("/{ids}")
+ public Result delete(@Parameter(description = "用户ID,多个用逗号分隔") @PathVariable String ids) {
+ boolean success = userService.removeBatchByIds(
+ Arrays.stream(ids.split(",")).map(Long::valueOf).toList()
+ );
+ return success ? Result.success() : Result.failed();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/aspect/DeviceStateChangeAspect.java b/src/main/java/com/iflytop/handacid/app/core/aspect/DeviceStateChangeAspect.java
new file mode 100644
index 0000000..56bc570
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/aspect/DeviceStateChangeAspect.java
@@ -0,0 +1,45 @@
+package com.iflytop.handacid.app.core.aspect;
+
+import com.iflytop.handacid.app.core.state.DeviceState;
+import com.iflytop.handacid.app.websocket.server.WebSocketMessageType;
+import com.iflytop.handacid.app.websocket.server.WebSocketSender;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.After;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Slf4j
+@Aspect
+@Component
+@RequiredArgsConstructor
+public class DeviceStateChangeAspect {
+ private final WebSocketSender webSocketService;
+ private final DeviceState deviceState;
+
+ private final Lock lock = new ReentrantLock();
+
+ @Before("execution(* com.iflytop.handacid.app.core.state.*.set*(..))")
+ public void beforeSetMethod(JoinPoint joinPoint) {
+ lock.lock();
+ }
+
+ @After("execution(* com.iflytop.handacid.app.core.state.*.set*(..))")
+ public void afterSetMethod(JoinPoint joinPoint) {
+ try {
+ Object[] methodArgs = joinPoint.getArgs();
+ if (methodArgs != null && methodArgs.length > 0) {
+ webSocketService.push(WebSocketMessageType.STATUS, deviceState.toJSON());
+ }
+ } catch (Exception e) {
+ log.error("处理状态变更后的值失败", e);
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/BaseCommandHandler.java b/src/main/java/com/iflytop/handacid/app/core/command/BaseCommandHandler.java
new file mode 100644
index 0000000..9b65ceb
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/BaseCommandHandler.java
@@ -0,0 +1,22 @@
+package com.iflytop.handacid.app.core.command;
+
+
+import com.iflytop.handacid.app.common.annotation.CheckedRunnable;
+import com.iflytop.handacid.app.common.utils.LambdaUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+public abstract class BaseCommandHandler implements CommandHandler {
+
+ @Autowired
+ @Qualifier("commandTaskExecutor")
+ private Executor commandExecutor;
+
+ protected CompletableFuture runAsync(CheckedRunnable task) {
+ return CompletableFuture.runAsync(LambdaUtil.unchecked(task), commandExecutor);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/CommandDebugHandlerRegistry.java b/src/main/java/com/iflytop/handacid/app/core/command/CommandDebugHandlerRegistry.java
new file mode 100644
index 0000000..33b984b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/CommandDebugHandlerRegistry.java
@@ -0,0 +1,55 @@
+package com.iflytop.handacid.app.core.command;
+
+import com.iflytop.handacid.app.common.annotation.CommandDebugMapping;
+import com.iflytop.handacid.common.exception.AppException;
+import com.iflytop.handacid.common.result.ResultCode;
+import io.micrometer.common.lang.NonNull;
+import jakarta.annotation.PostConstruct;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class CommandDebugHandlerRegistry implements ApplicationContextAware {
+
+ private final Map handlerMap = new HashMap<>();
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @PostConstruct
+ public void init() {
+ Map beans = applicationContext.getBeansWithAnnotation(CommandDebugMapping.class);
+ for (Object bean : beans.values()) {
+ // 获取实际目标类,而不是代理类
+ Class> targetClass = AopUtils.getTargetClass(bean);
+ CommandDebugMapping mapping = targetClass.getAnnotation(CommandDebugMapping.class);
+ if (mapping != null && bean instanceof CommandHandler) {
+ String mappingKey = mapping.value();
+ handlerMap.put(mappingKey, (CommandHandler) bean);
+ }
+ }
+ }
+
+ /**
+ * 通过模块名称和命令名称获取命令处理器
+ *
+ * @param commandName 命令名称
+ * @return 命令处理器
+ */
+ public CommandHandler getCommandHandler(@NotNull String commandName) {
+ if (!handlerMap.containsKey(commandName)) {
+ throw new AppException(ResultCode.COMMAND_NOT_FOUND);
+ }
+ return handlerMap.get(commandName);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/CommandFuture.java b/src/main/java/com/iflytop/handacid/app/core/command/CommandFuture.java
new file mode 100644
index 0000000..57a3473
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/CommandFuture.java
@@ -0,0 +1,59 @@
+package com.iflytop.handacid.app.core.command;
+
+import cn.hutool.json.JSONObject;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+@Setter
+@Getter
+public class CommandFuture {
+ /**
+ * 用于保存response反馈
+ */
+ private CompletableFuture responseFuture = new CompletableFuture<>();
+ /**
+ * 原始指令
+ */
+ private DeviceCommand deviceCommand;
+ /**
+ * 指令发送时间
+ */
+ private long startSendTime;
+ /**
+ * 指令反馈时间
+ */
+ private long endSendTime;
+ /**
+ * 业务指令id
+ */
+ private String cmdId;
+ /**
+ * 业务指令code
+ */
+ private String cmdCode;
+
+ /**
+ * 完成response
+ */
+ public void completeResponse(JSONObject result) {
+ responseFuture.complete(result);
+ }
+
+ /**
+ * 异常完成response
+ */
+ public void completeResponseExceptionally(Throwable ex) {
+ responseFuture.completeExceptionally(ex);
+ }
+
+ /**
+ * 获取response的json
+ */
+ public JSONObject getResponseResult() throws ExecutionException, InterruptedException {
+ return responseFuture.get();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/CommandHandler.java b/src/main/java/com/iflytop/handacid/app/core/command/CommandHandler.java
new file mode 100644
index 0000000..feaf263
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/CommandHandler.java
@@ -0,0 +1,10 @@
+package com.iflytop.handacid.app.core.command;
+
+
+import com.iflytop.handacid.app.model.dto.CommandDTO;
+
+import java.util.concurrent.CompletableFuture;
+
+public interface CommandHandler {
+ CompletableFuture handle(CommandDTO commandDTO) throws Exception;
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/CommandHandlerRegistry.java b/src/main/java/com/iflytop/handacid/app/core/command/CommandHandlerRegistry.java
new file mode 100644
index 0000000..68881b1
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/CommandHandlerRegistry.java
@@ -0,0 +1,55 @@
+package com.iflytop.handacid.app.core.command;
+
+import com.iflytop.handacid.app.common.annotation.CommandMapping;
+import com.iflytop.handacid.common.exception.AppException;
+import com.iflytop.handacid.common.result.ResultCode;
+import io.micrometer.common.lang.NonNull;
+import jakarta.annotation.PostConstruct;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class CommandHandlerRegistry implements ApplicationContextAware {
+
+ private final Map handlerMap = new HashMap<>();
+ private ApplicationContext applicationContext;
+
+ @Override
+ public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
+ this.applicationContext = applicationContext;
+ }
+
+ @PostConstruct
+ public void init() {
+ Map beans = applicationContext.getBeansWithAnnotation(CommandMapping.class);
+ for (Object bean : beans.values()) {
+ // 获取实际目标类,而不是代理类
+ Class> targetClass = AopUtils.getTargetClass(bean);
+ CommandMapping mapping = targetClass.getAnnotation(CommandMapping.class);
+ if (mapping != null && bean instanceof CommandHandler) {
+ String mappingKey = mapping.value();
+ handlerMap.put(mappingKey, (CommandHandler) bean);
+ }
+ }
+ }
+
+ /**
+ * 通过模块名称和命令名称获取命令处理器
+ *
+ * @param commandName 命令名称
+ * @return 命令处理器
+ */
+ public CommandHandler getCommandHandler(@NotNull String commandName){
+ if (!handlerMap.containsKey(commandName)) {
+ throw new AppException(ResultCode.COMMAND_NOT_FOUND);
+ }
+ return handlerMap.get(commandName);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/CommandPoolManager.java b/src/main/java/com/iflytop/handacid/app/core/command/CommandPoolManager.java
new file mode 100644
index 0000000..e39083a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/CommandPoolManager.java
@@ -0,0 +1,50 @@
+package com.iflytop.handacid.app.core.command;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Slf4j
+@Component
+public class CommandPoolManager {
+ private final ThreadPoolTaskExecutor executor;
+
+ public CommandPoolManager(@Qualifier("commandTaskExecutor") ThreadPoolTaskExecutor executor) {
+ this.executor = executor;
+ }
+
+ /**
+ * 强制中断所有正在执行的任务
+ */
+ public void forceShutdownAll() {
+ try {
+ ThreadPoolExecutor nativeExecutor = executor.getThreadPoolExecutor();
+ nativeExecutor.shutdownNow();
+ } catch (Exception e) {
+ log.error("强制关闭所有cmd线程失败");
+ }
+ }
+
+ /**
+ * 重启命令线程池
+ */
+ public void restartExecutor() {
+ try {
+ Field field = ThreadPoolTaskExecutor.class.getDeclaredField("threadPoolExecutor");
+ field.setAccessible(true);
+ field.set(executor, null);
+ executor.initialize();
+ log.info("命令线程池已重建并启动:corePoolSize={}, maxPoolSize={}, queueCapacity={}",
+ executor.getCorePoolSize(),
+ executor.getMaxPoolSize(),
+ executor.getQueueCapacity());
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ log.error("通过反射重置 threadPoolExecutor 字段失败,无法重启命令线程池", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/CyclicNumberGenerator.java b/src/main/java/com/iflytop/handacid/app/core/command/CyclicNumberGenerator.java
new file mode 100644
index 0000000..0fd2000
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/CyclicNumberGenerator.java
@@ -0,0 +1,39 @@
+package com.iflytop.handacid.app.core.command;
+
+public class CyclicNumberGenerator {
+ // 饿汉式单例,在类加载时就创建实例
+ private static final CyclicNumberGenerator INSTANCE = new CyclicNumberGenerator();
+ // 当前生成的数字,初始值为 1
+ private int currentNumber = 1;
+
+ // 私有构造函数,防止外部实例化
+ private CyclicNumberGenerator() {
+ }
+
+ // 提供全局访问点获取单例实例
+ public static CyclicNumberGenerator getInstance() {
+ return INSTANCE;
+ }
+
+ public static void main(String[] args) {
+ CyclicNumberGenerator generator = CyclicNumberGenerator.getInstance();
+ for (int i = 0; i < 4096; i++) {
+ System.out.println(generator.generateNumber());
+ }
+ }
+
+ /**
+ * 生成 1 到 255 之间的循环整数
+ *
+ * @return 生成的整数
+ */
+ public synchronized int generateNumber() {
+ int result = currentNumber;
+ // 每次生成后将当前数字加 1
+ currentNumber++;
+ if (currentNumber > 4096) {
+ currentNumber = 1;
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/DeviceCommand.java b/src/main/java/com/iflytop/handacid/app/core/command/DeviceCommand.java
new file mode 100644
index 0000000..09b8c7a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/DeviceCommand.java
@@ -0,0 +1,35 @@
+package com.iflytop.handacid.app.core.command;
+
+import com.iflytop.handacid.common.command.DeviceCommandParams;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import lombok.Data;
+
+@Data
+public class DeviceCommand {
+ /**
+ * 指令ID
+ */
+ private Integer cmdId;
+
+ /**
+ * 指令代码 例如 "device_status_get"
+ */
+ private String cmdCode;
+
+ /**
+ * 目标设备
+ */
+ private Device device;
+
+ /**
+ * 执行动作
+ */
+ private Action action;
+
+ /**
+ * 指令参数
+ */
+ private DeviceCommandParams param;
+}
+
diff --git a/src/main/java/com/iflytop/handacid/app/core/command/DeviceCommandGenerator.java b/src/main/java/com/iflytop/handacid/app/core/command/DeviceCommandGenerator.java
new file mode 100644
index 0000000..a27b13c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/command/DeviceCommandGenerator.java
@@ -0,0 +1,1265 @@
+package com.iflytop.handacid.app.core.command;
+
+import com.iflytop.handacid.common.command.DeviceCommandParams;
+import com.iflytop.handacid.common.enums.*;
+
+/**
+ * 生成给设备发送的指令
+ */
+public class DeviceCommandGenerator {
+ // =========================================== 三色灯 ====================================================
+
+ /**
+ * 三色灯开启
+ *
+ * @param color red | green | blue
+ */
+ public static DeviceCommand tricolorLightOpen(TricolorLightColor color) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setColor(color);
+ return controlCmd(Device.TRICOLOR_LIGHT, Action.OPEN, params);
+ }
+
+ /**
+ * 三色灯关闭
+ */
+ public static DeviceCommand tricolorLightClose() {
+ return controlCmd(Device.TRICOLOR_LIGHT, Action.CLOSE, null);
+ }
+ // =========================================== 蜂鸣器 ====================================================
+
+ /**
+ * 蜂鸣器开启
+ *
+ * @param mode alarm | info
+ */
+ public static DeviceCommand beepOpen(BeepMode mode) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setMode(mode);
+ return controlCmd(Device.BEEP, Action.OPEN, params);
+ }
+
+ /**
+ * 蜂鸣器关闭
+ */
+ public static DeviceCommand beepClose() {
+ return controlCmd(Device.MAGNET, Action.CLOSE, null);
+ }
+ // =========================================== 磁子开关 ====================================================
+
+ /**
+ * 电磁开启
+ */
+ public static DeviceCommand magnetOpen() {
+ return controlCmd(Device.MAGNET, Action.OPEN, null);
+ }
+
+ /**
+ * 电磁关闭
+ */
+ public static DeviceCommand magnetClose() {
+ return controlCmd(Device.BEEP, Action.CLOSE, null);
+ }
+ // =========================================== 加热棒 ====================================================
+
+ /**
+ * 加热棒 1 获取参数 todo controlCmd->getInfoCmd
+ */
+ public static DeviceCommand heatRod1Get() {
+ return controlCmd(Device.HEAT_ROD_1, Action.GET, null);
+ }
+
+ /**
+ * 加热棒 1 打开
+ */
+ public static DeviceCommand heatRod1Open(Double temperature) {
+ return controlCmd(Device.HEAT_ROD_1, Action.OPEN, null);
+ }
+
+ /**
+ * 加热棒 1 关闭
+ */
+ public static DeviceCommand heatRod1Close() {
+ return controlCmd(Device.HEAT_ROD_1, Action.CLOSE, null);
+ }
+
+ /**
+ * 加热棒 2 获取参数
+ */
+ public static DeviceCommand heatRod2Get() {
+ return controlCmd(Device.HEAT_ROD_2, Action.GET, null);
+ }
+
+ /**
+ * 加热棒 2 打开
+ */
+ public static DeviceCommand heatRod2Open(Double temperature) {
+ return controlCmd(Device.HEAT_ROD_1, Action.OPEN, null);
+ }
+
+ /**
+ * 加热棒 2 关闭
+ */
+ public static DeviceCommand heatRod2Close() {
+ return controlCmd(Device.HEAT_ROD_1, Action.CLOSE, null);
+ }
+
+ // =========================================== 夹爪 ====================================================
+
+ /**
+ * 设置参数
+ */
+ public static DeviceCommand clawSet(Double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(speed);
+ return setInfoCmd(Device.CLAW, Action.SET, params);
+ }
+
+ /**
+ * 夹爪移动
+ */
+ public static DeviceCommand clawMove(Double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlMotorCmd(Device.CLAW, Action.MOVE, params);
+ }
+
+ /**
+ * 夹爪停止
+ */
+ public static DeviceCommand clawStop() {
+ return controlMotorCmd(Device.CLAW, Action.STOP, null);
+ }
+
+
+ // =========================================== 步进泵 ====================================================
+
+ /**
+ * 步进泵 1 设置速度
+ */
+ public static DeviceCommand stepPump1SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.STEP_PUMP_1, Action.SET, params);
+ }
+
+ /**
+ * 步进泵 1 正向旋转
+ */
+ public static DeviceCommand stepPump1ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.STEP_PUMP_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 步进泵 1 反向旋转
+ */
+ public static DeviceCommand stepPump1BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.STEP_PUMP_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 步进泵 1 相对移动
+ */
+ public static DeviceCommand stepPump1MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.STEP_PUMP_1, Action.MOVE_BY, null);
+ }
+
+ /**
+ * 步进泵 1 停止
+ */
+ public static DeviceCommand stepPump1Stop() {
+ return controlCmd(Device.STEP_PUMP_1, Action.STOP, null);
+ }
+
+ /**
+ * 步进泵 2 设置速度
+ */
+ public static DeviceCommand stepPump2SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.STEP_PUMP_2, Action.SET, params);
+ }
+
+ /**
+ * 步进泵 2 正向旋转
+ */
+ public static DeviceCommand stepPump2ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.STEP_PUMP_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 步进泵 2 反向旋转
+ */
+ public static DeviceCommand stepPump2BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.STEP_PUMP_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 步进泵 2 相对移动
+ */
+ public static DeviceCommand stepPump2MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.STEP_PUMP_2, Action.MOVE_BY, null);
+ }
+
+ /**
+ * 步进泵 2 停止
+ */
+ public static DeviceCommand stepPump2Stop() {
+ return controlCmd(Device.STEP_PUMP_2, Action.STOP, null);
+ }
+
+ /**
+ * 步进泵 3 设置速度
+ */
+ public static DeviceCommand stepPump3SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.STEP_PUMP_3, Action.SET, params);
+ }
+
+ /**
+ * 步进泵 3 正向旋转
+ */
+ public static DeviceCommand stepPump3ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.STEP_PUMP_3, Action.ROTATE, params);
+ }
+
+ /**
+ * 步进泵 3 反向旋转
+ */
+ public static DeviceCommand stepPump3BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.STEP_PUMP_3, Action.ROTATE, params);
+ }
+
+ /**
+ * 步进泵 3 相对移动
+ */
+ public static DeviceCommand stepPump3MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.STEP_PUMP_3, Action.MOVE_BY, null);
+ }
+
+
+ /**
+ * 步进泵 3 停止
+ */
+ public static DeviceCommand stepPump3Stop() {
+ return setInfoCmd(Device.STEP_PUMP_3, Action.STOP, null);
+ }
+ // =========================================== 无刷泵 ====================================================
+
+ /**
+ * 陶瓷泵 1 设置速度
+ */
+ public static DeviceCommand brushlessPump1SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_1, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 1 正向旋转
+ */
+ public static DeviceCommand brushlessPump1ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 1 反向旋转
+ */
+ public static DeviceCommand brushlessPump1BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 1 相对移动
+ */
+ public static DeviceCommand brushlessPump1MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_1, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 1 停止
+ */
+ public static DeviceCommand brushlessPump1Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_1, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 2 设置速度
+ */
+ public static DeviceCommand brushlessPump2SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_2, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 2 正向旋转
+ */
+ public static DeviceCommand brushlessPump2ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 2 反向旋转
+ */
+ public static DeviceCommand brushlessPump2BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 2 相对移动
+ */
+ public static DeviceCommand brushlessPump2MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_2, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 2 停止
+ */
+ public static DeviceCommand brushlessPump2Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_2, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 3 设置速度
+ */
+ public static DeviceCommand brushlessPump3SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_3, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 3 正向旋转
+ */
+ public static DeviceCommand brushlessPump3ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_3, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 3 反向旋转
+ */
+ public static DeviceCommand brushlessPump3BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_3, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 3 相对移动
+ */
+ public static DeviceCommand brushlessPump3MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_3, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 3 停止
+ */
+ public static DeviceCommand brushlessPump3Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_3, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 4 设置速度
+ */
+ public static DeviceCommand brushlessPump4SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_4, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 4 正向旋转
+ */
+ public static DeviceCommand brushlessPump4ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_4, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 4 反向旋转
+ */
+ public static DeviceCommand brushlessPump4BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_4, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 4 相对移动
+ */
+ public static DeviceCommand brushlessPump4MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_4, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 4 停止
+ */
+ public static DeviceCommand brushlessPump4Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_4, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 5 设置速度
+ */
+ public static DeviceCommand brushlessPump5SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_5, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 5 正向旋转
+ */
+ public static DeviceCommand brushlessPump5ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_5, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 5 反向旋转
+ */
+ public static DeviceCommand brushlessPump5BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_5, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 5 相对移动
+ */
+ public static DeviceCommand brushlessPump5MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_5, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 5 停止
+ */
+ public static DeviceCommand brushlessPump5Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_5, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 6 设置速度
+ */
+ public static DeviceCommand brushlessPump6SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_6, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 6 正向旋转
+ */
+ public static DeviceCommand brushlessPump6ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_6, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 6 反向旋转
+ */
+ public static DeviceCommand brushlessPump6BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_6, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 6 相对移动
+ */
+ public static DeviceCommand brushlessPump6MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_6, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 6 停止
+ */
+ public static DeviceCommand brushlessPump6Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_6, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 7 设置速度
+ */
+ public static DeviceCommand brushlessPump7SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_7, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 7 正向旋转
+ */
+ public static DeviceCommand brushlessPump7ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_7, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 7 反向旋转
+ */
+ public static DeviceCommand brushlessPump7BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_7, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 7 相对移动
+ */
+ public static DeviceCommand brushlessPump7MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_7, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 7 停止
+ */
+ public static DeviceCommand brushlessPump7Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_7, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 8 设置速度
+ */
+ public static DeviceCommand brushlessPump8SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_8, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 8 正向旋转
+ */
+ public static DeviceCommand brushlessPump8ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_8, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 8 反向旋转
+ */
+ public static DeviceCommand brushlessPump8BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_8, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 8 相对移动
+ */
+ public static DeviceCommand brushlessPump8MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_8, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 8 停止
+ */
+ public static DeviceCommand brushlessPump8Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_8, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 9 设置速度
+ */
+ public static DeviceCommand brushlessPump9SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_9, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 9 正向旋转
+ */
+ public static DeviceCommand brushlessPump9ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_9, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 9 反向旋转
+ */
+ public static DeviceCommand brushlessPump9BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_9, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 9 相对移动
+ */
+ public static DeviceCommand brushlessPump9MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_9, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 9 停止
+ */
+ public static DeviceCommand brushlessPump9Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_9, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 10 设置速度
+ */
+ public static DeviceCommand brushlessPump10SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_10, Action.SET, params);
+ }
+
+ /**
+ * 无刷泵 10 正向旋转
+ */
+ public static DeviceCommand brushlessPump10ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_10, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 10 反向旋转
+ */
+ public static DeviceCommand brushlessPump10BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.BRUSHLESS_PUMP_10, Action.ROTATE, params);
+ }
+
+ /**
+ * 无刷泵 10 相对移动
+ */
+ public static DeviceCommand brushlessPump10MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.BRUSHLESS_PUMP_10, Action.MOVE_BY, params);
+ }
+
+ /**
+ * 无刷泵 10 停止
+ */
+ public static DeviceCommand brushlessPump10Stop() {
+ return controlCmd(Device.BRUSHLESS_PUMP_10, Action.STOP, null);
+ }
+ // =========================================== 陶瓷泵 ====================================================
+
+ /**
+ * 陶瓷泵 1 设置速度
+ */
+ public static DeviceCommand ceramicPump1SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.CERAMIC_PUMP_1, Action.SET, params);
+ }
+
+ /**
+ * 陶瓷泵 1 正向旋转
+ */
+ public static DeviceCommand ceramicPump1ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.CERAMIC_PUMP_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 陶瓷泵 1 反向旋转
+ */
+ public static DeviceCommand ceramicPump1BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.CERAMIC_PUMP_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 陶瓷泵 1 相对移动
+ */
+ public static DeviceCommand ceramicPump1MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.CERAMIC_PUMP_1, Action.MOVE_BY, null);
+ }
+
+ /**
+ * 陶瓷泵 1 停止
+ */
+ public static DeviceCommand ceramicPump1Stop() {
+ return controlCmd(Device.CERAMIC_PUMP_1, Action.STOP, null);
+ }
+
+ /**
+ * 陶瓷泵 2 设置速度
+ */
+ public static DeviceCommand ceramicPump2SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.CERAMIC_PUMP_2, Action.SET, params);
+ }
+
+ /**
+ * 陶瓷泵 2 正向旋转
+ */
+ public static DeviceCommand ceramicPump2ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.CERAMIC_PUMP_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 陶瓷泵 2 反向旋转
+ */
+ public static DeviceCommand ceramicPump2BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.CERAMIC_PUMP_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 陶瓷泵 2 相对移动
+ */
+ public static DeviceCommand ceramicPump2MoveBy(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.CERAMIC_PUMP_2, Action.MOVE_BY, null);
+ }
+
+ /**
+ * 陶瓷泵 2 停止
+ */
+ public static DeviceCommand ceramicPump2Stop() {
+
+ return controlCmd(Device.CERAMIC_PUMP_2, Action.STOP, null);
+ }
+
+ // =========================================== 滴定移动电机 ====================================================
+
+ /**
+ * titration_motor_1 回原点
+ */
+ public static DeviceCommand titrationMotor1Origin() {
+
+ return controlMotorCmd(Device.TITRATION_MOTOR_1, Action.ORIGIN, null);
+ }
+
+ /**
+ * titration_motor_1 停止
+ */
+ public static DeviceCommand titrationMotor1Stop() {
+
+ return controlMotorCmd(Device.TITRATION_MOTOR_1, Action.STOP, null);
+ }
+
+ /**
+ * titration_motor_1 使能
+ */
+ public static DeviceCommand titrationMotor1Enable() {
+
+ return setInfoCmd(Device.TITRATION_MOTOR_1, Action.ENABLE, null);
+ }
+
+ /**
+ * titration_motor_1 失能
+ */
+ public static DeviceCommand titrationMotor1Disable() {
+
+ return setInfoCmd(Device.TITRATION_MOTOR_1, Action.DISABLE, null);
+ }
+
+ /**
+ * titration_motor_1 设置速度
+ */
+ public static DeviceCommand titrationMotor1SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.TITRATION_MOTOR_1, Action.SET, params);
+ }
+
+ /**
+ * titration_motor_1 绝对移动
+ */
+ public static DeviceCommand titrationMotor1MoveTo(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.TITRATION_MOTOR_1, Action.MOVE, params);
+ }
+
+ /**
+ * titration_motor_1 相对移动
+ */
+ public static DeviceCommand titrationMotor1MoveBy(double distance) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(distance);
+ return controlCmd(Device.TITRATION_MOTOR_1, Action.MOVE_BY, params);
+ }
+
+ /**
+ * titration_motor_2 回原点
+ */
+ public static DeviceCommand titrationMotor2Origin() {
+
+ return controlMotorCmd(Device.TITRATION_MOTOR_2, Action.ORIGIN, null);
+ }
+
+ /**
+ * titration_motor_2 停止
+ */
+ public static DeviceCommand titrationMotor2Stop() {
+
+ return controlMotorCmd(Device.TITRATION_MOTOR_2, Action.STOP, null);
+ }
+
+ /**
+ * titration_motor_2 使能
+ */
+ public static DeviceCommand titrationMotor2Enable() {
+
+ return setInfoCmd(Device.TITRATION_MOTOR_2, Action.ENABLE, null);
+ }
+
+ /**
+ * titration_motor_2 失能
+ */
+ public static DeviceCommand titrationMotor2Disable() {
+
+ return setInfoCmd(Device.TITRATION_MOTOR_2, Action.DISABLE, null);
+ }
+
+ /**
+ * titration_motor_2 设置速度
+ */
+ public static DeviceCommand titrationMotor2SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.TITRATION_MOTOR_2, Action.SET, params);
+ }
+
+
+ /**
+ * titration_motor_2 绝对移动
+ */
+ public static DeviceCommand titrationMotor2MoveTo(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.TITRATION_MOTOR_2, Action.MOVE, params);
+ }
+
+ /**
+ * titration_motor_2 相对移动
+ */
+ public static DeviceCommand titrationMotor2MoveBy(double distance) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(distance);
+ return controlCmd(Device.TITRATION_MOTOR_2, Action.MOVE_BY, params);
+ }
+ // =========================================== 机械臂大臂移动电机 ====================================================
+
+ /**
+ * 机械臂大臂回原点
+ */
+ public static DeviceCommand roboticArmBigMotorOrigin() {
+
+ return controlMotorCmd(Device.ROBOTIC_ARM_BIG, Action.ORIGIN, null);
+ }
+
+ /**
+ * 机械臂大臂停止
+ */
+ public static DeviceCommand roboticArmBigMotorStop() {
+
+ return controlMotorCmd(Device.ROBOTIC_ARM_BIG, Action.STOP, null);
+ }
+
+ /**
+ * 机械臂大臂 使能
+ */
+ public static DeviceCommand roboticArmBigMotorEnable() {
+
+ return setInfoCmd(Device.ROBOTIC_ARM_BIG, Action.ENABLE, null);
+ }
+
+ /**
+ * 机械臂大臂 失能
+ */
+ public static DeviceCommand roboticArmBigMotorDisable() {
+
+ return setInfoCmd(Device.ROBOTIC_ARM_BIG, Action.DISABLE, null);
+ }
+
+ /**
+ * 机械臂大臂 设置速度
+ */
+ public static DeviceCommand roboticArmBigMotorSetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.ROBOTIC_ARM_BIG, Action.SET, params);
+ }
+
+ /**
+ * 机械臂大臂 绝对移动
+ */
+ public static DeviceCommand roboticArmBigMotorMoveTo(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.ROBOTIC_ARM_BIG, Action.MOVE, params);
+ }
+
+ /**
+ * 机械臂大臂 相对移动
+ */
+ public static DeviceCommand roboticArmBigMotorMoveBy(double distance) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(distance);
+ return controlCmd(Device.ROBOTIC_ARM_BIG, Action.MOVE_BY, params);
+ }
+// =========================================== 机械臂小臂移动电机 ====================================================
+
+ /**
+ * 机械臂小臂 回原点
+ */
+ public static DeviceCommand roboticArmSmallMotorOrigin() {
+
+ return controlMotorCmd(Device.ROBOTIC_ARM_SMALL, Action.ORIGIN, null);
+ }
+
+ /**
+ * 机械臂小臂 停止
+ */
+ public static DeviceCommand roboticArmSmallMotorStop() {
+
+ return controlMotorCmd(Device.ROBOTIC_ARM_SMALL, Action.STOP, null);
+ }
+
+ /**
+ * 机械臂小臂 使能
+ */
+ public static DeviceCommand roboticArmSmallMotorEnable() {
+
+ return setInfoCmd(Device.ROBOTIC_ARM_SMALL, Action.ENABLE, null);
+ }
+
+ /**
+ * 机械臂小臂 失能
+ */
+ public static DeviceCommand roboticArmSmallMotorDisable() {
+
+ return setInfoCmd(Device.ROBOTIC_ARM_SMALL, Action.DISABLE, null);
+ }
+
+ /**
+ * 机械臂小臂 设置速度
+ */
+ public static DeviceCommand roboticArmSmallMotorSetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.ROBOTIC_ARM_SMALL, Action.SET, params);
+ }
+
+
+ /**
+ * 机械臂小臂 绝对移动
+ */
+ public static DeviceCommand roboticArmSmallMotorMoveTo(double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlCmd(Device.ROBOTIC_ARM_SMALL, Action.MOVE, params);
+ }
+
+ /**
+ * 机械臂小臂 相对移动
+ */
+ public static DeviceCommand roboticArmSmallMotorMoveBy(double distance) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(distance);
+ return controlCmd(Device.ROBOTIC_ARM_SMALL, Action.MOVE_BY, params);
+ }
+ // =========================================== 磁子搅拌电机 ====================================================
+
+ /**
+ * 搅拌电机 1 正向旋转
+ */
+ public static DeviceCommand stirMotor1ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.STIR_MOTOR_1, Action.ROTATE, params);
+ }
+
+ /**
+ * 搅拌电机 1 反向旋转
+ */
+ public static DeviceCommand stirMotor1BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.STIR_MOTOR_1, Action.ROTATE, params);
+ }
+
+ /**
+ * STIR_MOTOR_1 回原点
+ */
+ public static DeviceCommand stirMotor1Origin() {
+
+ return controlMotorCmd(Device.STIR_MOTOR_1, Action.ORIGIN, null);
+ }
+
+ /**
+ * STIR_MOTOR_1 停止
+ */
+ public static DeviceCommand stirMotor1Stop() {
+
+ return controlMotorCmd(Device.STIR_MOTOR_1, Action.STOP, null);
+ }
+
+ /**
+ * STIR_MOTOR_1 使能
+ */
+ public static DeviceCommand stirMotor1Enable() {
+
+ return setInfoCmd(Device.STIR_MOTOR_1, Action.ENABLE, null);
+ }
+
+ /**
+ * STIR_MOTOR_1 失能
+ */
+ public static DeviceCommand stirMotor1Disable() {
+
+ return setInfoCmd(Device.STIR_MOTOR_1, Action.DISABLE, null);
+ }
+
+ /**
+ * STIR_MOTOR_1 设置速度
+ */
+ public static DeviceCommand stirMotor1SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.STIR_MOTOR_1, Action.SET, params);
+ }
+
+
+ /**
+ * STIR_MOTOR_1 相对移动
+ */
+ public static DeviceCommand stirMotor1MoveBy(double distance) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(distance);
+ return controlCmd(Device.STIR_MOTOR_1, Action.MOVE_BY, params);
+ }
+
+ /**
+ * STIR_MOTOR_2 回原点
+ */
+ public static DeviceCommand stirMotor2Origin() {
+
+ return controlMotorCmd(Device.STIR_MOTOR_2, Action.ORIGIN, null);
+ }
+
+ /**
+ * 搅拌电机 2 正向旋转
+ */
+ public static DeviceCommand stirMotor2ForwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.FORWARD);
+ return setInfoCmd(Device.STIR_MOTOR_2, Action.ROTATE, params);
+ }
+
+ /**
+ * 搅拌电机 2 反向旋转
+ */
+ public static DeviceCommand stirMotor2BackwardRotate() {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setDirection(MotorDirection.BACKWARD);
+ return setInfoCmd(Device.STIR_MOTOR_2, Action.ROTATE, params);
+ }
+
+ /**
+ * STIR_MOTOR_2 停止
+ */
+ public static DeviceCommand stirMotor2Stop() {
+
+ return controlMotorCmd(Device.STIR_MOTOR_2, Action.STOP, null);
+ }
+
+ /**
+ * STIR_MOTOR_2 使能
+ */
+ public static DeviceCommand stirMotor2Enable() {
+
+ return setInfoCmd(Device.STIR_MOTOR_2, Action.ENABLE, null);
+ }
+
+ /**
+ * STIR_MOTOR_2 失能
+ */
+ public static DeviceCommand stirMotor2Disable() {
+
+ return setInfoCmd(Device.STIR_MOTOR_2, Action.DISABLE, null);
+ }
+
+ /**
+ * STIR_MOTOR_2 设置速度
+ */
+ public static DeviceCommand stirMotor2SetSpeed(double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setSpeed(speed);
+ return setInfoCmd(Device.STIR_MOTOR_2, Action.SET, params);
+ }
+
+ /**
+ * STIR_MOTOR_2 相对移动
+ */
+ public static DeviceCommand stirMotor2MoveBy(double distance) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(distance);
+ return controlCmd(Device.TITRATION_MOTOR_2, Action.MOVE_BY, params);
+ }
+
+ // =========================================== Z 轴升降电机 ====================================================
+
+ /**
+ * 设置参数
+ */
+ public static DeviceCommand zSet(Double speed) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(speed);
+ return setInfoCmd(Device.Z_MOTOR, Action.SET, params);
+ }
+
+ /**
+ * z轴移动
+ */
+ public static DeviceCommand zMove(Double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlMotorCmd(Device.Z_MOTOR, Action.MOVE, params);
+ }
+
+ /**
+ * z轴相对移动
+ */
+ public static DeviceCommand zMoveBy(Double position) {
+ DeviceCommandParams params = new DeviceCommandParams();
+ params.setPosition(position);
+ return controlMotorCmd(Device.Z_MOTOR, Action.MOVE_BY, params);
+ }
+
+ /**
+ * z轴回原点
+ */
+ public static DeviceCommand zOrigin() {
+ return controlMotorCmd(Device.Z_MOTOR, Action.ORIGIN, null);
+ }
+
+ /**
+ * z轴停止
+ */
+ public static DeviceCommand zStop() {
+ return controlMotorCmd(Device.Z_MOTOR, Action.STOP, null);
+ }
+
+ /**
+ * z轴使能
+ */
+ public static DeviceCommand zEnable() {
+ return setInfoCmd(Device.Z_MOTOR, Action.ENABLE, null);
+ }
+
+ /**
+ * z轴失能
+ */
+ public static DeviceCommand zDisable() {
+ return setInfoCmd(Device.Z_MOTOR, Action.DISABLE, null);
+ }
+
+//===========================================PRIVATE====================================================================
+
+ /**
+ * 控制设备电机指令
+ */
+ private static DeviceCommand controlMotorCmd(Device device, Action action, DeviceCommandParams params) {
+ return deviceCmd("controlMotorCmd", device, action, params);
+ }
+
+ /**
+ * 设置参数指令
+ */
+ private static DeviceCommand setInfoCmd(Device device, Action action, DeviceCommandParams params) {
+ return deviceCmd("setInfoCmd", device, action, params);
+ }
+
+ /**
+ * 获取参数指令
+ */
+ private static DeviceCommand getInfoCmd(Device device) {
+ return deviceCmd("getInfoCmd", device, Action.GET, null);
+ }
+
+
+ /**
+ * 设备控制指令
+ */
+ private static DeviceCommand controlCmd(Device device, Action action, DeviceCommandParams params) {
+ return deviceCmd("controlCmd", device, action, params);
+ }
+
+ /**
+ * 设备指令包装
+ */
+ private static DeviceCommand deviceCmd(String code, Device device, Action action, DeviceCommandParams params) {
+ DeviceCommand deviceCommand = new DeviceCommand();
+ deviceCommand.setCmdCode(code);
+ deviceCommand.setDevice(device);
+ deviceCommand.setAction(action);
+ deviceCommand.setParam(params);
+ return deviceCommand;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/core/event/CommandFeedbackEvent.java b/src/main/java/com/iflytop/handacid/app/core/event/CommandFeedbackEvent.java
new file mode 100644
index 0000000..34e3be4
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/event/CommandFeedbackEvent.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.app.core.event;
+
+import cn.hutool.json.JSONObject;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+public class CommandFeedbackEvent extends ApplicationEvent {
+ private final JSONObject jsonResponse;
+
+ public CommandFeedbackEvent(Object source, JSONObject jsonResponse) {
+ super(source);
+ this.jsonResponse = jsonResponse;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/core/event/StateChangeEvent.java b/src/main/java/com/iflytop/handacid/app/core/event/StateChangeEvent.java
new file mode 100644
index 0000000..df8ab75
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/event/StateChangeEvent.java
@@ -0,0 +1,14 @@
+package com.iflytop.handacid.app.core.event;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 状态变更事件
+ */
+@Data
+@AllArgsConstructor
+public class StateChangeEvent {
+ private String fieldPath;
+ private Object oldValue;
+ private Object newValue;
+}
diff --git a/src/main/java/com/iflytop/handacid/app/core/event/VirtualDeviceCmdResponseEvent.java b/src/main/java/com/iflytop/handacid/app/core/event/VirtualDeviceCmdResponseEvent.java
new file mode 100644
index 0000000..0bc28ac
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/event/VirtualDeviceCmdResponseEvent.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.app.core.event;
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+@Getter
+public class VirtualDeviceCmdResponseEvent extends ApplicationEvent {
+ private final DeviceCommand cmdToDevice;
+
+ public VirtualDeviceCmdResponseEvent(Object source, DeviceCommand cmdToDevice) {
+ super(source);
+ this.cmdToDevice = cmdToDevice;
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/core/listener/VirtualDeviceCmdResponseEventListener.java b/src/main/java/com/iflytop/handacid/app/core/listener/VirtualDeviceCmdResponseEventListener.java
new file mode 100644
index 0000000..7c36852
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/listener/VirtualDeviceCmdResponseEventListener.java
@@ -0,0 +1,20 @@
+package com.iflytop.handacid.app.core.listener;
+
+import com.iflytop.handacid.app.core.event.VirtualDeviceCmdResponseEvent;
+import com.iflytop.handacid.app.service.VirtualDeviceService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class VirtualDeviceCmdResponseEventListener {
+ private final VirtualDeviceService virtualDeviceService;
+
+ @EventListener
+ public void handleDeviceTcpMessageEvent(VirtualDeviceCmdResponseEvent event) {
+ virtualDeviceService.completeCommandResponse(event.getCmdToDevice());
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/core/state/DeviceState.java b/src/main/java/com/iflytop/handacid/app/core/state/DeviceState.java
new file mode 100644
index 0000000..81a7313
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/core/state/DeviceState.java
@@ -0,0 +1,53 @@
+package com.iflytop.handacid.app.core.state;
+
+import cn.hutool.json.JSONObject;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.iflytop.handacid.app.common.enums.ModuleStateCode;
+import com.iflytop.handacid.app.common.enums.MultipleModuleCode;
+import com.iflytop.handacid.common.model.entity.User;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@Schema(description = "设备当前状态")
+@Data
+@Component
+@JsonIgnoreProperties(value = {"advisors", "frozen", "preFiltered", "proxyTargetClass", "targetSource", "exposeProxy", "advisorCount", "proxiedInterfaces", "targetClass"})
+public class DeviceState {
+ @Schema(description = "当前设备是否暂停")
+ private volatile boolean craftsPaused = false;
+
+ @Schema(description = "通道1状态")
+ private volatile ModuleStateCode MODULE_1 = ModuleStateCode.IDLE;
+
+ @Schema(description = "通道2状态")
+ private volatile ModuleStateCode MODULE_2 = ModuleStateCode.IDLE;
+
+ @Schema(description = "通道3状态")
+ private volatile ModuleStateCode MODULE_3 = ModuleStateCode.IDLE;
+
+ @Schema(description = "通道4状态")
+ private volatile ModuleStateCode MODULE_4 = ModuleStateCode.IDLE;
+
+ @Schema(description = "虚拟模式,true为虚拟")
+ private volatile boolean virtual = false;
+
+ @Schema(description = "是否是急停状态,true为急停")
+ private volatile boolean emergencyStop = false;
+
+ @Schema(description = "当前登录用户")
+ private User currentUser;
+
+ public JSONObject toJSON() {
+ JSONObject json = new JSONObject();
+ json.putOnce("craftsPaused", craftsPaused);
+ json.putOnce("virtual", virtual);
+ json.putOnce("emergencyStop", emergencyStop);
+ json.putOnce("currentUser", currentUser);
+ return json;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/model/dto/CommandDTO.java b/src/main/java/com/iflytop/handacid/app/model/dto/CommandDTO.java
new file mode 100644
index 0000000..f19e632
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/model/dto/CommandDTO.java
@@ -0,0 +1,73 @@
+package com.iflytop.handacid.app.model.dto;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.Map;
+
+@Schema(description = "指令")
+@Data
+public class CommandDTO {
+
+ @NotNull
+ @Schema(description = "指令id,前端生成唯一ID")
+ private String commandId;
+
+ @NotNull
+ @Schema(description = "命令名称")
+ private String command;
+
+ @Schema(description = "参数")
+ private Map params;
+
+ // 获取 Double 类型的参数,null 或空字符串时返回 null
+ public Double getDoubleParam(String key) {
+ String value = getStringParam(key);
+ return (value != null && !value.isEmpty()) ? Double.parseDouble(value) : null;
+ }
+
+ // 获取 String 类型的参数,null 或空字符串时返回 null
+ public String getStringParam(String key) {
+ Object value = params.get(key);
+ // 确保值不是 null 或空字符串,转换成 String 后返回
+ return (value != null && !value.toString().isEmpty()) ? value.toString() : null;
+ }
+
+ // 获取 Integer 类型的参数,null 或空字符串时返回 null
+ public Integer getIntegerParam(String key) {
+ String value = getStringParam(key);
+ return (value != null && !value.isEmpty()) ? Integer.parseInt(value) : null;
+ }
+
+ // 获取 Long 类型的参数,null 或空字符串时返回 null
+ public Long getLongParam(String key) {
+ String value = getStringParam(key);
+ return (value != null && !value.isEmpty()) ? Long.parseLong(value) : null;
+ }
+
+ // 获取 Boolean 类型的参数,null 或空字符串时返回 null
+ public Boolean getBooleanParam(String key) {
+ String value = getStringParam(key);
+ return (value != null && !value.isEmpty()) ? Boolean.parseBoolean(value) : null;
+ }
+
+ public JSONObject getJsonObjectParam(String key) {
+ Object value = params.get(key);
+ return new JSONObject(value);
+
+ }
+
+ public JSONArray getJSONArrayParam(String key) {
+ Object value = params.get(key);
+ return new JSONArray(value);
+ }
+
+ @Override
+ public String toString() {
+ return JSONUtil.toJsonStr(this);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/model/dto/LoginDTO.java b/src/main/java/com/iflytop/handacid/app/model/dto/LoginDTO.java
new file mode 100644
index 0000000..8e587a8
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/model/dto/LoginDTO.java
@@ -0,0 +1,22 @@
+package com.iflytop.handacid.app.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 用户登录表单
+ */
+@Schema(description = "用户登录")
+@Data
+public class LoginDTO {
+
+ @NotNull
+ @Schema(description = "用户名", example = "admin")
+ private String username;
+
+ @NotNull
+ @Schema(description = "用户密码", example = "12345")
+ private String password;
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/model/dto/TimeSetDTO.java b/src/main/java/com/iflytop/handacid/app/model/dto/TimeSetDTO.java
new file mode 100644
index 0000000..3fce537
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/model/dto/TimeSetDTO.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.app.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Data
+public class TimeSetDTO {
+ /**
+ * 要设置的时间戳(毫秒),UTC 纪元毫秒数
+ */
+ @NotNull
+ @Schema(description = "要设置的时间戳(毫秒),UTC 纪元毫秒数")
+ private Long epochMilli;
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/model/dto/TrayTubeSetExistDTO.java b/src/main/java/com/iflytop/handacid/app/model/dto/TrayTubeSetExistDTO.java
new file mode 100644
index 0000000..517e26d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/model/dto/TrayTubeSetExistDTO.java
@@ -0,0 +1,19 @@
+package com.iflytop.handacid.app.model.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "是否存在试管")
+@Data
+public class TrayTubeSetExistDTO {
+
+ @NotNull
+ @Schema(description = "试管编号")
+ private Integer tubeNum;
+
+ @NotNull
+ @Schema(description = "是否存在试管 true存在 false不存在")
+ private Boolean tubeExist;
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/model/vo/TimeResponseVO.java b/src/main/java/com/iflytop/handacid/app/model/vo/TimeResponseVO.java
new file mode 100644
index 0000000..a23a0a3
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/model/vo/TimeResponseVO.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.app.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class TimeResponseVO {
+
+ @Schema(description = "当前系统时间的 UTC 毫秒时间戳")
+ private long epochMilli;
+
+
+ public TimeResponseVO(long epochMilli) {
+ this.epochMilli = epochMilli;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/service/DeviceCommandService.java b/src/main/java/com/iflytop/handacid/app/service/DeviceCommandService.java
new file mode 100644
index 0000000..211cd8d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/service/DeviceCommandService.java
@@ -0,0 +1,172 @@
+package com.iflytop.handacid.app.service;
+
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.iflytop.handacid.app.common.constant.CommandStatus;
+import com.iflytop.handacid.app.core.command.CommandFuture;
+import com.iflytop.handacid.app.core.command.CyclicNumberGenerator;
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.app.core.event.VirtualDeviceCmdResponseEvent;
+import com.iflytop.handacid.app.core.state.DeviceState;
+import com.iflytop.handacid.app.websocket.server.DebugGenerator;
+import com.iflytop.handacid.app.websocket.server.WebSocketSender;
+import com.iflytop.handacid.hardware.HardwareService;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.LinkedBlockingQueue;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DeviceCommandService {
+ private final HardwareService hardwareService;
+ private final WebSocketSender webSocketService;
+ private final DeviceState deviceState;
+ private final ApplicationEventPublisher publisher;
+
+ /**
+ * 需要等待加液区空闲的龙门架机械臂指令
+ */
+ private final ConcurrentMap sendCommandFutureMap = new ConcurrentHashMap<>();
+ private final BlockingQueue gantryCommandQueue = new LinkedBlockingQueue<>();
+
+ @PostConstruct
+ private void initExecutorThread() {
+ new Thread(this::executeCommands).start();
+ }
+
+ private void executeCommands() {
+ while (true) {
+ try {
+ CommandFuture[] commandFutureArray = gantryCommandQueue.take();
+ for (CommandFuture commandFuture : commandFutureArray) {
+ executeCommand(commandFuture);
+ }
+ } catch (Exception e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ public synchronized CommandFuture[] sendCommandGantryQueue(DeviceCommand... deviceCommandBundles) {
+ return sendCommandGantryQueue(null, null, deviceCommandBundles);
+ }
+
+ public synchronized CommandFuture[] sendCommandGantryQueue(String cmdId, String cmdCode, DeviceCommand... deviceCommands) {
+ List commandFutureList = new ArrayList<>();
+ for (DeviceCommand deviceCommand : deviceCommands) {
+ commandFutureList.add(createDeviceCommandFuture(cmdId, cmdCode, deviceCommand));
+ }
+ CommandFuture[] commandFutureArray = commandFutureList.toArray(new CommandFuture[0]);
+ try {
+ gantryCommandQueue.put(commandFutureArray);
+ } catch (Exception e) {
+ log.error("设备指令入队列失败", e);
+ throw new RuntimeException(e);
+ }
+ return commandFutureArray;
+ }
+
+ /**
+ * 根据 DeviceCommand 创建 CommandFuture
+ */
+ private CommandFuture createDeviceCommandFuture(String cmdId, String cmdCode, DeviceCommand deviceCommand) {
+ CommandFuture commandFuture = createDeviceCommandFuture(deviceCommand);
+ commandFuture.setCmdId(cmdId);
+ commandFuture.setCmdCode(cmdCode);
+ return commandFuture;
+ }
+
+ /**
+ * 根据 DeviceCommand 创建 CommandFuture
+ */
+ private CommandFuture createDeviceCommandFuture(DeviceCommand deviceCommand) {
+ CommandFuture commandFuture = new CommandFuture();
+ commandFuture.setDeviceCommand(deviceCommand);
+ commandFuture.getResponseFuture().whenComplete((result, ex) -> {
+ sendCommandFutureMap.remove(deviceCommand.getCmdId());
+ });
+ return commandFuture;
+ }
+
+ public void executeCommand(CommandFuture commandFuture) {
+ int cmdId = CyclicNumberGenerator.getInstance().generateNumber();
+ commandFuture.getDeviceCommand().setCmdId(cmdId);
+ sendCommandFutureMap.put(cmdId, commandFuture);
+ commandFuture.setStartSendTime(System.currentTimeMillis());
+ if (!deviceState.isVirtual()) {
+ if (!hardwareService.sendCommand(commandFuture.getDeviceCommand())) {
+ sendCommandFutureMap.remove(commandFuture.getDeviceCommand().getCmdId());
+ throw new RuntimeException("向设备发送指令失败");
+ }
+ if (commandFuture.getCmdId() != null) {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_SEND, commandFuture.getDeviceCommand().getDevice() + "_" + commandFuture.getDeviceCommand().getAction() + "指令,已发给设备", commandFuture.getDeviceCommand()));
+ }
+ } else {
+ //虚拟模式
+ log.info("模拟向设备发送TCP指令:{}", JSONUtil.toJsonStr(commandFuture.getDeviceCommand()));
+ //模拟反馈
+ publisher.publishEvent(new VirtualDeviceCmdResponseEvent(this, commandFuture.getDeviceCommand()));
+ }
+
+ }
+
+ public CommandFuture sendCommand(DeviceCommand deviceCommand) {
+ CommandFuture commandFuture = createDeviceCommandFuture(deviceCommand);
+ executeCommand(commandFuture);
+ return commandFuture;
+ }
+
+ public CommandFuture sendCommand(String cmdId, String cmdCode, DeviceCommand deviceCommand) {
+ CommandFuture commandFuture = createDeviceCommandFuture(cmdId, cmdCode, deviceCommand);
+ executeCommand(commandFuture);
+ return commandFuture;
+ }
+
+ public void completeCommandResponse(JSONObject deviceResult) {
+ Integer cmdId = deviceResult.getInt("cmdId");
+ if (cmdId != null) {
+ CommandFuture commandFuture = sendCommandFutureMap.get(cmdId);
+ if (commandFuture != null) {
+ commandFuture.setEndSendTime(System.currentTimeMillis());
+ Boolean success = deviceResult.getBool("success"); //数据验证
+ if (success == null || !success) { //response失败
+ if (commandFuture.getCmdId() != null) {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_ERROR,
+ commandFuture.getDeviceCommand().getDevice() + "_" + commandFuture.getDeviceCommand().getAction() + "指令,设备response错误,耗时:" + (commandFuture.getEndSendTime() - commandFuture.getStartSendTime()), deviceResult));
+ }
+ commandFuture.completeResponseExceptionally(new RuntimeException("response失败:" + deviceResult));
+ } else {
+ if (commandFuture.getCmdId() != null) {
+ webSocketService.pushDebug(DebugGenerator.generateJson(commandFuture.getCmdId(), commandFuture.getCmdCode(), CommandStatus.DEVICE_RESULT,
+ commandFuture.getDeviceCommand().getDevice() + "_" + commandFuture.getDeviceCommand().getAction() + "指令,设备response正常,耗时:" + (commandFuture.getEndSendTime() - commandFuture.getStartSendTime()), deviceResult));
+ }
+ commandFuture.completeResponse(deviceResult);
+ }
+ }
+ }
+ }
+
+ /**
+ * 取消等待中的future并从map中移除
+ */
+ public synchronized void releaseAllCommandFutures() {
+ for (Integer key : sendCommandFutureMap.keySet()) {
+ CommandFuture future = sendCommandFutureMap.remove(key);
+ if (future != null) {
+ future.getResponseFuture().cancel(true);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/service/DeviceInitService.java b/src/main/java/com/iflytop/handacid/app/service/DeviceInitService.java
new file mode 100644
index 0000000..b4e4a25
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/service/DeviceInitService.java
@@ -0,0 +1,47 @@
+package com.iflytop.handacid.app.service;
+
+import com.iflytop.handacid.app.core.state.DeviceState;
+import com.iflytop.handacid.hardware.service.AppEventBusService;
+import com.iflytop.handacid.hardware.type.appevent.A8kCanBusOnConnectEvent;
+import com.iflytop.handacid.hardware.type.appevent.AppEvent;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DeviceInitService {
+ private final AppEventBusService eventBus;
+ private boolean isLink = false;
+ private final DeviceState deviceState;
+
+ @PostConstruct
+ public void init() {
+ eventBus.regListener(this::onAppEvent);
+ new Thread(() -> {
+ try {
+ log.info("初始化开始");
+
+ initDeviceState();
+ //缓存点位数据
+ log.info("初始化完毕");
+ } catch (Exception e) {
+ }
+ }).start();
+
+ }
+
+ private void onAppEvent(AppEvent event) {
+ if (event instanceof A8kCanBusOnConnectEvent) {
+ isLink = true;
+ }
+ }
+
+ public void initDeviceState() {
+ log.info("初始化 initDeviceState");
+ log.info("初始化 initDeviceState完毕");
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/service/DeviceParamConfigService.java b/src/main/java/com/iflytop/handacid/app/service/DeviceParamConfigService.java
new file mode 100644
index 0000000..1baad52
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/service/DeviceParamConfigService.java
@@ -0,0 +1,119 @@
+package com.iflytop.handacid.app.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.iflytop.handacid.common.mapper.DeviceParamConfigMapper;
+import com.iflytop.handacid.common.model.entity.DeviceParamConfig;
+import com.iflytop.handacid.common.model.vo.DeviceParamGroupVO;
+import com.iflytop.handacid.common.model.vo.ModuleIdVO;
+import com.iflytop.handacid.common.model.vo.RegIndexVO;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.RegIndex;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 设备参数配置服务
+ */
+@Service
+@RequiredArgsConstructor
+public class DeviceParamConfigService extends ServiceImpl {
+ private final DeviceParamConfigMapper deviceParamConfigMapper;
+
+ /**
+ * 按 ModuleId 分组,列出每个模块下的 regIndex + regVal
+ */
+ public List listGroupedByModule() {
+ // 拉取所有配置记录
+ List all = this.list();
+
+ // 按 mid 分组
+ Map> grouped = all.stream()
+ .collect(Collectors.groupingBy(DeviceParamConfig::getMid));
+
+ // 构造 VO 列表
+ return grouped.entrySet().stream()
+ .map(entry -> {
+ String moduleId = entry.getKey();
+ List items = entry.getValue().stream()
+ .map(cfg -> new DeviceParamGroupVO.ParamItem(
+ cfg.getRegIndex(),
+ cfg.getRegVal()
+ ))
+ .collect(Collectors.toList());
+ return new DeviceParamGroupVO(moduleId, items);
+ })
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 根据模块标识和寄存器索引查询设备参数配置
+ *
+ * @param moduleId 模块枚举
+ * @param regIndex 寄存器索引
+ * @return 唯一匹配的 DeviceParamConfig,找不到返回 null
+ */
+ public DeviceParamConfig getByModuleAndReg(MId moduleId, RegIndex regIndex) {
+ return deviceParamConfigMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(DeviceParamConfig::getMid, moduleId.name())
+ .eq(DeviceParamConfig::getRegIndex, regIndex.name())
+ );
+ }
+
+ /**
+ * 根据模块标识和寄存器索引设置设备参数配置
+ *
+ * @param moduleId 模块枚举
+ * @param regIndex 寄存器索引
+ * @param regVal 寄存器值
+ * @return 唯一匹配的 DeviceParamConfig,找不到返回 null
+ */
+ public int setModuleAndReg(String moduleId, String regIndex, Integer regVal) {
+ return deviceParamConfigMapper.update(new LambdaUpdateWrapper()
+ .eq(DeviceParamConfig::getMid, moduleId)
+ .eq(DeviceParamConfig::getRegIndex, regIndex)
+ .set(DeviceParamConfig::getRegVal, regVal));
+
+ }
+
+ /**
+ * 列出所有 ModuleId,返回 VO 列表
+ *
+ * @return List
+ */
+ public List listAllModuleIds() {
+ return Arrays.stream(MId.values())
+ .map(e -> new ModuleIdVO(
+ e.name(), // 枚举常量名
+ e.description // 描述字段
+ ))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 列出所有寄存器索引枚举,返回 VO 列表
+ *
+ * @return List
+ */
+ public List listAllRegIndices() {
+ return Arrays.stream(RegIndex.values())
+ .map(e -> new RegIndexVO(
+ e.name() // 枚举常量名
+ ))
+ .collect(Collectors.toList());
+ }
+
+ public boolean deleteDeviceParam(String idsStr) {
+ List ids = Arrays.stream(idsStr.split(","))
+ .map(Long::parseLong)
+ .collect(Collectors.toList());
+ return this.removeByIds(ids);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/service/SystemService.java b/src/main/java/com/iflytop/handacid/app/service/SystemService.java
new file mode 100644
index 0000000..ac2ceed
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/service/SystemService.java
@@ -0,0 +1,27 @@
+package com.iflytop.handacid.app.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.iflytop.handacid.common.mapper.SystemConfigMapper;
+import com.iflytop.handacid.common.model.entity.SystemConfig;
+import com.iflytop.handacid.common.utils.CommandUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统配置接口服务
+ */
+@Service
+@RequiredArgsConstructor
+public class SystemService extends ServiceImpl {
+ /**
+ * 将系统时区(可选)和系统时间(必选)一起设定
+ *
+ * @param epochMilli UTC 毫秒时间戳
+ */
+ public void setSystemTime(long epochMilli) {
+ long epochSecond = epochMilli / 1_000;
+ CommandUtil.runCommand("timedatectl", "set-ntp", "false");
+ CommandUtil.runCommand("date", "-s", "@" + epochSecond);
+ CommandUtil.runCommand("hwclock", "--systohc");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/app/service/TestService.java b/src/main/java/com/iflytop/handacid/app/service/TestService.java
new file mode 100644
index 0000000..db17d94
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/service/TestService.java
@@ -0,0 +1,15 @@
+package com.iflytop.handacid.app.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * 测试用
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class TestService {
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/service/VirtualDeviceService.java b/src/main/java/com/iflytop/handacid/app/service/VirtualDeviceService.java
new file mode 100644
index 0000000..9d7d6d3
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/service/VirtualDeviceService.java
@@ -0,0 +1,45 @@
+package com.iflytop.handacid.app.service;
+
+import cn.hutool.json.JSONObject;
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 虚拟设备服务
+ */
+@Service
+@RequiredArgsConstructor
+public class VirtualDeviceService {
+ private final DeviceCommandService deviceCommandService;
+
+ public void completeCommandResponse(DeviceCommand cmdToDevice) {
+ new Thread(() -> {
+ try {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.putOnce("cmdId", cmdToDevice.getCmdId());
+ jsonObject.putOnce("success", true);
+
+ String code = cmdToDevice.getCmdCode();
+ Action action = cmdToDevice.getAction();
+ Device device = cmdToDevice.getDevice();
+ if (code.contains("controlMotorCmd")) {
+ if (Action.ORIGIN.equals(action)) {
+ Thread.sleep(3000);
+ } else if (!Action.SET.equals(action)) {//非设置电机参数,也就是电机移动
+ Thread.sleep(500);
+ }
+ } else if (code.contains("getInfoCmd")) {
+
+ }
+ deviceCommandService.completeCommandResponse(jsonObject);
+ } catch (InterruptedException e) {
+ // 处理中断异常
+ Thread.currentThread().interrupt();
+ }
+ }).start();
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/websocket/server/DebugGenerator.java b/src/main/java/com/iflytop/handacid/app/websocket/server/DebugGenerator.java
new file mode 100644
index 0000000..6443592
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/websocket/server/DebugGenerator.java
@@ -0,0 +1,22 @@
+package com.iflytop.handacid.app.websocket.server;
+
+import cn.hutool.json.JSONObject;
+
+public class DebugGenerator {
+
+ public static JSONObject generateJson(String commandId, String command, String status, String title, Object content) {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.set("commandId", commandId);
+ jsonObject.set("command", command);
+ jsonObject.set("status", status);
+ jsonObject.set("title", title);
+ jsonObject.set("content", content);
+ return jsonObject;
+ }
+
+ public static JSONObject generateJson(String commandId, String command, String status, String title) {
+ return generateJson(commandId, command, status, title, null);
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketMessageType.java b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketMessageType.java
new file mode 100644
index 0000000..235af6f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketMessageType.java
@@ -0,0 +1,59 @@
+package com.iflytop.handacid.app.websocket.server;
+
+public class WebSocketMessageType {
+ /**
+ * 设备状态
+ */
+ public static final String STATUS = "status";
+ /**
+ * 设备报警
+ */
+ public static final String ALARM = "alarm";
+ /**
+ * 滴定日志
+ */
+ public static final String LOG = "log";
+ /**
+ * 自检移动电机测试
+ */
+ public static final String SELF_MOVE_TEST = "self_move_test";
+ /**
+ * 指令反馈
+ */
+ public static final String CMD_RESPONSE = "cmd_response";
+
+ /**
+ * 工艺执行步骤反馈
+ */
+ public static final String CRAFTS_STEP = "crafts_step";
+
+ /**
+ * 工艺执行状态反馈
+ */
+ public static final String CRAFTS_STATE = "crafts_state";
+
+ /**
+ * 工艺DEBUG
+ */
+ public static final String CRAFTS_DEBUG = "crafts_debug";
+
+ /**
+ * 容器剩余状态
+ */
+ public static final String CONTAINER = "container";
+
+ /**
+ * DEBUG消息推送
+ */
+ public static final String CMD_DEBUG = "cmd_debug";
+
+ /**
+ * 加热倒计时
+ */
+ public static final String HEAT_COUNTDOWN = "heat_countdown";
+ /**
+ * 照片
+ */
+ public static final String PHOTO = "photo";
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketSender.java b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketSender.java
new file mode 100644
index 0000000..e0aa3fb
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketSender.java
@@ -0,0 +1,59 @@
+package com.iflytop.handacid.app.websocket.server;
+
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+@Slf4j
+@Component
+public class WebSocketSender {
+
+ public void push(String type, Object data) {
+ WebsocketResult websocketResult = new WebsocketResult();
+ websocketResult.setType(type);
+ websocketResult.setData(data);
+ websocketResult.setTimestamp(Instant.now().toEpochMilli());
+ WebSocketServer.sendMessageToClients(JSONUtil.toJsonStr(websocketResult));
+// log.info("WS::{}", JSONUtil.toJsonStr(websocketResult));
+ }
+
+ public void pushCraftsDebug(Object data) {
+ push(WebSocketMessageType.CRAFTS_DEBUG, data);
+ }
+
+ public void pushDebug(Object data) {
+ push(WebSocketMessageType.CMD_DEBUG, data);
+ }
+
+ public void pushLog(String code, String type, String content) {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ String formattedDate = LocalDateTime.now().format(formatter);
+ WebsocketResult websocketResult = new WebsocketResult();
+ websocketResult.setType(WebSocketMessageType.LOG);
+ websocketResult.setData(formattedDate + "模块" + StringUtils.substring(code, code.length() - 1) + type + content);
+ websocketResult.setTimestamp(Instant.now().toEpochMilli());
+ WebSocketServer.sendMessageToClients(JSONUtil.toJsonStr(websocketResult));
+ }
+
+ public void pushCMDResponse(Object data) {
+ push(WebSocketMessageType.CMD_RESPONSE, data);
+ }
+
+ public void pushSelfMoveTest(Object data) {
+ push(WebSocketMessageType.SELF_MOVE_TEST, data);
+ }
+
+ public void pushHeatCountdown(Object data) {
+ push(WebSocketMessageType.HEAT_COUNTDOWN, data);
+ }
+
+ /* public void pushNotification(Notification notification) {
+ push("notification", notification);
+ }*/
+
+}
diff --git a/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServer.java b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServer.java
new file mode 100644
index 0000000..816b56f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServer.java
@@ -0,0 +1,51 @@
+package com.iflytop.handacid.app.websocket.server;
+
+import jakarta.websocket.*;
+import jakarta.websocket.server.ServerEndpoint;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+@Slf4j
+@ServerEndpoint("/ws")
+@Component
+public class WebSocketServer {
+ private static final Set sessions = Collections.synchronizedSet(new HashSet<>());
+
+ public static void sendMessageToClients(String message) {
+ synchronized (sessions) {
+ for (Session session : sessions) {
+ try {
+ session.getBasicRemote().sendText(message);
+ } catch (Exception e) {
+ log.error("WS发送给客户端失败 sessionId={}", session.getId(), e);
+ }
+ }
+ }
+ }
+
+ @OnOpen
+ public void onOpen(Session session) {
+ sessions.add(session);
+ log.info("新连接加入,sessionId={}", session.getId());
+ }
+
+ @OnMessage
+ public void onMessage(String message, Session session) {
+ log.info("收到消息 sessionId={},内容:{}", session.getId(), message);
+ }
+
+ @OnClose
+ public void onClose(Session session) {
+ sessions.remove(session);
+ log.info("连接已关闭,sessionId={}", session.getId());
+ }
+
+ @OnError
+ public void onError(Session session, Throwable error) {
+ log.error("发生错误,sessionId={}", session.getId(), error);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServerConfig.java b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServerConfig.java
new file mode 100644
index 0000000..60233f2
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/websocket/server/WebSocketServerConfig.java
@@ -0,0 +1,14 @@
+package com.iflytop.handacid.app.websocket.server;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketServerConfig {
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/app/websocket/server/WebsocketResult.java b/src/main/java/com/iflytop/handacid/app/websocket/server/WebsocketResult.java
new file mode 100644
index 0000000..3963ff3
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/app/websocket/server/WebsocketResult.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid.app.websocket.server;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Data
+public class WebsocketResult {
+ /**
+ * 推送类型(指令 cmd,报警 warn ,状态 status)
+ */
+ @Schema(description = "推送类型(指令 cmd,报警 warn ,状态 status)")
+ private String type;
+ /**
+ * 执行结果
+ */
+ @Schema(description = "推送数据")
+ private Object data;
+
+ @Schema(description = "推送时间戳")
+ private Long timestamp;
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/base/BaseEntity.java b/src/main/java/com/iflytop/handacid/common/base/BaseEntity.java
new file mode 100644
index 0000000..f6a1d78
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/base/BaseEntity.java
@@ -0,0 +1,44 @@
+package com.iflytop.handacid.common.base;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 基础实体类
+ * 实体类的基类,包含了实体类的公共属性,如创建时间、更新时间、逻辑删除标识等
+ */
+@Data
+public class BaseEntity implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键ID
+ */
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ /**
+ * 创建时间
+ */
+ @TableField(fill = FieldFill.INSERT)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+
+ /**
+ * 更新时间
+ */
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime updateTime;
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/base/BasePageQuery.java b/src/main/java/com/iflytop/handacid/common/base/BasePageQuery.java
new file mode 100644
index 0000000..10e9add
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/base/BasePageQuery.java
@@ -0,0 +1,26 @@
+package com.iflytop.handacid.common.base;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 基础分页请求对象
+ */
+@Data
+@Schema
+public class BasePageQuery implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Schema(description = "页码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private int pageNum = 1;
+
+ @Schema(description = "每页记录数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+ private int pageSize = 10;
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/base/IBaseEnum.java b/src/main/java/com/iflytop/handacid/common/base/IBaseEnum.java
new file mode 100644
index 0000000..b6b1b3e
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/base/IBaseEnum.java
@@ -0,0 +1,97 @@
+package com.iflytop.handacid.common.base;
+
+
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Objects;
+
+/**
+ * 枚举通用接口
+ */
+public interface IBaseEnum {
+
+ /**
+ * 根据值获取枚举
+ *
+ * @param value
+ * @param clazz
+ * @param 枚举
+ * @return
+ */
+ static & IBaseEnum> E getEnumByValue(Object value, Class clazz) {
+ Objects.requireNonNull(value);
+ EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
+ E matchEnum = allEnums.stream()
+ .filter(e -> ObjectUtil.equal(e.getValue(), value))
+ .findFirst()
+ .orElse(null);
+ return matchEnum;
+ }
+
+ /**
+ * 根据文本标签获取值
+ *
+ * @param value
+ * @param clazz
+ * @param
+ * @return
+ */
+ static & IBaseEnum> String getLabelByValue(Object value, Class clazz) {
+ Objects.requireNonNull(value);
+ EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
+ E matchEnum = allEnums.stream()
+ .filter(e -> ObjectUtil.equal(e.getValue(), value))
+ .findFirst()
+ .orElse(null);
+
+ String label = null;
+ if (matchEnum != null) {
+ label = matchEnum.getLabel();
+ }
+ return label;
+ }
+
+ /**
+ * 根据文本标签获取值
+ *
+ * @param label
+ * @param clazz
+ * @param
+ * @return
+ */
+ static & IBaseEnum> Object getValueByLabel(String label, Class clazz) {
+ Objects.requireNonNull(label);
+ EnumSet allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
+ String finalLabel = label;
+ E matchEnum = allEnums.stream()
+ .filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel))
+ .findFirst()
+ .orElse(null);
+
+ Object value = null;
+ if (matchEnum != null) {
+ value = matchEnum.getValue();
+ }
+ return value;
+ }
+ /**
+ * 根据name值比对是否包含
+ *
+ * @param enumClass
+ * @param name
+ * @return
+ */
+ static > boolean contains(Class enumClass, String name) {
+ return Arrays.stream(enumClass.getEnumConstants())
+ .map(Enum::name)
+ .anyMatch(name::equals);
+ }
+
+ T getValue();
+
+ String getLabel();
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/command/DeviceCommandParams.java b/src/main/java/com/iflytop/handacid/common/command/DeviceCommandParams.java
new file mode 100644
index 0000000..221b599
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/command/DeviceCommandParams.java
@@ -0,0 +1,24 @@
+package com.iflytop.handacid.common.command;
+
+
+import com.iflytop.handacid.common.enums.BeepMode;
+import com.iflytop.handacid.common.enums.MotorDirection;
+import com.iflytop.handacid.common.enums.TricolorLightColor;
+import lombok.Data;
+
+@Data
+public class DeviceCommandParams {
+ private String device;
+ private MotorDirection direction;
+ private Double position;
+ private Double speed;
+ private Double angle;
+ private Double volume;
+ private Double distance;
+ private Double temperature;
+ private TricolorLightColor color;
+ private BeepMode mode;
+ private Double x;
+ private Double y;
+ private Double z;
+}
diff --git a/src/main/java/com/iflytop/handacid/common/config/A8kCanBusConnectionConfig.java b/src/main/java/com/iflytop/handacid/common/config/A8kCanBusConnectionConfig.java
new file mode 100644
index 0000000..97938c4
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/config/A8kCanBusConnectionConfig.java
@@ -0,0 +1,32 @@
+package com.iflytop.handacid.common.config;
+
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusConnection;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class A8kCanBusConnectionConfig {
+ //协议参考 https://iflytop1.feishu.cn/wiki/QFwVwGnI8iYp0fk20W9cnpYAnkg
+ @Value("${iflytophald.ip}")
+ String ip;
+
+ @Value("${iflytophald.cmdch.port}")
+ Integer cmdchPort;
+
+ @Value("${iflytophald.datach.port}")
+ Integer datachPort;
+
+ public String getDatachUrl(String datachannel) {
+ return String.format("ws://%s:%d/%s", ip, datachPort, datachannel);
+ }
+
+ public String getCmdChBaseUrl(String cmdchannel) {
+ return String.format("http://%s:%d/%s", ip, cmdchPort, cmdchannel);
+ }
+
+ @Bean
+ A8kCanBusConnection a8kCanBusBaseService() {
+ return new A8kCanBusConnection(getCmdChBaseUrl("zexcan"), getDatachUrl("zexcan"));
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/config/MybatisPlusConfig.java b/src/main/java/com/iflytop/handacid/common/config/MybatisPlusConfig.java
new file mode 100644
index 0000000..15fa4c5
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/config/MybatisPlusConfig.java
@@ -0,0 +1,61 @@
+package com.iflytop.handacid.common.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.core.config.GlobalConfig;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.iflytop.handacid.app.common.handler.MyMetaObjectHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+/**
+ *
+ */
+@EnableTransactionManagement
+@Configuration
+public class MybatisPlusConfig {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ // 乐观锁插件
+ interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
+ // 分页插件
+ interceptor.addInnerInterceptor(paginationInnerInterceptor());
+
+ return interceptor;
+ }
+
+ /**
+ * 分页插件,自动识别数据库类型
+ * ...
+ */
+ public PaginationInnerInterceptor paginationInnerInterceptor() {
+ PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+ // 设置数据库类型
+ paginationInnerInterceptor.setDbType(DbType.SQLITE);
+ // 设置最大单页限制数量,默认 500 条,-1 不受限制
+ paginationInnerInterceptor.setMaxLimit(-1L);
+ return paginationInnerInterceptor;
+ }
+
+ /**
+ * 乐观锁插件
+ * ...
+ */
+ public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+ return new OptimisticLockerInnerInterceptor();
+ }
+
+ /**
+ * 自动填充数据库创建人、创建时间、更新人、更新时间
+ */
+ @Bean
+ public GlobalConfig globalConfig() {
+ GlobalConfig globalConfig = new GlobalConfig();
+ globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
+ return globalConfig;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/config/SwaggerConfig.java b/src/main/java/com/iflytop/handacid/common/config/SwaggerConfig.java
new file mode 100644
index 0000000..be4c805
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/config/SwaggerConfig.java
@@ -0,0 +1,68 @@
+package com.iflytop.handacid.common.config;
+
+import cn.hutool.core.util.ArrayUtil;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.stream.Stream;
+
+/***
+ * 创建Swagger配置
+ */
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() {
+ return openApi -> {
+ // 全局添加Authorization
+ if (openApi.getPaths() != null) {
+ openApi.getPaths().forEach((path, pathItem) -> {
+
+ // 忽略认证的请求无需携带 Authorization
+ String[] ignoreUrls = {"/api/auth/login"};
+ if (ArrayUtil.isNotEmpty(ignoreUrls)) {
+ // Ant 匹配忽略的路径,不添加Authorization
+ AntPathMatcher antPathMatcher = new AntPathMatcher();
+ if (Stream.of(ignoreUrls).anyMatch(ignoreUrl -> antPathMatcher.match(ignoreUrl, path))) {
+ return;
+ }
+ }
+
+ // 其他接口统一添加Authorization
+// pathItem.readOperations()
+// .forEach(operation ->
+// operation.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION))
+// );
+ });
+ }
+ };
+ }
+
+ @Bean
+ public OpenAPI customOpenAPI() {
+ return new OpenAPI()
+ .info(new Info()
+ .title("系统API")
+ .version("1.0")) // 配置全局鉴权参数-Authorize
+ .components(new Components()
+ .addSecuritySchemes(HttpHeaders.AUTHORIZATION,
+ new SecurityScheme()
+ .name(HttpHeaders.AUTHORIZATION)
+ .type(SecurityScheme.Type.APIKEY)
+ .in(SecurityScheme.In.HEADER)
+ .scheme("Bearer")
+ .bearerFormat("JWT")
+ )
+ );
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/config/WebConfig.java b/src/main/java/com/iflytop/handacid/common/config/WebConfig.java
new file mode 100644
index 0000000..0bd8679
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/config/WebConfig.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.common.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/api/**")
+ .allowedOrigins("*")
+ .allowedMethods("GET", "POST", "PUT", "DELETE")
+ .allowedHeaders("*");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/enums/Action.java b/src/main/java/com/iflytop/handacid/common/enums/Action.java
new file mode 100644
index 0000000..d92ad4d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/Action.java
@@ -0,0 +1,39 @@
+package com.iflytop.handacid.common.enums;
+
+import lombok.Getter;
+
+/**
+ * 通用设备动作枚举
+ */
+public enum Action {
+ MOVE("绝对移动"),
+ MOVE_BY("相对移动"),
+ ORIGIN("回原点"),
+ ROTATE("旋转"),
+ STOP("停止"),
+ OPEN("打开"),
+ CLOSE("关闭"),
+ SET("设置"),
+ GET("获取"),
+ OPEN_CLAMP("打开抱闸"),
+ CLOSE_CLAMP("关闭抱闸"),
+ ENABLE("使能"),
+ DISABLE("失能"),
+ MOVE_END("移动到末端点"),
+ ;
+
+ /**
+ * 动作的中文描述
+ */
+ @Getter
+ private final String description;
+
+ Action(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return name();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/enums/BeepMode.java b/src/main/java/com/iflytop/handacid/common/enums/BeepMode.java
new file mode 100644
index 0000000..bc7b5e4
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/BeepMode.java
@@ -0,0 +1,19 @@
+package com.iflytop.handacid.common.enums;
+
+
+import com.iflytop.handacid.common.base.IBaseEnum;
+
+public enum BeepMode implements IBaseEnum {
+ alarm, // 报警音
+ info; //提示音
+
+ @Override
+ public Object getValue() {
+ return null;
+ }
+
+ @Override
+ public String getLabel() {
+ return "";
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/enums/Device.java b/src/main/java/com/iflytop/handacid/common/enums/Device.java
new file mode 100644
index 0000000..676f086
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/Device.java
@@ -0,0 +1,66 @@
+package com.iflytop.handacid.common.enums;
+
+import lombok.Getter;
+
+public enum Device {
+ Z_MOTOR(HardwareType.STEPPER_MOTOR, "Z 轴升降电机"),
+ CERAMIC_PUMP_1(HardwareType.STEPPER_MOTOR, "陶瓷泵 1"),
+ CERAMIC_PUMP_2(HardwareType.STEPPER_MOTOR, "陶瓷泵 2"),
+ BRUSHLESS_PUMP_1(HardwareType.STEPPER_MOTOR, "无刷泵 1"),
+ BRUSHLESS_PUMP_2(HardwareType.STEPPER_MOTOR, "无刷泵 2"),
+ BRUSHLESS_PUMP_3(HardwareType.STEPPER_MOTOR, "无刷泵 3"),
+ BRUSHLESS_PUMP_4(HardwareType.STEPPER_MOTOR, "无刷泵 4"),
+ BRUSHLESS_PUMP_5(HardwareType.STEPPER_MOTOR, "无刷泵 5"),
+ BRUSHLESS_PUMP_6(HardwareType.STEPPER_MOTOR, "无刷泵 6"),
+ BRUSHLESS_PUMP_7(HardwareType.STEPPER_MOTOR, "无刷泵 7"),
+ BRUSHLESS_PUMP_8(HardwareType.STEPPER_MOTOR, "无刷泵 8"),
+ BRUSHLESS_PUMP_9(HardwareType.STEPPER_MOTOR, "无刷泵 9"),
+ BRUSHLESS_PUMP_10(HardwareType.STEPPER_MOTOR, "无刷泵 10"),
+ STEP_PUMP_1(HardwareType.STEPPER_MOTOR, "步进泵 1"),
+ STEP_PUMP_2(HardwareType.STEPPER_MOTOR, "步进泵 2"),
+ STEP_PUMP_3(HardwareType.STEPPER_MOTOR, "步进泵 3"),
+ STIR_MOTOR_1(HardwareType.STEPPER_MOTOR, "搅拌电机 1"),
+ STIR_MOTOR_2(HardwareType.STEPPER_MOTOR, "搅拌电机 2"),
+ TITRATION_MOTOR_1(HardwareType.STEPPER_MOTOR, "滴定移动电机 1"),
+ TITRATION_MOTOR_2(HardwareType.STEPPER_MOTOR, "滴定移动电机 2"),
+ CLAW(HardwareType.SERVO_MOTOR, "夹爪"),
+ ROBOTIC_ARM_BIG(HardwareType.STEPPER_MOTOR, "双轴机械臂大臂"),
+ ROBOTIC_ARM_SMALL(HardwareType.STEPPER_MOTOR, "双轴机械臂小臂"),
+ HEAT_ROD_1(HardwareType.IO_DEVICE, "加热棒 1"),
+ HEAT_ROD_2(HardwareType.IO_DEVICE, "加热棒 2"),
+ TRICOLOR_LIGHT(HardwareType.IO_DEVICE, "三色灯"),
+ BEEP(HardwareType.IO_DEVICE, "蜂鸣器"),
+ MAGNET(HardwareType.IO_DEVICE, "电磁开关"),
+ CONTAINER_LOW_LEVEL_1(HardwareType.IO_DEVICE, "容器低液位检测 1"),
+ CONTAINER_LOW_LEVEL_2(HardwareType.IO_DEVICE, "容器低液位检测 2"),
+ CONTAINER_LOW_LEVEL_3(HardwareType.IO_DEVICE, "容器低液位检测 3"),
+ CONTAINER_LOW_LEVEL_4(HardwareType.IO_DEVICE, "容器低液位检测 4"),
+ CONTAINER_LOW_LEVEL_5(HardwareType.IO_DEVICE, "容器低液位检测 5"),
+ CONTAINER_LOW_LEVEL_6(HardwareType.IO_DEVICE, "容器低液位检测 6"),
+ CONTAINER_LOW_LEVEL_7(HardwareType.IO_DEVICE, "容器低液位检测 7"),
+ CONTAINER_LOW_LEVEL_8(HardwareType.IO_DEVICE, "容器低液位检测 8"),
+ HEATER_TUBE_EXIST_1(HardwareType.IO_DEVICE, "加热位是否存在试管 1"),
+ HEATER_TUBE_EXIST_2(HardwareType.IO_DEVICE, "加热位是否存在试管 2"),
+ ;
+
+ /**
+ * 设备所属硬件类型
+ */
+ @Getter
+ private final HardwareType type;
+ /**
+ * 设备中文描述或显示名
+ */
+ @Getter
+ private final String description;
+
+ Device(HardwareType type, String description) {
+ this.type = type;
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return this.name();
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/enums/EnableStatus.java b/src/main/java/com/iflytop/handacid/common/enums/EnableStatus.java
new file mode 100644
index 0000000..13c84c3
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/EnableStatus.java
@@ -0,0 +1,5 @@
+package com.iflytop.handacid.common.enums;
+
+public enum EnableStatus {
+ ENABLE, DISABLE
+}
diff --git a/src/main/java/com/iflytop/handacid/common/enums/HardwareType.java b/src/main/java/com/iflytop/handacid/common/enums/HardwareType.java
new file mode 100644
index 0000000..a618b14
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/HardwareType.java
@@ -0,0 +1,25 @@
+package com.iflytop.handacid.common.enums;
+
+public enum HardwareType {
+ /**
+ * 步进电机
+ */
+ STEPPER_MOTOR,
+ /**
+ * 伺服电机
+ */
+ SERVO_MOTOR,
+ /**
+ * 通用传感器(温度、湿度等)
+ */
+ SENSOR,
+ /**
+ * IO 控制(阀门、风扇、加热器等)
+ */
+ IO_DEVICE,
+ /**
+ * 相机
+ */
+ CAMERA;
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/enums/MotorDirection.java b/src/main/java/com/iflytop/handacid/common/enums/MotorDirection.java
new file mode 100644
index 0000000..066a38a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/MotorDirection.java
@@ -0,0 +1,5 @@
+package com.iflytop.handacid.common.enums;
+
+public enum MotorDirection {
+ FORWARD, BACKWARD
+}
diff --git a/src/main/java/com/iflytop/handacid/common/enums/TricolorLightColor.java b/src/main/java/com/iflytop/handacid/common/enums/TricolorLightColor.java
new file mode 100644
index 0000000..4052d09
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/enums/TricolorLightColor.java
@@ -0,0 +1,17 @@
+package com.iflytop.handacid.common.enums;
+
+import com.iflytop.handacid.common.base.IBaseEnum;
+
+public enum TricolorLightColor implements IBaseEnum {
+ RED, GREEN, BLUE;
+
+ @Override
+ public Object getValue() {
+ return null;
+ }
+
+ @Override
+ public String getLabel() {
+ return "";
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/exception/AppException.java b/src/main/java/com/iflytop/handacid/common/exception/AppException.java
new file mode 100644
index 0000000..13616ad
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/exception/AppException.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid.common.exception;
+
+import com.iflytop.handacid.common.result.IResultCode;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AppException extends RuntimeException {
+ private final IResultCode resultCode;
+
+ public AppException(IResultCode resultCode) {
+ super(resultCode.getMsg());
+ this.resultCode = resultCode;
+ }
+
+ @Override
+ public String toString() {
+ return "AppException{" + "code='" + resultCode.getCode() + ", msg=" + resultCode.getMsg() + '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/mapper/DeviceParamConfigMapper.java b/src/main/java/com/iflytop/handacid/common/mapper/DeviceParamConfigMapper.java
new file mode 100644
index 0000000..5722a48
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/mapper/DeviceParamConfigMapper.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.common.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.iflytop.handacid.common.model.entity.DeviceParamConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 设备参数配置持久层接口
+ */
+@Mapper
+public interface DeviceParamConfigMapper extends BaseMapper {
+}
diff --git a/src/main/java/com/iflytop/handacid/common/mapper/SolutionsMapper.java b/src/main/java/com/iflytop/handacid/common/mapper/SolutionsMapper.java
new file mode 100644
index 0000000..4c625b6
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/mapper/SolutionsMapper.java
@@ -0,0 +1,13 @@
+package com.iflytop.handacid.common.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.iflytop.handacid.common.model.entity.Solutions;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 溶液持久层接口
+ */
+@Mapper
+public interface SolutionsMapper extends BaseMapper {
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/mapper/SystemConfigMapper.java b/src/main/java/com/iflytop/handacid/common/mapper/SystemConfigMapper.java
new file mode 100644
index 0000000..2e2b9c4
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/mapper/SystemConfigMapper.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.common.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.iflytop.handacid.common.model.entity.SystemConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 系统配置持久层接口
+ */
+@Mapper
+public interface SystemConfigMapper extends BaseMapper {
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/mapper/SystemLogMapper.java b/src/main/java/com/iflytop/handacid/common/mapper/SystemLogMapper.java
new file mode 100644
index 0000000..d3862c9
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/mapper/SystemLogMapper.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.common.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.iflytop.handacid.common.model.entity.SystemLog;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 系统日志持久层接口
+ */
+@Mapper
+public interface SystemLogMapper extends BaseMapper {
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/mapper/UserMapper.java b/src/main/java/com/iflytop/handacid/common/mapper/UserMapper.java
new file mode 100644
index 0000000..9703d9d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/mapper/UserMapper.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.common.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.iflytop.handacid.common.model.entity.User;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 用户持久层接口
+ */
+@Mapper
+public interface UserMapper extends BaseMapper {
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/model/bo/Point2D.java b/src/main/java/com/iflytop/handacid/common/model/bo/Point2D.java
new file mode 100644
index 0000000..77f8860
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/bo/Point2D.java
@@ -0,0 +1,20 @@
+package com.iflytop.handacid.common.model.bo;
+
+
+import lombok.Data;
+
+@Data
+public class Point2D {
+ public Double x;
+ public Double y;
+
+ public Point2D() {
+ }
+
+ public Point2D(Double x, Double y) {
+ this.x = x;
+ this.y = y;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/model/bo/Point3D.java b/src/main/java/com/iflytop/handacid/common/model/bo/Point3D.java
new file mode 100644
index 0000000..dbb73ba
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/bo/Point3D.java
@@ -0,0 +1,22 @@
+package com.iflytop.handacid.common.model.bo;
+
+
+import lombok.Data;
+
+@Data
+public class Point3D {
+ public Double x;
+ public Double y;
+ public Double z;
+
+ public Point3D() {
+ }
+
+ public Point3D(Double x, Double y, Double z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/model/entity/DeviceParamConfig.java b/src/main/java/com/iflytop/handacid/common/model/entity/DeviceParamConfig.java
new file mode 100644
index 0000000..2af2758
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/entity/DeviceParamConfig.java
@@ -0,0 +1,30 @@
+package com.iflytop.handacid.common.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iflytop.handacid.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "设备参数配置")
+@TableName("device_param_config")
+@Data
+public class DeviceParamConfig extends BaseEntity {
+
+ @NotNull
+ @NotBlank
+ @Schema(description = "模块标识")
+ private String mid;
+
+ @NotNull
+ @NotBlank
+ @Schema(description = "寄存器索引")
+ private String regIndex;
+
+ @Schema(description = "寄存器值")
+ private Integer regVal;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/model/entity/Solutions.java b/src/main/java/com/iflytop/handacid/common/model/entity/Solutions.java
new file mode 100644
index 0000000..bf9a347
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/entity/Solutions.java
@@ -0,0 +1,17 @@
+package com.iflytop.handacid.common.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iflytop.handacid.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@TableName("solutions")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "溶液")
+public class Solutions extends BaseEntity {
+
+ @Schema(description = "溶液名称")
+ private String name;
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/model/entity/SystemConfig.java b/src/main/java/com/iflytop/handacid/common/model/entity/SystemConfig.java
new file mode 100644
index 0000000..c776e7e
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/entity/SystemConfig.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid.common.model.entity;
+
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iflytop.handacid.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@TableName("system_config")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "系统配置")
+public class SystemConfig extends BaseEntity {
+
+ @Schema(description = "配置键")
+ private String key;
+
+ @Schema(description = "配置值")
+ private String value;
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/model/entity/SystemLog.java b/src/main/java/com/iflytop/handacid/common/model/entity/SystemLog.java
new file mode 100644
index 0000000..68a5d55
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/entity/SystemLog.java
@@ -0,0 +1,20 @@
+package com.iflytop.handacid.common.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iflytop.handacid.common.base.BaseEntity;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@TableName("system_log")
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "系统日志")
+public class SystemLog extends BaseEntity {
+
+ @Schema(description = "日志标题")
+ private String title;
+
+ @Schema(description = "日志内容")
+ private String content;
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/model/entity/User.java b/src/main/java/com/iflytop/handacid/common/model/entity/User.java
new file mode 100644
index 0000000..2c41f22
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/entity/User.java
@@ -0,0 +1,44 @@
+package com.iflytop.handacid.common.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.iflytop.handacid.common.base.BaseEntity;
+import com.iflytop.handacid.common.enums.EnableStatus;
+import com.iflytop.handacid.app.common.enums.UserRole;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户实体
+ */
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "用户")
+@TableName("user")
+@Data
+public class User extends BaseEntity {
+
+ @NotNull
+ @Schema(description = "用户名")
+ private String username;
+
+ @NotNull
+ @Schema(description = "昵称")
+ private String nickname;
+
+ @NotNull
+ @Schema(description = "密码")
+ private String password;
+
+ @NotNull
+ @Schema(description = "人员角色")
+ private UserRole role;
+
+ @NotNull
+ @Schema(description = "是否删除")
+ private EnableStatus deleted;
+
+ @Schema(description = "是否是系统固定用户")
+ private EnableStatus fixedUser;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/model/vo/DeviceParamGroupVO.java b/src/main/java/com/iflytop/handacid/common/model/vo/DeviceParamGroupVO.java
new file mode 100644
index 0000000..c102b4d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/vo/DeviceParamGroupVO.java
@@ -0,0 +1,36 @@
+package com.iflytop.handacid.common.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 按模块分组的设备参数视图
+ */
+@Data
+@Schema(description = "按模块分组的设备参数")
+public class DeviceParamGroupVO {
+
+ @Schema(description = "模块标识(ModuleId.name),如 DoorM")
+ private String mid;
+
+ @Schema(description = "该模块下的所有配置项")
+ private List reg;
+
+ public DeviceParamGroupVO(String mid, List reg) {
+ this.mid = mid;
+ this.reg = reg;
+ }
+
+ @AllArgsConstructor
+ @Data
+ @Schema(description = "配置项")
+ public static class ParamItem {
+ @Schema(description = "配置项名称,如 kreg_step_motor_pos")
+ private String name;
+ @Schema(description = "值")
+ private Integer value;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/model/vo/ModuleIdVO.java b/src/main/java/com/iflytop/handacid/common/model/vo/ModuleIdVO.java
new file mode 100644
index 0000000..894ef81
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/vo/ModuleIdVO.java
@@ -0,0 +1,19 @@
+package com.iflytop.handacid.common.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * ModuleId 的展示视图
+ */
+@Data
+@AllArgsConstructor
+@Schema(description = "ModuleId 的展示视图")
+public class ModuleIdVO {
+ @Schema(description = "模块名称,如 DoorM")
+ private String name;
+
+ @Schema(description = "模块描述")
+ private String description;
+}
diff --git a/src/main/java/com/iflytop/handacid/common/model/vo/RegIndexVO.java b/src/main/java/com/iflytop/handacid/common/model/vo/RegIndexVO.java
new file mode 100644
index 0000000..ac64b80
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/model/vo/RegIndexVO.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.common.model.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * RegIndex 的展示视图
+ */
+@Data
+@AllArgsConstructor
+@Schema(description = "RegIndex 的展示视图")
+public class RegIndexVO {
+ @Schema(description = "配置项名称,如 kreg_step_motor_pos")
+ private String name;
+}
diff --git a/src/main/java/com/iflytop/handacid/common/result/IResultCode.java b/src/main/java/com/iflytop/handacid/common/result/IResultCode.java
new file mode 100644
index 0000000..84921dc
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/result/IResultCode.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.common.result;
+
+/**
+ * 响应码接口
+ **/
+public interface IResultCode {
+
+ String getCode();
+
+ String getMsg();
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/result/PageResult.java b/src/main/java/com/iflytop/handacid/common/result/PageResult.java
new file mode 100644
index 0000000..70a5cdf
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/result/PageResult.java
@@ -0,0 +1,43 @@
+package com.iflytop.handacid.common.result;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分页响应结构体
+ */
+@Data
+public class PageResult implements Serializable {
+
+ private String code;
+
+ private Data data;
+
+ private String msg;
+
+ public static PageResult success(IPage page) {
+ PageResult result = new PageResult<>();
+ result.setCode(ResultCode.SUCCESS.getCode());
+
+ Data data = new Data<>();
+ data.setList(page.getRecords());
+ data.setTotal(page.getTotal());
+
+ result.setData(data);
+ result.setMsg(ResultCode.SUCCESS.getMsg());
+ return result;
+ }
+
+ @lombok.Data
+ public static class Data {
+
+ private List list;
+
+ private long total;
+
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/common/result/Result.java b/src/main/java/com/iflytop/handacid/common/result/Result.java
new file mode 100644
index 0000000..14e7ff3
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/result/Result.java
@@ -0,0 +1,75 @@
+package com.iflytop.handacid.common.result;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 统一响应结构体
+ **/
+@Data
+public class Result implements Serializable {
+
+ private String code;
+
+ private T data;
+
+ private String msg;
+
+ public static Result success() {
+ return success(null);
+ }
+
+ public static Result success(T data) {
+ Result result = new Result<>();
+ result.setCode(ResultCode.SUCCESS.getCode());
+ result.setMsg(ResultCode.SUCCESS.getMsg());
+ result.setData(data);
+ return result;
+ }
+
+ public static Result failed() {
+ return result(ResultCode.SYSTEM_ERROR.getCode(), ResultCode.SYSTEM_ERROR.getMsg(), null);
+ }
+
+ public static Result failed(String msg) {
+ return result(ResultCode.SYSTEM_ERROR.getCode(), msg, null);
+ }
+
+ public static Result judge(boolean status) {
+ if (status) {
+ return success();
+ } else {
+ return failed();
+ }
+ }
+
+ public static Result failed(IResultCode resultCode) {
+ return result(resultCode.getCode(), resultCode.getMsg(), null);
+ }
+
+ public static Result failed(IResultCode resultCode, String msg) {
+ return result(resultCode.getCode(), StrUtil.isNotBlank(msg) ? msg : resultCode.getMsg(), null);
+ }
+
+ public static Result failed(String code, String msg) {
+ return result(code, msg, null);
+ }
+
+ private static Result result(IResultCode resultCode, T data) {
+ return result(resultCode.getCode(), resultCode.getMsg(), data);
+ }
+
+ private static Result result(String code, String msg, T data) {
+ Result result = new Result<>();
+ result.setCode(code);
+ result.setData(data);
+ result.setMsg(msg);
+ return result;
+ }
+
+ public static boolean isSuccess(Result> result) {
+ return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode());
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/result/ResultCode.java b/src/main/java/com/iflytop/handacid/common/result/ResultCode.java
new file mode 100644
index 0000000..eaa1946
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/result/ResultCode.java
@@ -0,0 +1,96 @@
+package com.iflytop.handacid.common.result;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.io.Serializable;
+
+/**
+ * 统一前端响应码定义
+ */
+@Getter
+@AllArgsConstructor
+public enum ResultCode implements IResultCode, Serializable {
+ //================================ 通用 =================================
+ SUCCESS("0", "成功"),
+ FAILED("-1", "未知错误"),
+
+ //============================ 1xxx:请求 & 参数 ============================
+ INVALID_PARAMETER("1000", "参数无效或缺失"),
+ PARAMETER_TYPE_MISMATCH("1001", "参数类型不匹配"),
+ PARAMETER_OUT_OF_RANGE("1002", "参数超出允许范围"),
+
+ //============================ 2xxx:认证 & 授权 ============================
+ UNAUTHORIZED("2000", "未认证或登录失效"),
+ FORBIDDEN("2001", "无访问权限"),
+ TOKEN_EXPIRED("2002", "Token 已过期"),
+ TOKEN_INVALID("2003", "Token 无效"),
+
+ //============================ 3xxx:资源访问 ============================
+ NOT_FOUND("3000", "资源不存在"),
+ METHOD_NOT_ALLOWED("3001", "不支持的请求方法"),
+
+ //============================ 4xxx:业务错误 ============================
+ USER_NOT_FOUND("4000", "用户不存在"),
+ USER_ALREADY_EXISTS("4001", "用户已存在"),
+ INVALID_CREDENTIALS("4002", "用户名或密码错误"),
+ OPERATION_NOT_ALLOWED("4003", "业务操作不允许"),
+ DATA_ALREADY_EXISTS("4004", "数据已存在"),
+ DATA_ALREADY_NOT_EXISTS("4005", "数据不存在"),
+
+ CRAFT_RUNNING("4101", "工艺正在执行"),
+ CRAFT_CONTEXT_NULL("4102", "请先配置该加热区工艺"),
+ CRAFT_NO_TRAY("4005", "工艺未找到托盘"),
+
+ CONTAINER_NOT_FOUND("4201", "未找到对应溶液容器"),
+ //============================ 5xxx:系统 & 第三方 ============================
+ SYSTEM_ERROR("5000", "系统内部错误"),
+ SERVICE_UNAVAILABLE("5001", "服务暂不可用"),
+ EXTERNAL_API_ERROR("5002", "第三方服务调用失败"),
+ COMMAND_EXEC_TIMEOUT("5003", "命令执行超时"),
+ HARDWARE_ERROR("5004", "硬件错误"),
+ EMERGENCY_STOP("5555", "设备急停中"),
+ //============================ 6xxx:设备、指令相关 ============================
+ COMMAND_NOT_FOUND("6000", "指令未找到"),
+ COMMAND_ALREADY_EXECUTING("6001", "指令正在执行,无法重复执行"),
+ SENSOR_STATUS_FAILED("6010", "获取传感器状态失败"),
+ TARGET_MODULE_OCCUPIED("6021", "目标模块被占用"),
+ TARGET_MODULE_NO_TUBE("6022", "目标模块无试管"),
+ CMD_BUSY("6025", "设备忙,请稍后"),
+ ;
+ /**
+ * 状态码
+ */
+ private final String code;
+ /**
+ * 提示信息
+ */
+ private final String msg;
+
+ @Override
+ public String getCode() {
+ return code;
+ }
+
+ @Override
+ public String getMsg() {
+ return msg;
+ }
+
+ /**
+ * 根据 code 获取枚举
+ */
+ public static ResultCode parse(String code) {
+ for (ResultCode item : values()) {
+ if (item.code.equals(code)) {
+ return item;
+ }
+ }
+ return FAILED;
+ }
+
+ @Override
+ public String toString() {
+ return "{\"code\":\"" + code + "\", \"msg\":\"" + msg + "\"}";
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/service/SolutionsService.java b/src/main/java/com/iflytop/handacid/common/service/SolutionsService.java
new file mode 100644
index 0000000..52d7278
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/service/SolutionsService.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.common.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.iflytop.handacid.common.model.entity.Solutions;
+import com.iflytop.handacid.common.mapper.SolutionsMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 溶液接口服务
+ */
+@Service
+@RequiredArgsConstructor
+public class SolutionsService extends ServiceImpl {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/service/SystemConfigService.java b/src/main/java/com/iflytop/handacid/common/service/SystemConfigService.java
new file mode 100644
index 0000000..2d11e5c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/service/SystemConfigService.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.common.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.iflytop.handacid.common.mapper.SystemConfigMapper;
+import com.iflytop.handacid.common.model.entity.SystemConfig;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统配置接口服务
+ */
+@Service
+@RequiredArgsConstructor
+
+public class SystemConfigService extends ServiceImpl {
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/service/SystemLogService.java b/src/main/java/com/iflytop/handacid/common/service/SystemLogService.java
new file mode 100644
index 0000000..84981c7
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/service/SystemLogService.java
@@ -0,0 +1,17 @@
+package com.iflytop.handacid.common.service;
+
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.iflytop.handacid.common.model.entity.SystemLog;
+import com.iflytop.handacid.common.mapper.SystemLogMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 系统日志接口服务
+ */
+@Service
+@RequiredArgsConstructor
+public class SystemLogService extends ServiceImpl {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/service/UserService.java b/src/main/java/com/iflytop/handacid/common/service/UserService.java
new file mode 100644
index 0000000..bbce801
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/service/UserService.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.common.service;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.iflytop.handacid.common.mapper.UserMapper;
+import com.iflytop.handacid.common.model.entity.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+/**
+ * 用户接口服务
+ */
+@Service
+@RequiredArgsConstructor
+public class UserService extends ServiceImpl {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/utils/ByteArray.java b/src/main/java/com/iflytop/handacid/common/utils/ByteArray.java
new file mode 100644
index 0000000..3bad849
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/utils/ByteArray.java
@@ -0,0 +1,125 @@
+// Source code is decompiled from a .class file using FernFlower decompiler.
+package com.iflytop.handacid.common.utils;
+
+import org.springframework.lang.NonNull;
+
+public class ByteArray {
+ public ByteArray() {
+ }
+
+ public static int readU8bit(byte[] code, int index) {
+ if (index >= code.length)
+ return 0;
+ return code[index] & 255;
+ }
+
+ public static int readS8bit(byte[] code, int index) {
+ if (index >= code.length)
+ return 0;
+
+ return code[index];
+ }
+
+ public static int readU16bit(byte[] code, int index) {
+ if (index + 1 >= code.length)
+ return 0;
+ return (code[index + 1] & 255) << 8 | code[index] & 255;
+ }
+
+ public static void setU16bit(byte[] code, int index, int value) {
+ code[index + 1] = (byte) (value >> 8);
+ code[index] = (byte) value;
+ }
+
+ public static void setU8(byte[] code, int off, int value) {
+ code[off] = (byte) value;
+ }
+
+ public static int readS16bit(byte[] code, int index) {
+ if (index + 1 >= code.length)
+ return 0;
+
+ return code[index + 1] << 8 | code[index] & 255;
+ }
+
+ public static int read32bit(byte[] code, int index) {
+ if (index + 3 >= code.length)
+ return 0;
+ return code[index + 3] << 24 | (code[index + 2] & 255) << 16 | (code[index + 1] & 255) << 8 | code[index] & 255;
+ }
+
+ public static Integer[] read32bitArray(byte[] code) {
+ int count = code.length / 4;
+ Integer[] array = new Integer[count];
+ for (int i = 0; i < count; i++) {
+ array[i] = read32bit(code, i * 4);
+ }
+ return array;
+ }
+
+ public static Integer[] read16bitArray(byte[] code) {
+ int count = code.length / 2;
+ Integer[] array = new Integer[count];
+ for (int i = 0; i < count; i++) {
+ array[i] = readS16bit(code, i * 2);
+ }
+ return array;
+ }
+
+ public static Integer[] readU16bitArray(byte[] code) {
+ int count = code.length / 2;
+ Integer[] array = new Integer[count];
+ for (int i = 0; i < count; i++) {
+ array[i] = readU16bit(code, i * 2);
+ }
+ return array;
+ }
+
+
+ public static String toByteString(byte[] arrary) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : arrary) {
+ sb.append(String.format("%02X", b));
+ }
+ return sb.toString();
+ }
+
+ public static byte[] hexStringToBytes(@NonNull String str) {
+ if (str.isEmpty()) {
+ return new byte[0];
+ } else {
+ byte[] byteArray = new byte[str.length() / 2];
+ for (int i = 0; i < byteArray.length; i++) {
+ int high = Character.digit(str.charAt(i * 2), 16);
+ int low = Character.digit(str.charAt(i * 2 + 1), 16);
+ if (high == -1 || low == -1) {
+ return null;
+ }
+ byteArray[i] = (byte) (high * 16 + low);
+ }
+ return byteArray;
+ }
+ }
+
+
+ public static byte[] concat(byte[]... arrays) {
+ int length = 0;
+ for (byte[] array : arrays) {
+ length += array.length;
+ }
+
+ byte[] result = new byte[length];
+ int destPos = 0;
+ for (byte[] array : arrays) {
+ System.arraycopy(array, 0, result, destPos, array.length);
+ destPos += array.length;
+ }
+
+ return result;
+ }
+
+ // public static void main(String[] args) {
+ // byte[] bytes = new byte[]{0x01, 0x02, 0x03, 0x04};
+ // System.out.println(toByteString(bytes));
+ // }
+}
diff --git a/src/main/java/com/iflytop/handacid/common/utils/CommandUtil.java b/src/main/java/com/iflytop/handacid/common/utils/CommandUtil.java
new file mode 100644
index 0000000..b65703d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/utils/CommandUtil.java
@@ -0,0 +1,56 @@
+package com.iflytop.handacid.common.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 系统命令执行工具类
+ */
+@Slf4j
+public class CommandUtil {
+
+ private static final String DEFAULT_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
+
+ /**
+ * 执行系统命令并返回输出内容
+ *
+ * @param cmd 命令及其参数,如 runCommand("ls", "-l")
+ * @return 命令输出
+ * @throws RuntimeException 命令执行失败或中断
+ */
+ public static String runCommand(String... cmd) {
+ try {
+ log.info("CMD: {}", String.join(" ", cmd));
+ ProcessBuilder pb = new ProcessBuilder(cmd);
+ pb.environment().put("PATH", DEFAULT_PATH); // 设置 PATH 变量,防止找不到命令
+ pb.redirectErrorStream(true); // 合并错误输出
+
+ Process process = pb.start();
+ StringBuilder output = new StringBuilder();
+
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ output.append(line).append('\n');
+ }
+ }
+
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new IllegalStateException(String.format(
+ "命令执行失败: [%s], 退出码=%d, 输出=\n%s",
+ String.join(" ", cmd), exitCode, output));
+ }
+
+ return output.toString().trim(); // 去掉最后一个换行
+ } catch (IOException | InterruptedException e) {
+ Thread.currentThread().interrupt(); // 恢复中断状态
+ throw new RuntimeException("执行系统命令失败: " + String.join(" ", cmd), e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/common/utils/LocalDateTimeUtil.java b/src/main/java/com/iflytop/handacid/common/utils/LocalDateTimeUtil.java
new file mode 100644
index 0000000..70745ed
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/common/utils/LocalDateTimeUtil.java
@@ -0,0 +1,15 @@
+package com.iflytop.handacid.common.utils;
+
+public class LocalDateTimeUtil {
+
+ /**
+ * 秒数格式化为 “mm:ss” 格式。
+ */
+ public static String formatSecondsToHMS(long diffSeconds) {
+ long absSeconds = Math.abs(diffSeconds);
+ long minutes = absSeconds / 60;
+ long seconds = absSeconds % 60;
+ return String.format("%02d:%02d", minutes, seconds);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/hardware/HardwareService.java b/src/main/java/com/iflytop/handacid/hardware/HardwareService.java
new file mode 100644
index 0000000..d089b5f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/HardwareService.java
@@ -0,0 +1,95 @@
+package com.iflytop.handacid.hardware;
+
+
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.app.core.event.CommandFeedbackEvent;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.command.DeviceResponse;
+import com.iflytop.handacid.hardware.command.checker.SupportMethod;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import java.security.InvalidParameterException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ThreadPoolExecutor;
+
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class HardwareService {
+ private final ApplicationEventPublisher eventPublisher;
+ private final ApplicationContext applicationContext;
+ private final Map cmdHandlers = new HashMap<>();
+
+ ThreadPoolExecutor executor = ThreadUtil.newFixedExecutor(50, "hardware-service", true);
+
+
+ @PostConstruct
+ public void postInit() {
+ Map handlers = applicationContext.getBeansOfType(CommandHandler.class);
+ handlers.values().forEach(this::registerCommandHandler);
+ }
+
+ void registerCommandHandler(CommandHandler handler) {
+ Set devices = handler.getSupportedDevices();
+ for (Device device : devices) {
+ cmdHandlers.put(device, handler);
+ }
+ }
+
+ public boolean sendCommand(DeviceCommand cmd) {
+ log.trace("sendCommand: {}", cmd);
+ if (executor.getQueue().remainingCapacity() == 0) {
+ log.error("线程池队列已满,无法提交任务");
+ return false;
+ }
+ executor.submit(() -> handleCommand(cmd));
+ return true;
+ }
+
+ void handleCommand(DeviceCommand cmd) {
+ DeviceResponse response = new DeviceResponse();
+ response.setCmdId(cmd.getCmdId());
+ try {
+ String strMethod = cmd.getCmdCode();
+
+ SupportMethod.checkMethod(strMethod);
+
+ if (cmdHandlers.containsKey(cmd.getDevice())) {
+ log.trace("cmdHandlers: {}", cmd);
+ cmdHandlers.get(cmd.getDevice()).sendCommand(cmd);
+ response.setSuccess(Boolean.TRUE);
+ } else {
+ log.error("不支持的设备类型: {}", cmd.getDevice());
+ throw new InvalidParameterException(StrUtil.format("[Device]: {}", cmd.getDevice()));
+ }
+
+ } catch (HardwareException e) {
+ String errorStr = e.getError().toString();
+ log.error("Hardware ERROR {}", errorStr);
+ response.setSuccess(Boolean.FALSE);
+ response.setErrorMsg(errorStr);
+ } catch (Exception e) {
+ log.error("指令执行失败: {}", e.getMessage(), e);
+ response.setSuccess(Boolean.FALSE);
+ response.setErrorMsg(e.getMessage());
+ }
+ finally {
+ JSONObject jsonResponse = JSONUtil.parseObj(response);
+ eventPublisher.publishEvent(new CommandFeedbackEvent(this, jsonResponse));
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/comm/can/A8kCanBusConnection.java b/src/main/java/com/iflytop/handacid/hardware/comm/can/A8kCanBusConnection.java
new file mode 100644
index 0000000..c061fcd
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/comm/can/A8kCanBusConnection.java
@@ -0,0 +1,371 @@
+package com.iflytop.handacid.hardware.comm.can;
+
+import com.iflytop.handacid.common.utils.ByteArray;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.factory.A8kPacketFactory;
+import com.iflytop.handacid.hardware.service.AppEventBusService;
+import com.iflytop.handacid.hardware.type.A8kPacket;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.ModuleStatus;
+import com.iflytop.handacid.hardware.type.appevent.A8kCanBusOnConnectEvent;
+import com.iflytop.handacid.hardware.type.error.A8kEcode;
+import com.iflytop.handacid.hardware.type.error.AEHardwareError;
+import com.iflytop.handacid.hardware.utils.OS;
+import com.iflytop.handacid.hardware.utils.ZList;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.enums.ReadyState;
+import org.java_websocket.handshake.ServerHandshake;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+@Slf4j
+public class A8kCanBusConnection extends WebSocketClient {
+ @Resource
+ AppEventBusService eventBus;
+
+ String datachurl = null;
+ String cmdchurl = null;
+ Boolean firstCall = true;
+
+ @Value("${device.enableCanBus}")
+ Boolean enableCanBus;
+
+
+ static class ProcessContext {
+ BlockingQueue receiptQueue = new LinkedBlockingQueue<>(); //
+ A8kPacket cmdPacket;
+ Map txcmdcache = new HashMap<>();
+
+
+ public A8kPacket getReceipt(int overtime) {
+ long end = System.currentTimeMillis() + overtime;
+ A8kPacket packet = null;
+ while (System.currentTimeMillis() < end) {
+ try {
+ packet = receiptQueue.poll(10, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ if (packet != null) {
+ return packet;
+ }
+ }
+
+ synchronized (this) {
+ try {
+ packet = receiptQueue.poll(1, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ receiptQueue.clear();
+ return packet;
+ }
+ }
+
+ synchronized void pushReceipt(A8kPacket receipt) {
+ if (this.cmdPacket != null && this.cmdPacket.getPacketIndex() == receipt.getPacketIndex()) {
+ if (this.cmdPacket.getModuleId() == receipt.getModuleId() && this.cmdPacket.getCmdId() == receipt.getCmdId()) {
+ receiptQueue.add(receipt);
+ this.cmdPacket = null;
+ } else {
+ log.warn("RX index equal, but cmdId not equal, {} {}", this.cmdPacket, receipt);
+ }
+ } else {
+ log.warn("RX unmatched index receipt, {} {}", this.cmdPacket, receipt);
+ log.warn(".");
+ }
+ }
+
+ synchronized void setWaitingReceiptIndex(A8kPacket cmdPacket) {
+ receiptQueue.clear();
+ this.cmdPacket = cmdPacket;
+ }
+
+
+ synchronized void storageTxLastCmd(A8kPacket pack) {
+ MId mid = MId.valueOf(pack.getModuleId());
+ CmdId cmdId = CmdId.valueOf(pack.getCmdId());
+ if (cmdId != null && cmdId.isActionCmd())
+ txcmdcache.put(mid, cmdId);
+ }
+
+ synchronized CmdId getLastTxCmd(MId mid) {
+ return txcmdcache.get(mid);
+ }
+ }
+
+
+ ProcessContext context = new ProcessContext();
+ int packetIndex = 0;//发送包的packetIndex
+
+
+ public A8kCanBusConnection(String cmdchurl, String datachurl) {
+ super(URI.create(datachurl));
+ log.info("new A8kCanBusConnection: {} {}", cmdchurl, datachurl);
+
+ this.datachurl = datachurl;
+ this.cmdchurl = cmdchurl;
+ packetIndex = 0;
+
+ }
+
+ @Override public void onOpen(ServerHandshake serverHandshake) {
+ log.info("a8k canbus connect sucess");
+ eventBus.pushEvent(new A8kCanBusOnConnectEvent());
+ }
+
+ @Override public void onMessage(String s) {
+ log.trace("RX-RAW: {}", s);
+ processCanRxMessage(s);
+ }
+
+ @Override public void onClose(int i, String s, boolean b) {
+ log.warn("a8k canbus lost connection...");
+ }
+
+ @Override public void onError(Exception e) {
+ log.info("a8k can-websocket-channel on error");
+ }
+
+
+ synchronized public A8kPacket callcmd(MId moduleId, CmdId cmdId, Integer... param) throws HardwareException {
+ var packet = packParamsToPacket(moduleId.toInt(), cmdId.toInt(), ZList.of(param));
+ return autoReSend(packet, A8kPacket.CMD_OVERTIME);
+ }
+
+ synchronized public A8kPacket callcmd2(MId moduleId, CmdId cmdId, Integer overtime, Integer... params) throws HardwareException {
+ var packet = packParamsToPacket(moduleId.toInt(), cmdId.toInt(), ZList.of(params));
+ return autoReSend(packet, overtime);
+ }
+
+ synchronized public A8kPacket send(A8kPacket pack, int overtime) throws HardwareException {
+ return priSend(pack, overtime);
+ }
+
+ synchronized public ModuleStatus moduleGetStatus(MId id) throws HardwareException {
+ var getStatusPacket = packParamsToPacket(id.toInt(), CmdId.module_get_status.toInt(), ZList.of());
+ for (int i = 0; i < 10; i++) {
+ try {
+ return ModuleStatus.valueOf(priSend(getStatusPacket, 30).getContentI32(0));
+ } catch (HardwareException ignored) {
+ }
+ restartCanif();
+ OS.hsleep(10);
+ }
+ throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME,
+ MId.valueOf(getStatusPacket.getModuleId()), CmdId.valueOf(getStatusPacket.getCmdId())));
+
+ }
+
+ synchronized public Boolean ping(MId id) {
+ var getStatusPacket = packParamsToPacket(id.toInt(), CmdId.module_ping.toInt(), ZList.of());
+ try {
+ priSend(getStatusPacket, 30);
+ } catch (HardwareException e) {
+ return false;
+ }
+ return true;
+ }
+
+ //
+ // PRIVATE
+ //
+ @Scheduled(fixedRate = 2000)
+ private void autoConnect() {
+ if (!enableCanBus)
+ return;
+
+ if (!isOpen()) {
+ if (getReadyState().equals(ReadyState.NOT_YET_CONNECTED)) {
+ try {
+ log.warn("canbus connect ...");
+ connect();
+ log.warn("canbus connect success");
+ } catch (IllegalStateException ignored) {
+ log.warn("a8k canbus connect failed, maybe already connected");
+ } catch (Exception e) {
+ log.error("a8k canbus connect error", e);
+ // 连接失败,等待重试
+ return;
+ }
+ } else if (getReadyState().equals(ReadyState.CLOSED)) {
+ log.warn("a8k canbus connection closed, try to reconnect");
+ reconnect();
+ }
+ }
+ }
+
+ /**
+ * 强制关闭websocket连接,让其与canbus服务重连
+ */
+ private A8kPacket packParamsToPacket(Integer moduleId, Integer cmdId, List params) {
+ return A8kPacketFactory.buildCMDPacket(moduleId, cmdId, params);
+ }
+
+
+ private A8kPacket autoReSend(A8kPacket pack, int overtime) throws HardwareException {
+ if (firstCall) {
+ firstCall = false;
+ restartCanif();
+ }
+
+ for (int i = 0; i < 5; i++) {
+ try {
+ return this.priSend(pack, overtime);
+ } catch (HardwareException e) {
+ if (!e.error.code.equals(A8kEcode.LOW_ERROR_OVERTIME)) {
+ throw e;
+ }
+ }
+ restartCanif();
+ OS.hsleep(50);
+
+
+ log.error("send cmd {} {} fail, retry {}", pack, pack.toByteString(), i);
+ }
+ throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, MId.valueOf(pack.getModuleId()), CmdId.valueOf(pack.getCmdId())));
+ }
+
+
+ private A8kPacket priSend(A8kPacket pack, int overtime) throws HardwareException {
+ try {
+ return _priSend(pack, overtime);
+ } catch (HardwareException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("priSend error", e);
+ throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, MId.valueOf(pack.getModuleId()), CmdId.valueOf(pack.getCmdId())));
+ }
+ }
+
+
+ HardwareException buildOvertimeError(A8kPacket pack) {
+ return HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, MId.valueOf(pack.getModuleId()), CmdId.valueOf(pack.getCmdId())));
+ }
+
+ private A8kPacket _priSend(A8kPacket pack, int overtime) throws HardwareException {
+ // alloc new packetIndex
+ packetIndex = packetIndex + 1;
+ if (packetIndex > 30000) {
+ packetIndex = 1;
+ }
+
+ //set waiting receipt index
+ pack.setPacketIndex(packetIndex);
+ context.storageTxLastCmd(pack);
+ context.setWaitingReceiptIndex(pack);
+
+ // TX packet
+ String txpacket = pack.toByteString();
+ log.trace("TX-RAW: {} | {}", txpacket, pack);
+ send(txpacket);
+
+
+ A8kPacket receipt;
+ receipt = context.getReceipt(overtime);
+ if (receipt == null) {
+ throw buildOvertimeError(pack);
+ }
+
+ if (receipt.getPacketType() == A8kPacket.PACKET_TYPE_ERROR_ACK) {
+ throw HardwareException.of(new AEHardwareError(A8kEcode.fromInt(receipt.getContentI32(0)),
+ MId.valueOf(pack.getModuleId()),
+ CmdId.valueOf(pack.getCmdId()),
+ txpacket,
+ receipt.toByteString()
+ )
+ );
+ }
+ return receipt;
+ }
+
+ /**
+ * 处理接收到的消息
+ */
+ private void processCanRxMessage(String s) {
+ byte[] rx = ByteArray.hexStringToBytes(s);
+ if (rx == null || rx.length == 0) {
+ log.warn("rx is empty");
+ return;
+ }
+
+ if (rx.length < A8kPacket.PACKET_MIN_LEN) {
+ log.warn("rx is too short,{}", s);
+ return;
+ }
+
+ A8kPacket packet = new A8kPacket(rx);
+
+ if (packet.getCheckcode() != packet.computeCheckcode()) {
+ log.warn("Rx packet checkcode error: {}, expect: {} but got: {}", packet, packet.computeCheckcode(), packet.getCheckcode());
+ return;
+ }
+
+ if (!packet.isSupportPacket()) {
+ log.warn("Rx packet not support: {}", packet);
+ return;
+ }
+
+
+ if (packet.getPacketType() == A8kPacket.PACKET_TYPE_ACK || packet.getPacketType() == A8kPacket.PACKET_TYPE_ERROR_ACK) {
+ log.trace("RX-ACK |RAW:{}| {}", s, packet);
+ context.pushReceipt(packet);
+ } else if (packet.getPacketType() == A8kPacket.PACKET_TYPE_EVENT) {
+ log.trace("RX-REPORT |RAW:{}| {}", s, packet);
+// eventBus.pushEvent(new A8kHardwareReport(packet));
+ } else {
+ log.warn("RX-UNPROCESSABLE: |RAW:{}| {}", s, packet);
+ }
+ }
+
+ public synchronized CmdId getLastTxCmd(MId mid) {
+ return context.getLastTxCmd(mid);
+ }
+
+ public void restartCanif() {
+ callLocalCmd("restart");
+ }
+
+
+ public HttpResponse callLocalCmd(String path) {
+ // 创建一个HttpClient实例
+ HttpClient httpClient = HttpClient.newHttpClient();
+ HttpRequest httpRequest = HttpRequest.newBuilder().uri(URI.create(String.format("%s/%s", cmdchurl, path))).build();
+ HttpResponse ret = null;
+ try {
+ ret = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ } catch (Exception e) {
+ log.error("callcmd error", e);
+ return null;
+ }
+ return ret;
+ }
+
+ public static void main(String[] args) {
+ HttpClient httpClient = HttpClient.newHttpClient();
+ HttpRequest httpRequest = HttpRequest.newBuilder().uri(URI.create("http://192.168.8.10:19004/zexcan/restart")).build();
+ HttpResponse ret = null;
+ try {
+ ret = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ log.info("ret: \t{}", ret.body());
+ } catch (Exception e) {
+ log.error("callcmd error", e);
+ }
+ }
+
+}
+
+
diff --git a/src/main/java/com/iflytop/handacid/hardware/comm/can/A8kCanBusService.java b/src/main/java/com/iflytop/handacid/hardware/comm/can/A8kCanBusService.java
new file mode 100644
index 0000000..a61379e
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/comm/can/A8kCanBusService.java
@@ -0,0 +1,132 @@
+package com.iflytop.handacid.hardware.comm.can;
+
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.*;
+import com.iflytop.handacid.hardware.type.error.A8kEcode;
+import com.iflytop.handacid.hardware.type.error.AEHardwareError;
+import com.iflytop.handacid.hardware.utils.OS;
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.net.URISyntaxException;
+
+@Component
+@Slf4j
+public class A8kCanBusService {
+ @Resource
+ A8kCanBusConnection connection;
+
+ @Value("${device.enableCanBus}")
+ Boolean enableCanBus;
+
+ @PostConstruct
+ public void init() throws URISyntaxException {
+ if (enableCanBus) {
+ connection.connect();
+ }else{
+ log.warn("canBus is disabled");
+ }
+ }
+
+ public Boolean ping(MId id) {
+ return connection.ping(id);
+ }
+
+ /**
+ * 强制关闭websocket连接,让其与canbus服务重连
+ */
+ public void forceShutdown() {
+ connection.close();
+ }
+
+ //
+ // BASE_OPERATION
+ //
+
+ public A8kPacket callcmd(MId moduleId, CmdId cmdId, Integer... param) throws HardwareException {
+ return connection.callcmd(moduleId, cmdId, param);
+ }
+
+ public A8kPacket callcmd2(MId moduleId, CmdId cmdId, Integer overtime, Integer... params) throws HardwareException {
+ return connection.callcmd2(moduleId, cmdId, overtime, params);
+ }
+
+ synchronized public A8kPacket send(A8kPacket pack, int overtime) throws HardwareException {
+ return connection.send(pack, overtime);
+ }
+
+ //
+ // MODULE FUNCTION
+ //
+
+ public void moduleStop(MId id) throws HardwareException {
+ connection.callcmd(id, CmdId.module_stop);
+ }
+
+ public void moduleStopNoException(MId id) {
+ try {
+ moduleStop(id);
+ } catch (HardwareException ignored) {
+ }
+ }
+
+ public ModuleStatus moduleGetStatus(MId id) throws HardwareException {
+ return connection.moduleGetStatus(id);
+ }
+
+ public void moduleSetReg(MId id, RegIndex regindex, Integer reg) throws HardwareException {
+ connection.callcmd2(id, CmdId.module_set_reg, 100, regindex.index, reg);
+ }
+
+ public Integer moduleGetReg(MId id, RegIndex regindex) throws HardwareException {
+ var packet = connection.callcmd2(id, CmdId.module_get_reg, 100, regindex.index);
+ return packet.getContentI32(0);
+ }
+
+ public A8kEcode moduleGetError(MId id) throws HardwareException {
+ var packet = connection.callcmd(id, CmdId.module_get_error);
+ return A8kEcode.fromInt(packet.getContentI32(0));
+ }
+
+ public Integer moduleGetDetailError(MId id) throws HardwareException {
+ return connection.callcmd(id, CmdId.module_get_detail_error).getContentI32(0);
+ }
+
+ public Integer moduleReadVersion(MId id) throws HardwareException {
+ return connection.callcmd(id, CmdId.module_get_version).getContentI32(0);
+ }
+
+ public ModuleType moduleReadType(MId id) throws HardwareException {
+ var packet = connection.callcmd(id, CmdId.module_get_type);
+ return ModuleType.of(packet.getContentI32(0));
+ }
+
+ public void waitForMod(MId mid, Integer acitionOvertime) throws HardwareException {
+ long startedAt = System.currentTimeMillis();
+ CmdId action = connection.getLastTxCmd(mid);
+ do {
+ ModuleStatus statu = moduleGetStatus(mid);
+ if (statu == ModuleStatus.IDLE) {
+ break;
+ } else if (statu == ModuleStatus.ERROR) {
+ log.error("{} waitting for action {} , catch error {}, defail ecode {}", mid, action, moduleGetError(mid), moduleGetDetailError(mid));
+ throw HardwareException.of(new AEHardwareError(moduleGetError(mid), mid, action));
+ }
+ long now = System.currentTimeMillis();
+ if (now - startedAt > acitionOvertime) {
+ log.error("{} waitting for action {} overtime({})", mid, action, acitionOvertime);
+ moduleStopNoException(mid);
+ throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_ACTION_OVERTIME, mid, action));
+ }
+ OS.hsleep(20);
+ } while (true);
+ }
+
+ public boolean isConnect() {
+ return connection.isOpen();
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/comm/modbus/JSerialCommWrapper.java b/src/main/java/com/iflytop/handacid/hardware/comm/modbus/JSerialCommWrapper.java
new file mode 100644
index 0000000..19db65f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/comm/modbus/JSerialCommWrapper.java
@@ -0,0 +1,85 @@
+package com.iflytop.handacid.hardware.comm.modbus;
+
+import com.fazecast.jSerialComm.SerialPort;
+import com.serotonin.modbus4j.serial.SerialPortWrapper;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@Slf4j
+public class JSerialCommWrapper implements SerialPortWrapper {
+ private final int baudRate;
+ private final int dataBits;
+ private final int stopBits;
+ private final int parity;
+
+ private final SerialPort serialPort;
+
+ public JSerialCommWrapper(String portName, int baudRate, int dataBits, int stopBits, int parity) {
+ this.baudRate = baudRate;
+ this.dataBits = dataBits;
+ this.stopBits = stopBits;
+ this.parity = parity;
+
+ serialPort = SerialPort.getCommPort(portName);
+ serialPort.setBaudRate(this.baudRate);
+ serialPort.setNumDataBits(this.dataBits);
+ serialPort.setNumStopBits(this.stopBits);
+ serialPort.setParity(this.parity);
+ }
+
+ @Override
+ public void open() throws Exception {
+ if (!serialPort.openPort()) {
+ log.error("Could not open serial port {}", serialPort.getSystemPortName());
+ throw new IOException("Failed to open serial port: " + serialPort.getSystemPortName());
+ }
+ else{
+ log.info("Opened serial port: " + serialPort.getSystemPortName());
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ if (serialPort != null) {
+ serialPort.closePort();
+ log.info("Closed serial port: {}", serialPort.getSystemPortName());
+ }
+ else
+ {
+ log.warn("Could not close serial port: {} null", serialPort.getSystemPortName());
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return serialPort.getInputStream();
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return serialPort.getOutputStream();
+ }
+
+ @Override
+ public int getBaudRate() {
+ return this.baudRate;
+ }
+
+ @Override
+ public int getStopBits() {
+ return this.stopBits;
+ }
+
+ @Override
+ public int getParity() {
+ return this.parity;
+ }
+
+ @Override
+ public int getDataBits() {
+ return this.dataBits;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/comm/modbus/ModbusMasterFactory.java b/src/main/java/com/iflytop/handacid/hardware/comm/modbus/ModbusMasterFactory.java
new file mode 100644
index 0000000..fe36e1b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/comm/modbus/ModbusMasterFactory.java
@@ -0,0 +1,13 @@
+package com.iflytop.handacid.hardware.comm.modbus;
+
+import com.serotonin.modbus4j.ModbusFactory;
+import com.serotonin.modbus4j.ModbusMaster;
+
+public class ModbusMasterFactory {
+ static final ModbusFactory factory = new ModbusFactory();
+
+ public static ModbusMaster createModbusRtuMaster(String portName, int baudRate) {
+ JSerialCommWrapper wrapper = new JSerialCommWrapper(portName, baudRate, 8, 1, 0);
+ return factory.createRtuMaster(wrapper);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/comm/modbus/ModbusMasterService.java b/src/main/java/com/iflytop/handacid/hardware/comm/modbus/ModbusMasterService.java
new file mode 100644
index 0000000..2526068
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/comm/modbus/ModbusMasterService.java
@@ -0,0 +1,283 @@
+package com.iflytop.handacid.hardware.comm.modbus;
+
+import com.serotonin.modbus4j.ModbusMaster;
+import com.serotonin.modbus4j.msg.*;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ModbusMasterService {
+ private ModbusMaster modbusMaster_;
+ private final ReentrantLock lock = new ReentrantLock();
+
+ @Value("${modbus.port}")
+ private String port;
+
+ @Value("${modbus.baudrate}")
+ private int baudrate;
+
+ @PostConstruct
+ public void init() {
+ modbusMaster_ = ModbusMasterFactory.createModbusRtuMaster(port, baudrate);
+ log.info("============== ============== ============== ============== ==============");
+ log.info("============== ============== ============== ============== ==============");
+ try {
+ this.connect();
+ log.info("Modbus master 端口: {} 波特率:{} initialized successfully.", port, baudrate);
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to initialize Modbus master: {} 端口: {} 波特率: {}",
+ e.getMessage(), port, baudrate);
+ }
+ log.info("============== ============== ============== ============== ==============");
+ log.info("============== ============== ============== ============== ==============");
+ }
+
+ /**
+ * 检查连接状态
+ */
+ private boolean isConnectionInvalid() {
+ if (modbusMaster_ == null) {
+ log.error("Modbus master is not initialized.");
+ return true;
+ }
+// if (!modbusMaster_.isConnected()) {
+// log.error("Modbus master is not connected.");
+// return true;
+// }
+ return false;
+ }
+
+ // ==== ==== ==== ==== ==== ==== Modbus 功能封装 ==== ==== ==== ==== ==== ====
+
+ /**
+ * 读取线圈状态 (功能码: 1, READ_COILS)
+ */
+ public boolean[] readCoils(int slaveId, int startAddress, int length) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return null;
+ ReadCoilsRequest request = new ReadCoilsRequest(slaveId, startAddress, length);
+ ReadCoilsResponse response = (ReadCoilsResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error reading coils: {}", response.getExceptionMessage());
+ return null;
+ }
+ return response.getBooleanData();
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to read coils: {}", e.getMessage());
+ return null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 读取离散输入 (功能码: 2, READ_DISCRETE_INPUTS)
+ */
+ public boolean[] readDiscreteInputs(int slaveId, int startAddress, int length) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return null;
+ ReadDiscreteInputsRequest request = new ReadDiscreteInputsRequest(slaveId, startAddress, length);
+ ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error reading discrete inputs: {}", response.getExceptionMessage());
+ return null;
+ }
+ return response.getBooleanData();
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to read discrete inputs: {}", e.getMessage());
+ return null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 读取保持寄存器 (功能码: 3, READ_HOLDING_REGISTERS)
+ */
+ public short[] readHoldingRegisters(int slaveId, int startAddress, int length) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return null;
+ ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, startAddress, length);
+ ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error reading holding registers: {}", response.getExceptionMessage());
+ return null;
+ }
+ return response.getShortData();
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to read holding registers: {}", e.getMessage());
+ return null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 读取输入寄存器 (功能码: 4, READ_INPUT_REGISTERS)
+ */
+ public short[] readInputRegisters(int slaveId, int startAddress, int length) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return null;
+ ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, startAddress, length);
+ ReadInputRegistersResponse response = (ReadInputRegistersResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error reading input registers: {}", response.getExceptionMessage());
+ return null;
+ }
+ return response.getShortData();
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to read input registers: {}", e.getMessage());
+ return null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 写单个线圈 (功能码: 5, WRITE_COIL)
+ */
+ public boolean writeCoil(int slaveId, int address, boolean value) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return false;
+ WriteCoilRequest request = new WriteCoilRequest(slaveId, address, value);
+ WriteCoilResponse response = (WriteCoilResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error writing coil: {}", response.getExceptionMessage());
+ return false;
+ }
+ return true;
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to write coil: {}", e.getMessage());
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 写单个寄存器 (功能码: 6, WRITE_REGISTER)
+ */
+ public boolean writeRegister(int slaveId, int address, int value) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return false;
+ WriteRegisterRequest request = new WriteRegisterRequest(slaveId, address, value);
+ WriteRegisterResponse response = (WriteRegisterResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error writing register: {}", response.getExceptionMessage());
+ return false;
+ }
+ return true;
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to write register: {}", e.getMessage());
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 写多个线圈 (功能码: 15, WRITE_COILS)
+ */
+ public boolean writeCoils(int slaveId, int startAddress, boolean[] values) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return false;
+ WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startAddress, values);
+ WriteCoilsResponse response = (WriteCoilsResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error writing coils: {}", response.getExceptionMessage());
+ return false;
+ }
+ return true;
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to write coils: {}", e.getMessage());
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 写多个寄存器 (功能码: 16, WRITE_REGISTERS)
+ */
+ public boolean writeRegisters(int slaveId, int startAddress, short[] values) {
+ lock.lock();
+ try {
+ if (isConnectionInvalid()) return false;
+ WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startAddress, values);
+ WriteRegistersResponse response = (WriteRegistersResponse) modbusMaster_.send(request);
+ if (response.isException()) {
+ modbusMaster_.setConnected(false);
+ log.error("Error writing registers: {}", response.getExceptionMessage());
+ return false;
+ }
+ return true;
+ } catch (Exception e) {
+ modbusMaster_.setConnected(false);
+ log.error("Failed to write registers: {}", e.getMessage());
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ // ==== ==== ==== ==== ==== ==== 自动重连 ==== ==== ==== ==== ==== ====
+ void connect() throws Exception
+ {
+ modbusMaster_.init();
+ modbusMaster_.setRetries(3);
+// modbusMaster_.setIoLog(new BasicLogger());
+ modbusMaster_.setTimeout(2000);
+ modbusMaster_.setConnected(true);
+ }
+
+ /**
+ * 自动重连
+ */
+ @Scheduled(fixedRate = 1000)
+ public void autoReconnect() {
+ lock.lock();
+ try {
+ if(modbusMaster_ ==null || !modbusMaster_.isInitialized() || modbusMaster_.isConnected())
+ return;
+
+ this.connect();
+
+ log.info("Modbus master 重连成功 端口: {} 波特率:{}.", port, baudrate);
+ } catch (Exception e) {
+ log.error("Modbus master : {}", e.getMessage());
+ log.info("Modbus master 重连失败 端口: {} 波特率:{}. {}", port, baudrate, e.getMessage());
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/CommandHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/CommandHandler.java
new file mode 100644
index 0000000..f1a9aac
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/CommandHandler.java
@@ -0,0 +1,70 @@
+package com.iflytop.handacid.hardware.command;
+
+import cn.hutool.core.util.StrUtil;
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.hardware.type.MId;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 命令处理器
+ * H - 硬件 C - 命令
+ */
+public abstract class CommandHandler {
+
+ abstract protected Map getSupportCmdDeviceMIdMap();
+ abstract protected Set getSupportActions();
+
+ /**
+ * 发送指令
+ */
+ public void sendCommand(DeviceCommand command) throws Exception
+ {
+ // 校验动作是否合法
+ checkAction(command.getAction());
+ // 校验参数是否合法
+ checkParams(command);
+
+ // 处理指令
+ handleCommand(command);
+ }
+
+ abstract protected void handleCommand(DeviceCommand command) throws Exception;
+
+ /**
+ * 获取支持的设备
+ */
+ public Set getSupportedDevices() {
+ Map cmdDeviceMIdMap = getSupportCmdDeviceMIdMap();
+ return cmdDeviceMIdMap.keySet();
+ }
+
+ /**
+ * 检查Action 是否合法
+ * @param action
+ */
+ protected void checkAction(Action action) throws Exception
+ {
+ Set supportedActions = getSupportActions();
+ if (!supportedActions.contains(action)) {
+ // 生成支持的动作列表字符串
+ String supported = supportedActions.stream()
+ .map(Enum::name)
+ .collect(Collectors.joining(", "));
+
+ throw new IllegalArgumentException(
+ StrUtil.format("action [{}] not supported! Supported actions: {}",
+ action.name(), supported));
+ }
+ }
+
+ /**
+ * 检查参数是否合法
+ */
+ protected void checkParams(DeviceCommand command) throws Exception
+ {}
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/DeviceResponse.java b/src/main/java/com/iflytop/handacid/hardware/command/DeviceResponse.java
new file mode 100644
index 0000000..19882f7
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/DeviceResponse.java
@@ -0,0 +1,12 @@
+package com.iflytop.handacid.hardware.command;
+
+import cn.hutool.json.JSONObject;
+import lombok.Data;
+
+@Data
+public class DeviceResponse {
+ Integer cmdId;
+ Boolean success;
+ String errorMsg;
+ JSONObject data;
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/checker/SupportMethod.java b/src/main/java/com/iflytop/handacid/hardware/command/checker/SupportMethod.java
new file mode 100644
index 0000000..5f4587d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/checker/SupportMethod.java
@@ -0,0 +1,31 @@
+package com.iflytop.handacid.hardware.command.checker;
+
+public class SupportMethod {
+ public enum CommandMethod {
+ CMD_CONTROL("controlCmd"),
+ CMD_CONTROL_MOTOR("controlMotorCmd"),
+ CMD_GET_INFO("getInfoCmd"),
+ CMD_SET_INFO("setInfoCmd");
+
+ private final String cmdName;
+ CommandMethod(String commandName) {
+ this.cmdName = commandName;
+ }
+ public String getCmdName() {
+ return cmdName;
+ }
+ }
+
+ public static void checkMethod(String strMethod) {
+ if (strMethod == null || strMethod.isEmpty()) {
+ throw new NoSuchMethodError("* Empty Method *");
+ }
+
+ for (CommandMethod type : CommandMethod.values()) {
+ if (type.getCmdName().equals(strMethod)) {
+ return;
+ }
+ }
+ throw new NoSuchMethodError(strMethod);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/handlers/CeramicPumpHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/handlers/CeramicPumpHandler.java
new file mode 100644
index 0000000..6c4576c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/handlers/CeramicPumpHandler.java
@@ -0,0 +1,85 @@
+package com.iflytop.handacid.hardware.command.handlers;
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.common.enums.MotorDirection;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.drivers.CeramicPumpDriver;
+import com.iflytop.handacid.hardware.type.CeramicPumpDriverSlaveId;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.StepMotor.MotorDirect;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.lang.Math.abs;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CeramicPumpHandler extends CommandHandler {
+ private final CeramicPumpDriver driver_;
+
+ private final Map stepMotorMIdMap_ = Map.ofEntries(
+ Map.entry(Device.CERAMIC_PUMP_1, CeramicPumpDriverSlaveId.CERAMIC_PUMP1_ID),
+ Map.entry(Device.CERAMIC_PUMP_2, CeramicPumpDriverSlaveId.CERAMIC_PUMP2_ID)
+ );
+ private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream().
+ collect(Collectors.toMap(Map.Entry::getKey, entry -> MId.NotSet));
+
+ private final Set supportActions = Set.of(
+ Action.SET, Action.CLOSE_CLAMP,
+ Action.MOVE_BY,
+ Action.STOP);
+
+ @Override
+ protected Map getSupportCmdDeviceMIdMap() {
+ return supportCmdDeviceMIdMap;
+ }
+
+ @Override
+ protected Set getSupportActions() {
+ return supportActions;
+ }
+
+ public MotorDirect getMotorDirect(MotorDirection cmdDirection)
+ {
+ return switch (cmdDirection) {
+ case FORWARD -> MotorDirect.FORWARD;
+ case BACKWARD -> MotorDirect.BACKWARD;
+ };
+ }
+
+ @Override
+ public void handleCommand(DeviceCommand command) throws Exception {
+ // 发送命令
+ CeramicPumpDriverSlaveId slaveId = stepMotorMIdMap_.get(command.getDevice());
+
+ switch (command.getAction()) {
+ case MOVE_BY -> {
+ double distance = command.getParam().getPosition();
+ log.info("Motor {} Move By Distance {}", slaveId, distance);
+ CeramicPumpDriver.Direction direction = distance < 0 ?
+ CeramicPumpDriver.Direction.BACKWARD : CeramicPumpDriver.Direction.FORWARD;
+ driver_.moveByBlock(slaveId, direction, abs((int)(distance)));
+ }
+ case STOP -> {
+ log.info("Motor {} Stop", slaveId);
+ driver_.stopBlock(slaveId);
+ }
+ case SET -> {
+ Double speed = command.getParam().getSpeed();
+ if (speed != null) {
+ double d_speed = speed;
+ log.info("Motor {} Set Speed {}", slaveId, speed);
+ driver_.setSpeed(slaveId, (int)d_speed);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/handlers/HeatRodHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/handlers/HeatRodHandler.java
new file mode 100644
index 0000000..08d458c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/handlers/HeatRodHandler.java
@@ -0,0 +1,71 @@
+package com.iflytop.handacid.hardware.command.handlers;
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.drivers.TecHeaterRodDriver;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.TecHeaterRodMId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Set;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class HeatRodHandler extends CommandHandler {
+ private final TecHeaterRodDriver driver_;
+
+ private final Map supportCmdDeviceTecHeaterRodMIdMap = Map.ofEntries(
+ Map.entry(Device.HEAT_ROD_1, TecHeaterRodMId.HeaterRod1),
+ Map.entry(Device.HEAT_ROD_2, TecHeaterRodMId.HeaterRod2)
+ );
+
+ private final Map supportCmdDeviceMIdMap = Map.ofEntries(
+ Map.entry(Device.HEAT_ROD_1, MId.HeaterRod1),
+ Map.entry(Device.HEAT_ROD_2, MId.HeaterRod2)
+ );
+
+ private final Set supportActions = Set.of(Action.OPEN, Action.CLOSE);
+
+ @Override
+ protected Map getSupportCmdDeviceMIdMap()
+ {
+ return supportCmdDeviceMIdMap;
+ }
+
+ @Override
+ protected Set getSupportActions() {
+ return supportActions;
+ }
+
+ @Override
+ protected void checkParams(DeviceCommand command) throws Exception {
+ // 检查参数
+ if (command.getAction() == Action.OPEN) {
+ // 检查参数
+ }
+
+ }
+
+ MId getMId(Device cmdDevice){
+ return supportCmdDeviceMIdMap.get(cmdDevice);
+ }
+
+ @Override
+ public void handleCommand(DeviceCommand command) throws Exception {
+ // 发送命令
+
+ if (command.getAction() == Action.OPEN) {
+ Double temp = command.getParam().getTemperature();
+ driver_.open(supportCmdDeviceTecHeaterRodMIdMap.get(command.getDevice()), temp);
+
+ } else if (command.getAction() == Action.CLOSE) {
+ driver_.close(supportCmdDeviceTecHeaterRodMIdMap.get(command.getDevice()));
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/handlers/IOCtrlHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/handlers/IOCtrlHandler.java
new file mode 100644
index 0000000..40949d5
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/handlers/IOCtrlHandler.java
@@ -0,0 +1,55 @@
+package com.iflytop.handacid.hardware.command.handlers;
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.drivers.OutputIOCtrlDriver;
+import com.iflytop.handacid.hardware.type.IO.OutputIOMId;
+import com.iflytop.handacid.hardware.type.MId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+//
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class IOCtrlHandler extends CommandHandler {
+ private final OutputIOCtrlDriver driver_;
+ private final Map supportCmdDeviceIOOutputMap = Map.ofEntries(
+ );
+
+ private final Map supportCmdDeviceMIdMap = supportCmdDeviceIOOutputMap.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid));
+
+ private final Set supportActions = Set.of(Action.OPEN, Action.CLOSE);
+
+ @Override
+ protected Map getSupportCmdDeviceMIdMap()
+ {
+ return supportCmdDeviceMIdMap;
+ }
+
+ @Override
+ protected Set getSupportActions() {
+ return supportActions;
+ }
+
+ @Override
+ public void handleCommand(DeviceCommand command) throws Exception {
+ // 发送命令
+ if (command.getAction() == Action.OPEN) {
+ log.info("open {}", command.getDevice());
+ driver_.open(supportCmdDeviceIOOutputMap.get(command.getDevice()));
+ } else if (command.getAction() == Action.CLOSE) {
+ log.info("close {}", command.getDevice());
+ driver_.close(supportCmdDeviceIOOutputMap.get(command.getDevice()));
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/handlers/MiniServoHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/handlers/MiniServoHandler.java
new file mode 100644
index 0000000..259b5d6
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/handlers/MiniServoHandler.java
@@ -0,0 +1,61 @@
+package com.iflytop.handacid.hardware.command.handlers;
+
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.drivers.MiniServoDriverWrapper;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.Servo.MiniServoMId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MiniServoHandler extends CommandHandler {
+ private final MiniServoDriverWrapper driver_;
+
+ private final Map servoMIdMap_ = Map.ofEntries(
+ Map.entry(Device.CLAW, MiniServoMId.RobotArmClawSV)
+ );
+ private final Map supportCmdDeviceMIdMap = servoMIdMap_.entrySet().stream().
+ collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid));
+ private final Set supportActions = Set.of(Action.SET, Action.ORIGIN, Action.MOVE, Action.STOP);
+
+ @Override
+ protected Map getSupportCmdDeviceMIdMap()
+ {
+ return supportCmdDeviceMIdMap;
+ }
+
+ @Override
+ protected Set getSupportActions() {
+ return supportActions;
+ }
+
+ @Override
+ public void handleCommand(DeviceCommand command) throws Exception {
+ // 发送命令
+ MiniServoMId servoMId = servoMIdMap_.get(command.getDevice());
+ if(command.getAction() == Action.STOP) {
+ driver_.stop(servoMId);
+ }
+ else if(command.getAction() == Action.MOVE) {
+ double position = command.getParam().getPosition();
+ driver_.moveTo(servoMId, position);
+ }
+ else if(command.getAction() == Action.SET) {
+ Double speed = command.getParam().getSpeed();
+ if(speed != null) {
+ driver_.setSpeed(servoMId, speed);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/handlers/MotorHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/handlers/MotorHandler.java
new file mode 100644
index 0000000..a0b7c87
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/handlers/MotorHandler.java
@@ -0,0 +1,145 @@
+package com.iflytop.handacid.hardware.command.handlers;
+
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.common.enums.MotorDirection;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.drivers.MotorWrapperDriver;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.StepMotor.MotorDirect;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MotorHandler extends CommandHandler {
+ private final MotorWrapperDriver driver_;
+
+ private final Map stepMotorMIdMap_ = Map.ofEntries(
+ Map.entry(Device.ROBOTIC_ARM_BIG, StepMotorMId.DualRobot1M),
+ Map.entry(Device.ROBOTIC_ARM_SMALL, StepMotorMId.DualRobot2M),
+ Map.entry(Device.Z_MOTOR, StepMotorMId.RobotArmZM),
+ Map.entry(Device.BRUSHLESS_PUMP_1, StepMotorMId.BrushlessPump1M),
+ Map.entry(Device.BRUSHLESS_PUMP_2, StepMotorMId.BrushlessPump2M),
+ Map.entry(Device.BRUSHLESS_PUMP_3, StepMotorMId.BrushlessPump3M),
+ Map.entry(Device.BRUSHLESS_PUMP_4, StepMotorMId.BrushlessPump4M),
+ Map.entry(Device.BRUSHLESS_PUMP_5, StepMotorMId.BrushlessPump5M),
+ Map.entry(Device.BRUSHLESS_PUMP_6, StepMotorMId.BrushlessPump6M),
+ Map.entry(Device.BRUSHLESS_PUMP_7, StepMotorMId.BrushlessPump7M),
+ Map.entry(Device.BRUSHLESS_PUMP_8, StepMotorMId.BrushlessPump8M),
+ Map.entry(Device.BRUSHLESS_PUMP_9, StepMotorMId.BrushlessPump9M),
+ Map.entry(Device.BRUSHLESS_PUMP_10, StepMotorMId.BrushlessPump10M),
+ Map.entry(Device.STEP_PUMP_1, StepMotorMId.StepPump1M),
+ Map.entry(Device.STEP_PUMP_2, StepMotorMId.StepPump2M),
+ Map.entry(Device.STEP_PUMP_3, StepMotorMId.StepPump3M),
+ Map.entry(Device.STIR_MOTOR_1, StepMotorMId.TitratorStir1M),
+ Map.entry(Device.STIR_MOTOR_2, StepMotorMId.TitratorStir2M),
+ Map.entry(Device.TITRATION_MOTOR_1, StepMotorMId.Titrator1M),
+ Map.entry(Device.TITRATION_MOTOR_2, StepMotorMId.Titrator2M)
+ );
+ private final Map supportCmdDeviceMIdMap = stepMotorMIdMap_.entrySet().stream().
+ collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().mid));
+
+ private final Set supportActions = Set.of(
+ Action.SET,
+ Action.ENABLE,
+ Action.DISABLE,
+ Action.OPEN_CLAMP,
+ Action.CLOSE_CLAMP,
+ Action.ORIGIN,
+ Action.MOVE,
+ Action.MOVE_BY,
+ Action.ROTATE,
+ Action.STOP,
+ Action.MOVE_END);
+
+ @Override
+ protected Map getSupportCmdDeviceMIdMap() {
+ return supportCmdDeviceMIdMap;
+ }
+
+ @Override
+ protected Set getSupportActions() {
+ return supportActions;
+ }
+
+ public MotorDirect getMotorDirect(MotorDirection cmdDirection)
+ {
+ return switch (cmdDirection) {
+ case FORWARD -> MotorDirect.FORWARD;
+ case BACKWARD -> MotorDirect.BACKWARD;
+ };
+ }
+
+ @Override
+ public void handleCommand(DeviceCommand command) throws Exception {
+ // 发送命令
+ StepMotorMId stepMotorMId = stepMotorMIdMap_.get(command.getDevice());
+
+ switch (command.getAction()) {
+ case ENABLE -> {
+ log.info("Motor {} Enable", stepMotorMId.mid.getDescription());
+ driver_.motorEnable(stepMotorMId, true);
+ }
+ case DISABLE -> {
+ log.info("Motor {} Disable", stepMotorMId.mid.getDescription());
+ driver_.motorEnable(stepMotorMId, false);
+ }
+ case OPEN_CLAMP -> {
+ log.info("Motor {} Open Clamp", stepMotorMId.mid.getDescription());
+ driver_.openClamp(stepMotorMId);
+ }
+ case CLOSE_CLAMP -> {
+ log.info("Motor {} Close Clamp", stepMotorMId.mid.getDescription());
+ driver_.closeClamp(stepMotorMId);
+ }
+ case ORIGIN -> {
+ log.info("Motor {} Move To Origin", stepMotorMId.mid.getDescription());
+ driver_.moveToHome(stepMotorMId);
+ }
+ case MOVE -> {
+ double position = command.getParam().getPosition();
+ log.info("Motor {} Move To Position {}", stepMotorMId.mid.getDescription(), position);
+ driver_.moveTo(stepMotorMId, position);
+ }
+ case MOVE_BY -> {
+ double distance = command.getParam().getPosition();
+ log.info("Motor {} Move By Distance {}", stepMotorMId.mid.getDescription(), distance);
+ driver_.moveBy(stepMotorMId, distance);
+ }
+ case ROTATE -> {
+ MotorDirection cmdDirection = command.getParam().getDirection();
+ MotorDirect direct = getMotorDirect(cmdDirection);
+ log.info("Motor {} Rotate {}", stepMotorMId.mid.getDescription(), direct.name());
+ driver_.rotate(stepMotorMId, direct);
+ }
+ case STOP -> {
+ log.info("Motor {} Stop", stepMotorMId.mid.getDescription());
+ driver_.stop(stepMotorMId);
+ }
+ case SET -> {
+ Double speed = command.getParam().getSpeed();
+ if (speed != null) {
+ log.info("Motor {} Set Speed {}", stepMotorMId.mid.getDescription(), speed);
+ driver_.setSpeed(stepMotorMId, speed);
+ }
+ }
+ case MOVE_END -> {
+ driver_.moveToEnd(stepMotorMId);
+ }
+ }
+ }
+}
+
+
+
+
diff --git a/src/main/java/com/iflytop/handacid/hardware/command/handlers/TricolorLightHandler.java b/src/main/java/com/iflytop/handacid/hardware/command/handlers/TricolorLightHandler.java
new file mode 100644
index 0000000..c73f2ae
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/command/handlers/TricolorLightHandler.java
@@ -0,0 +1,84 @@
+package com.iflytop.handacid.hardware.command.handlers;
+
+
+import com.iflytop.handacid.app.core.command.DeviceCommand;
+import com.iflytop.handacid.common.enums.Action;
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.common.enums.TricolorLightColor;
+import com.iflytop.handacid.hardware.command.CommandHandler;
+import com.iflytop.handacid.hardware.drivers.TricolorLightDriver;
+import com.iflytop.handacid.hardware.type.MId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.security.InvalidParameterException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 三色灯
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TricolorLightHandler extends CommandHandler {
+ private final TricolorLightDriver tricolorLightDriver;
+
+ private final Map supportCmdDeviceMIdMap = Map.ofEntries(
+ Map.entry(Device.TRICOLOR_LIGHT, MId.IO1_TriColorLight)
+ );
+
+ private final Set supportActions = Set.of(Action.OPEN, Action.CLOSE);
+
+ @Override
+ protected Map getSupportCmdDeviceMIdMap()
+ {
+ return supportCmdDeviceMIdMap;
+ }
+
+ @Override
+ protected Set getSupportActions() {
+ return supportActions;
+ }
+
+ @Override
+ protected void checkParams(DeviceCommand command) throws Exception {
+ // 检查参数
+ if (command.getAction() == Action.OPEN) {
+ // 检查参数
+ TricolorLightColor cmdColor = command.getParam().getColor();
+ getCmdColor(cmdColor);
+ }
+
+ }
+
+ TricolorLightDriver.Color getCmdColor(TricolorLightColor cmdColor){
+ switch (cmdColor){
+ case RED:
+ return TricolorLightDriver.Color.RED;
+ case GREEN:
+ return TricolorLightDriver.Color.GREEN;
+ case BLUE:
+ return TricolorLightDriver.Color.BLUE;
+ default:
+ throw new InvalidParameterException("颜色参数错误");
+ }
+ }
+
+ MId getMId(Device cmdDevice){
+ return supportCmdDeviceMIdMap.get(cmdDevice);
+ }
+
+ @Override
+ public void handleCommand(DeviceCommand command) throws Exception {
+ // 发送命令
+ if (command.getAction() == Action.OPEN) {
+ TricolorLightColor cmdColor = command.getParam().getColor();
+ TricolorLightDriver.Color color = getCmdColor(cmdColor);
+ tricolorLightDriver.open(getMId(command.getDevice()), color);
+ } else if (command.getAction() == Action.CLOSE) {
+ tricolorLightDriver.close(getMId(command.getDevice()));
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/config/A8kSubModuleInitRegConfig.java b/src/main/java/com/iflytop/handacid/hardware/config/A8kSubModuleInitRegConfig.java
new file mode 100644
index 0000000..5fb5b35
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/config/A8kSubModuleInitRegConfig.java
@@ -0,0 +1,112 @@
+package com.iflytop.handacid.hardware.config;
+
+import com.iflytop.handacid.hardware.type.ModuleType;
+import com.iflytop.handacid.hardware.type.RegIndex;
+import jakarta.annotation.PostConstruct;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class A8kSubModuleInitRegConfig {
+ static class ModuleRegMarker {
+ public ModuleType moduleType;
+ public RegIndex regIndex;
+
+ ModuleRegMarker(ModuleType moduleType, RegIndex regIndex) {
+ this.moduleType = moduleType;
+ this.regIndex = regIndex;
+ }
+ }
+
+ private final List regMarkers = new ArrayList();
+
+ public List findRegIndexByModuleType(ModuleType moduleType) {
+ List regIndexes = new ArrayList<>();
+ for (ModuleRegMarker marker : regMarkers) {
+ if (marker.moduleType == moduleType) {
+ regIndexes.add(marker.regIndex);
+ }
+ }
+ return regIndexes;
+ }
+
+ public Boolean isNeeded(ModuleType moduleType, RegIndex regIndex) {
+ for (ModuleRegMarker marker : regMarkers) {
+ if (marker.moduleType == moduleType && marker.regIndex == regIndex) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @PostConstruct
+ void init() {
+ regMarkers();
+ }
+
+
+ private void regMarkers() {
+ //
+ // 标记数据库关心的寄存器,被标记的寄存器,会在设备初始化时侯,初始化其数值
+ //
+ //HBOT
+ //TMCStepMotor
+ // addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_shift);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_shaft);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_one_circle_pulse);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_one_circle_pulse_denominator);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_default_velocity);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_low_velocity);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_mid_velocity);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_high_velocity);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_ihold);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_irun);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_iholddelay);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_iglobalscaler);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_mres);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_run_to_zero_speed);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_look_zero_edge_speed);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_max_d);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_min_d);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_in_debug_mode);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_vstart);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_a1);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_amax);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_v1);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_dmax);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_d1);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_vstop);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_tzerowait);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_enc_resolution);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_enable_enc);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kreg_step_motor_dzero_pos);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kret_step_motor_pos_devi_tolerance);
+ addRegMarker(ModuleType.TMCStepMotor, RegIndex.kret_step_motor_io_trigger_append_distance);
+
+
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_limit_velocity);
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_limit_torque);
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_protective_torque);
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_target_pos_tolerance);
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_min_angle); // 最小角度限制
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_angle); // 最大角度限制
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_temp); // 最高温度上限
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_voltage); // 最高输入电压
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_min_voltage); // 最低输入电压
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_max_torque); // 最大扭矩
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_unload_condition); // 卸载条件
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_protect_current); // 保护电流
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_protect_torque); // 保护扭矩 0->100 ,触发后,需要写入与组转方向相反的位置指令,进行解除
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_protect_time); // 保护时间
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_overload_torque); // 过载扭矩
+ addRegMarker(ModuleType.MiniServo, RegIndex.kreg_mini_servo_servo_acc); // 加速度
+
+ }
+
+ private void addRegMarker(ModuleType moduleTYpe, RegIndex regIndex) {
+ regMarkers.add(new ModuleRegMarker(moduleTYpe, regIndex));
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/config/StepMotorConfig.java b/src/main/java/com/iflytop/handacid/hardware/config/StepMotorConfig.java
new file mode 100644
index 0000000..8015886
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/config/StepMotorConfig.java
@@ -0,0 +1,44 @@
+package com.iflytop.handacid.hardware.config;
+
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Slf4j
+@Component
+public class StepMotorConfig {
+
+ // 最大限位配置
+ static final double DEFAULT_MAX_LIMIT = Double.POSITIVE_INFINITY; // mm
+ final static Map stepMotorMaxLimitMap = Map.ofEntries(
+ );
+
+
+ /* ******************** ******************** ******************** */
+ /* ******************** 最大限位 ******************** */
+ /* ******************** ******************** ******************** */
+ // 从数据库拉取最大限位信息
+ private Double getMaxLimitFromDB(StepMotorMId stepMotorId)
+ {
+ // TODO: 从数据库中获取最大限位
+ return null;
+ }
+
+ private Double getMaxLimitFromDefault(StepMotorMId stepMotorId)
+ {
+ return stepMotorMaxLimitMap.getOrDefault(stepMotorId, DEFAULT_MAX_LIMIT);
+ }
+
+ public double getMaxLimit(StepMotorMId stepMotorId)
+ {
+ Double maxLimit = getMaxLimitFromDB(stepMotorId);
+ if (maxLimit != null) {
+ return maxLimit;
+ } else {
+ return getMaxLimitFromDefault(stepMotorId);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/constants/ActionOvertimeConstant.java b/src/main/java/com/iflytop/handacid/hardware/constants/ActionOvertimeConstant.java
new file mode 100644
index 0000000..fb7a57f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/constants/ActionOvertimeConstant.java
@@ -0,0 +1,61 @@
+package com.iflytop.handacid.hardware.constants;
+
+
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.Servo.LeisaiServoMId;
+import com.iflytop.handacid.hardware.type.Servo.LiquidArmMId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class ActionOvertimeConstant {
+ final Integer defaultOvertime = 80 * 1000;
+
+ static class OvertimeConfigItem {
+ public MId mid;
+ public CmdId cmdId;
+ public Integer overtime;
+ }
+
+ List overtimeConfigItems = new ArrayList<>();
+
+ public Integer get(MId mid, CmdId cmdId) {
+ return priGetOvertime(mid, cmdId);
+ }
+
+ public Integer get(StepMotorMId mid, CmdId cmdId) {
+ return priGetOvertime(mid.mid, cmdId);
+ }
+
+ public void pushNewConfig(LeisaiServoMId mid, CmdId cmdId, Integer overtime) {
+ pushNewConfig(mid.mid, cmdId, overtime);
+ }
+ public void pushNewConfig(LiquidArmMId mid, CmdId cmdId, Integer overtime) {
+ pushNewConfig(mid.mid, cmdId, overtime);
+ }
+ public void pushNewConfig(StepMotorMId mid, CmdId cmdId, Integer overtime) {
+ pushNewConfig(mid.mid, cmdId, overtime);
+ }
+
+ private void pushNewConfig(MId mid, CmdId cmdId, Integer overtime) {
+ OvertimeConfigItem item = new OvertimeConfigItem();
+ item.mid = mid;
+ item.cmdId = cmdId;
+ item.overtime = overtime;
+ overtimeConfigItems.add(item);
+ }
+
+ private Integer priGetOvertime(MId mid, CmdId cmdId) {
+ for (OvertimeConfigItem item : overtimeConfigItems) {
+ if (item.mid == mid && item.cmdId == cmdId) {
+ return item.overtime;
+ }
+ }
+ return defaultOvertime;
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/constants/FilePathConstant.java b/src/main/java/com/iflytop/handacid/hardware/constants/FilePathConstant.java
new file mode 100644
index 0000000..1931fab
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/constants/FilePathConstant.java
@@ -0,0 +1,5 @@
+package com.iflytop.handacid.hardware.constants;
+
+public class FilePathConstant {
+ public static final String FILE_DB_EXPORT_PATH = "runenv/files/db"; //存储数据库导出文件
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/constants/MiniServoConstant.java b/src/main/java/com/iflytop/handacid/hardware/constants/MiniServoConstant.java
new file mode 100644
index 0000000..bd5d427
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/constants/MiniServoConstant.java
@@ -0,0 +1,51 @@
+package com.iflytop.handacid.hardware.constants;
+
+import com.iflytop.handacid.hardware.type.Servo.MiniServoMId;
+import org.springframework.util.Assert;
+
+import java.util.Map;
+
+public class MiniServoConstant {
+ static public final Integer actionOvertime = 20000;
+ static class MiniServoFixPosConfig {
+ public Integer zeroPos;
+ public Integer refPos;
+ }
+
+ static Map map = Map.of(
+// //摇匀模组Y轴
+// MiniServoMId.ShakeModGripperYSV, new MiniServoFixPosConfig() {{
+// zeroPos = 110;
+// refPos = 100;//Y轴最外侧的位置
+// }},
+// //摇匀模组夹爪
+// MiniServoMId.ShakeModGripperSV, new MiniServoFixPosConfig() {{
+// zeroPos = 1600;
+// refPos = 1800;//完全张开的位置
+// }},
+// //试管架夹紧
+// MiniServoMId.ShakeModTubeScanerClampingSV, new MiniServoFixPosConfig() {{
+// zeroPos = 1800;
+// refPos = 1800;//水平位置
+// }},
+// //试管顶升舵机
+// MiniServoMId.ShakeModLiftingSV, new MiniServoFixPosConfig() {{
+// zeroPos = 644;//底部 644/3000 = 220/1024
+// refPos = 644;//底部
+// }}
+ );
+
+ static public Integer getZeroPos(MiniServoMId mid) {
+ var config = map.get(mid);
+ Assert.notNull(config, "getZeroPos fail");
+ return config.zeroPos;
+ }
+
+ static public Integer getRefPos(MiniServoMId mid) {
+ var config = map.get(mid);
+ Assert.notNull(config, "getRefPos fail");
+ return config.refPos;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/controller/IOController.java b/src/main/java/com/iflytop/handacid/hardware/controller/IOController.java
new file mode 100644
index 0000000..cfe0b88
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/controller/IOController.java
@@ -0,0 +1,32 @@
+package com.iflytop.handacid.hardware.controller;
+
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.hardware.drivers.OutputIOCtrlDriver;
+import com.iflytop.handacid.hardware.type.IO.OutputIOMId;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@Tag(name = "IO控制")
+@RestController
+@RequestMapping("/api/io")
+@RequiredArgsConstructor
+@Slf4j
+public class IOController {
+ private final OutputIOCtrlDriver driver;
+
+ @PostMapping("/set-output")
+ public Result> setOutput(@RequestParam OutputIOMId index, @RequestParam boolean state) {
+ log.info("Setting IO output at index: {} to state: {}", index, state);
+ try {
+ driver.setIOState(index, state);
+ } catch (Exception e) {
+ log.error("Failed to set IO output: {}", e.getMessage());
+ }
+ return Result.success();
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/controller/StepMotorController.java b/src/main/java/com/iflytop/handacid/hardware/controller/StepMotorController.java
new file mode 100644
index 0000000..64077e0
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/controller/StepMotorController.java
@@ -0,0 +1,312 @@
+package com.iflytop.handacid.hardware.controller;
+
+import com.iflytop.handacid.app.service.DeviceParamConfigService;
+import com.iflytop.handacid.common.result.Result;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.service.StepMotorService;
+import com.iflytop.handacid.hardware.type.StepMotor.DeviceStepMotorId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorRegIndex;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorSpeedLevel;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Tag(name = "步进电机控制")
+@RestController
+@RequestMapping("/api/step-motor")
+@RequiredArgsConstructor
+@Slf4j
+public class StepMotorController {
+ private final StepMotorService stepMotorService;
+ private final DeviceParamConfigService deviceParamConfigService;
+
+ // 获取设备列表
+ @PostMapping("/get-device-list")
+ @Operation(summary = "获取设备列表")
+ public Map getDeviceList() {
+ Map map = new HashMap<>();
+ for(DeviceStepMotorId id : DeviceStepMotorId.values()) {
+ map.put(id.name(), id.getDescription());
+ }
+ return map;
+ }
+
+ // 获取寄存器列表
+ @PostMapping("/get-reg-list")
+ @Operation(summary = "获取寄存器列表")
+ public List getRegList() {
+ List list = new ArrayList<>();
+ for(StepMotorRegIndex reg : StepMotorRegIndex.values()) {
+ list.add(reg.name());
+ }
+ return list;
+ }
+
+ // 基础设置
+ @PostMapping("/speed-level")
+ @Operation(summary = "设置速度等级")
+ public Result> setStepMotorSpeedLevel(@RequestParam StepMotorSpeedLevel speedLevel) {
+ stepMotorService.setStepMotorSpeedLevel(speedLevel);
+ return Result.success();
+ }
+
+ // 基础操作
+ @PostMapping("/enable")
+ @Operation(summary = "电源开启")
+ public Result> enableMotor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.enableMotor(deviceId);
+ return Result.success();
+ }
+
+ @PostMapping("/disable")
+ @Operation(summary = "电源关闭")
+ public Result> disableMotor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.disableMotor(deviceId);
+ return Result.success();
+ }
+
+ @PostMapping("/move-to-zero")
+ @Operation(summary = "回Home")
+ public Result> stepMotorEasyMoveToZero(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.stepMotorEasyMoveToZero(deviceId);
+ return Result.success();
+ }
+
+ @PostMapping("/move-to-zero-quick")
+ @Operation(summary = "快速回Home")
+ public Result> stepMotorEasyMoveToZeroPointQuickBlock(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.stepMotorEasyMoveToZeroPointQuickBlock(deviceId);
+ return Result.success();
+ }
+
+ @PostMapping("/stop")
+ @Operation(summary = "停止")
+ public Result> stepMotorStop(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.stepMotorStop(deviceId);
+ return Result.success();
+ }
+
+ // 相对移动
+ @PostMapping("/move-by-forward")
+ @Operation(summary = "正向点动")
+ public Result> stepMotorEasyMoveByForward(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer dpos) throws HardwareException {
+ stepMotorService.stepMotorEasyMoveByForward(deviceId, dpos);
+ return Result.success();
+ }
+
+ @PostMapping("/move-by-backward")
+ @Operation(summary = "反向点动")
+ public Result> stepMotorEasyMoveByBackward(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer dpos) throws HardwareException {
+ stepMotorService.stepMotorEasyMoveByBackward(deviceId, dpos);
+ return Result.success();
+ }
+
+ // 绝对移动
+ @PostMapping("/move-to")
+ @Operation(summary = "移动(绝对)")
+ public Result> stepMotorEasyMoveTo(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer pos) throws HardwareException {
+ stepMotorService.stepMotorEasyMoveTo(deviceId, pos);
+ return Result.success();
+ }
+
+ // 旋转
+ @PostMapping("/rotate-forward")
+ @Operation(summary = "正转")
+ public Result> rotateForward(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.rotateForward(deviceId);
+ return Result.success();
+ }
+
+ @PostMapping("/rotate-backward")
+ @Operation(summary = "反转")
+ public Result> rotateBackward(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ stepMotorService.rotateBackward(deviceId);
+ return Result.success();
+ }
+
+ // 寄存器配置
+ @PostMapping("/mres")
+ @Operation(summary = "设置细分")
+ public Result> setMres(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer mres) throws HardwareException {
+ stepMotorService.setMres(deviceId, mres);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_mres.regIndex.name(),mres);
+ return Result.success();
+ }
+
+ @PostMapping("/irun")
+ @Operation(summary = "设置IRUN")
+ public Result> setIRUN(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer irun) throws HardwareException {
+ stepMotorService.setIRUN(deviceId, irun);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_irun.regIndex.name(),irun);
+ return Result.success();
+ }
+
+ @PostMapping("/ihold")
+ @Operation(summary = "设置IHOLD")
+ public Result> setIHOLD(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer ihold) throws HardwareException {
+ stepMotorService.setIHOLD(deviceId, ihold);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_ihold.regIndex.name(),ihold);
+ return Result.success();
+ }
+
+ @PostMapping("/start-stop-vel")
+ @Operation(summary = "设置起停速度")
+ public Result> setStartAndStopVel(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer v) throws HardwareException {
+ stepMotorService.setStartAndStopVel(deviceId, v);
+ //开始速度参数保存到数据库
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_vstart.regIndex.name(),v);
+ //停止速度参数保存到数据库
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_vstop.regIndex.name(),v);
+ return Result.success();
+ }
+
+ @PostMapping("/v1")
+ @Operation(summary = "设置V1")
+ public Result> setV1(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer v) throws HardwareException {
+ stepMotorService.setV1(deviceId, v);
+ //参数保存到数据库
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_v1.regIndex.name(),v);
+ return Result.success();
+ }
+
+ @PostMapping("/acceleration")
+ @Operation(summary = "设置加速度")
+ public Result> setA1AndD1(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer acc) throws HardwareException {
+ stepMotorService.setA1AndD1(deviceId, acc);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_a1.regIndex.name(),acc);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_d1.regIndex.name(),acc);
+ return Result.success();
+ }
+
+ @PostMapping("/max-acceleration")
+ @Operation(summary = "设置最大加速度")
+ public Result> setAmaxAndDmax(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer acc) throws HardwareException {
+ stepMotorService.setAmaxAndDmax(deviceId, acc);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_amax.regIndex.name(),acc);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_dmax.regIndex.name(),acc);
+ return Result.success();
+ }
+
+ @PostMapping("/default-velocity")
+ @Operation(summary = "设置默认速度")
+ public Result> setDefaultVel(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer v) throws HardwareException {
+ stepMotorService.setDefaultVel(deviceId, v);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_default_velocity.regIndex.name(),v);
+ return Result.success();
+ }
+
+ @PostMapping("/speed")
+ @Operation(summary = "设置速度")
+ public Result> setSpeed(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer low,
+ @RequestParam Integer mid,
+ @RequestParam Integer high) throws HardwareException {
+ stepMotorService.setSpeed(deviceId, low, mid, high);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_low_velocity.regIndex.name(),low);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_mid_velocity.regIndex.name(),mid);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_high_velocity.regIndex.name(),high);
+ return Result.success();
+ }
+
+ @PostMapping("/one-circle-pulse")
+ @Operation(summary = "设置一圈脉冲数")
+ public Result> setOneCirclePulse(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer pulse,
+ @RequestParam Integer denominator) throws HardwareException {
+ stepMotorService.setOneCirclePulse(deviceId, pulse, denominator);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_one_circle_pulse.regIndex.name(),pulse);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_one_circle_pulse_denominator.regIndex.name(),denominator);
+ return Result.success();
+ }
+
+ @PostMapping("/dzero")
+ @Operation(summary = "设置DZero")
+ public Result> setDZero(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam Integer dzero) throws HardwareException {
+ stepMotorService.setDZero(deviceId, dzero);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),StepMotorRegIndex.kreg_step_motor_dzero_pos.regIndex.name(),dzero);
+ return Result.success();
+ }
+
+ @GetMapping("/reg")
+ @Operation(summary = "读取寄存器值")
+ public Integer readReg(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam StepMotorRegIndex reg) {
+ return stepMotorService.readReg(deviceId, reg);
+ }
+
+ @PostMapping("/reg")
+ @Operation(summary = "设置寄存器")
+ public Result> setReg(
+ @RequestParam DeviceStepMotorId deviceId,
+ @RequestParam StepMotorRegIndex reg,
+ @RequestParam Integer val) throws HardwareException {
+ stepMotorService.setReg(deviceId, reg, val);
+ deviceParamConfigService.setModuleAndReg(deviceId.getStepMotorMId().mid.name(),reg.regIndex.name(),val);
+ return Result.success();
+ }
+
+ // 状态查询
+ @GetMapping("/regs")
+ @Operation(summary = "读取所有寄存器")
+ public Map readAllRegs(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ return stepMotorService.readAllRegs(deviceId);
+ }
+
+ @GetMapping("/position")
+ @Operation(summary = "读取位置")
+ public Integer readPos(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ return stepMotorService.readPos(deviceId);
+ }
+
+ @GetMapping("/encoder-position")
+ @Operation(summary = "读取编码器位置")
+ public Integer readEncPos(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ return stepMotorService.readEncPos(deviceId);
+ }
+
+ @PostMapping("/read-zero-sensor")
+ @Operation(summary = "读取零点光电")
+ public Boolean readZeroSensor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ return stepMotorService.readIoState(deviceId, 0);
+ }
+ @PostMapping("/read-limit-sensor")
+ @Operation(summary = "读取限位光电")
+ public Boolean readLimitSensor(@RequestParam DeviceStepMotorId deviceId) throws HardwareException {
+ return stepMotorService.readIoState(deviceId, 1);
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/dao/SubModuleRegInitialValueDao.java b/src/main/java/com/iflytop/handacid/hardware/dao/SubModuleRegInitialValueDao.java
new file mode 100644
index 0000000..74cbe79
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/dao/SubModuleRegInitialValueDao.java
@@ -0,0 +1,38 @@
+package com.iflytop.handacid.hardware.dao;
+
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.RegIndex;
+import com.iflytop.handacid.hardware.type.db.SubModuleRegInitialValue;
+import com.iflytop.handacid.hardware.utils.ZSqlite;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class SubModuleRegInitialValueDao extends ZSqlite {
+ private final JdbcTemplate _jdbcTemplate;
+
+ @PostConstruct
+ void init() {
+ init(_jdbcTemplate, "zapp_sub_module_reg_initial_value", SubModuleRegInitialValue.class, false);
+ }
+
+ public SubModuleRegInitialValue findByIDAndRegIndex(MId mid, RegIndex regIndex) {
+ return queryOne("select * from " + tableName + " where mid = ? and regIndex = ?;", mid, regIndex);
+ }
+
+ public void update(MId mid, RegIndex regIndex, Integer regInitVal) {
+ var old = findByIDAndRegIndex(mid, regIndex);
+ if (old == null) {
+ add(new SubModuleRegInitialValue(mid, regIndex, regInitVal));
+ } else {
+ old.regInitVal = regInitVal;
+ update(old);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/CeramicPumpDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/CeramicPumpDriver.java
new file mode 100644
index 0000000..90dd57f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/CeramicPumpDriver.java
@@ -0,0 +1,267 @@
+package com.iflytop.handacid.hardware.drivers;
+
+
+import com.iflytop.handacid.hardware.comm.modbus.ModbusMasterService;
+import com.iflytop.handacid.hardware.type.CeramicPumpDriverSlaveId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CeramicPumpDriver {
+
+
+ public enum CeramicPumpBaud {
+ CeramicPumpBaud_9600(0x04),
+ CeramicPumpBaud_115200(0x08);
+
+ int getId(){
+ return id;
+ }
+
+ private final int id;
+
+ CeramicPumpBaud(int id) {
+ this.id = id;
+ }
+ };
+
+ public enum Direction {
+ FORWARD(0x0000), // 正转
+ BACKWARD(0x0001); // 反转
+
+ private final int value;
+
+ Direction(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ private final ModbusMasterService modbusMaster;
+
+ private static final int DEFAULT_ADDRESS = 0x11;
+ private static final int ADDRESS_REGISTER = 0x000A;
+ private static final int BAUDRATE_REGISTER = 0x001C;
+ private static final int SPEED_REGISTER = 0x000C;
+ private static final int RESET_REGISTER = 0x0014;
+ private static final int MOVE_REGISTER = 0x0014;
+ private static final int STOP_RESUME_REGISTER = 0x0010;
+ private static final int DIRECTION_REGISTER = 0x000E; // 正反转控制寄存器
+ private static final int POSITION_REGISTER = 0x0012; // 当前圈数查询寄存器
+
+ private final ConcurrentHashMap runFlags = new ConcurrentHashMap<>();
+
+ /**
+ * 设置注射泵地址
+ */
+ public void setDeviceAddress(CeramicPumpDriverSlaveId pumpId, int newAddress) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, ADDRESS_REGISTER, newAddress)) {
+ throw new Exception("Failed to set device address for pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] address set to {}", slaveId, newAddress);
+ }
+
+ /**
+ * 设置波特率
+ */
+ public void setBaudRate(CeramicPumpDriverSlaveId pumpId, CeramicPumpBaud baudRate) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, BAUDRATE_REGISTER, baudRate.getId())) {
+ throw new Exception("Failed to set baud rate for pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] baud rate set to {}", slaveId, baudRate);
+ }
+
+ /**
+ * 设置注射泵速度
+ */
+ public void setSpeed(CeramicPumpDriverSlaveId pumpId, int speed) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, SPEED_REGISTER, speed)) {
+ throw new Exception("Failed to set speed for pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] speed set to {} rpm", slaveId, speed);
+ }
+
+ /**
+ * 获取注射泵速度
+ */
+ public int getSpeed(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ short[] response = modbusMaster.readHoldingRegisters(slaveId, SPEED_REGISTER, 1);
+ if (response == null || response.length == 0) {
+ throw new Exception("Failed to read speed for pump, slaveId: " + slaveId);
+ }
+ return response[0];
+ }
+
+ /**
+ * 获取注射泵位置(圈数)
+ */
+ public int getTargetPosition(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ short[] response = modbusMaster.readHoldingRegisters(slaveId, MOVE_REGISTER, 1);
+ if (response == null || response.length == 0) {
+ throw new Exception("Failed to read position for pump, slaveId: " + slaveId);
+ }
+ return response[0];
+ }
+
+ /**
+ * 查询注射泵当前地址(ID)
+ */
+ public int getDeviceAddress(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ short[] response = modbusMaster.readHoldingRegisters(slaveId, ADDRESS_REGISTER, 1);
+ if (response == null || response.length == 0) {
+ throw new Exception("Failed to read device address for pump, slaveId: " + slaveId);
+ }
+ return response[0];
+ }
+
+ /**
+ * 复位
+ */
+ public void reset(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, RESET_REGISTER, 0)) {
+ throw new Exception("Failed to reset pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] reset", slaveId);
+ }
+
+ /**
+ * 定量运动
+ */
+ public void moveBy(CeramicPumpDriverSlaveId pumpId, int r) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, MOVE_REGISTER, r)) {
+ throw new Exception("Failed to move pump, slaveId: " + slaveId + ", r: " + r);
+ }
+ log.info("Pump[{}] move {} turns", slaveId, r);
+ }
+
+ /**
+ * 设置正反转方向
+ * @param direction 正转(FORWARD)或反转(REVERSE)
+ */
+ public void setDirection(CeramicPumpDriverSlaveId pumpId, Direction direction) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, DIRECTION_REGISTER, direction.getValue())) {
+ throw new Exception("Failed to set direction for pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] direction set to {}", slaveId, direction);
+ }
+
+
+ public int getCurrentPosition(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ short[] response = modbusMaster.readHoldingRegisters(slaveId, POSITION_REGISTER, 1);
+ if (response == null || response.length == 0) {
+ throw new Exception("Failed to read current position for pump, slaveId: " + slaveId);
+ }
+ return response[0];
+ }
+
+ // 停止运动
+ public void stop(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, STOP_RESUME_REGISTER, 0x0000)) {
+ throw new Exception("Failed to stop pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] stopped", slaveId);
+ }
+
+ /**
+ * 继续运动
+ */
+ public void resume(CeramicPumpDriverSlaveId pumpId) throws Exception {
+ int slaveId = pumpId.getSlaveId();
+ if (!modbusMaster.writeRegister(slaveId, STOP_RESUME_REGISTER, 0x0001)) {
+ throw new Exception("Failed to resume pump, slaveId: " + slaveId);
+ }
+ log.info("Pump[{}] resumed", slaveId);
+ }
+
+ public void moveByBlock(CeramicPumpDriverSlaveId pumpId, Direction direction, int r)
+ {
+ final int targetPosition = r; // 目标位置(圈数)
+ int currentPosition = 0; // 当前位置(圈数)
+ int currentSpeed = 0; // 当前速度(转速)
+
+ if (runFlags.getOrDefault(pumpId, false)) {
+ log.warn("Pump {} is already running, skipping moveByBlock", pumpId);
+ return;
+ }
+
+ runFlags.put(pumpId, true);
+
+ try {
+ // 初始化
+ currentSpeed = getSpeed(pumpId);
+
+ this.reset(pumpId);
+ this.setDirection(pumpId, direction);
+
+ // 计算移动目标位置的时间 速度单位 rpm, 距离 r,
+ int expectedMs = (int) ((targetPosition - currentPosition) * 60.0 * 1000.0 / currentSpeed) ;
+ log.info("Pump {} moveByBlock, targetPosition: {}, currentPosition: {}, currentSpeed: {}, expectedMs: {}",
+ pumpId, targetPosition, currentPosition, currentSpeed, expectedMs);
+ this.moveBy(pumpId, targetPosition);
+
+ int timoutMs = (int)(expectedMs * 1.5); // 设置超时时间为预期时间的两倍
+ int pollCurrentPosMs = (int)(expectedMs * 0.9); // 轮询当前位置的时间为预期时间的90%后开始
+ long startTime = System.currentTimeMillis();
+ int elapsedTime = 0; // 已用时间
+ while (elapsedTime < timoutMs) {
+ if (!runFlags.getOrDefault(pumpId, false)) {
+ log.info("Pump {} stopped before reaching target position", pumpId);
+ return; // 如果在等待过程中被停止,退出
+ }
+
+ elapsedTime = (int)(System.currentTimeMillis() - startTime);
+
+ if (elapsedTime >= pollCurrentPosMs) {
+ currentPosition = getCurrentPosition(pumpId);
+
+ if (currentPosition >= targetPosition) {
+ log.info("Pump {} reached target position: {}", pumpId, targetPosition);
+ break;
+ }
+ }
+ Thread.sleep(20); // 每100ms检查一次
+ }
+ }
+ catch (Exception e) {
+ log.error("Error initializing pump position: {}", e.getMessage());
+ throw new RuntimeException("Error initializing pump position", e);
+ }
+ finally {
+ runFlags.put(pumpId, false);
+ }
+ }
+
+ public void stopBlock(CeramicPumpDriverSlaveId pumpId) {
+ if (!runFlags.getOrDefault(pumpId, false)) {
+ log.warn("Pump {} is not running, cannot stop", pumpId);
+ return;
+ }
+ runFlags.put(pumpId, false);
+ try {
+ this.stop(pumpId);
+ } catch (Exception e) {
+ log.error("Error stopping pump {}: {}", pumpId, e.getMessage());
+ } finally {
+ runFlags.put(pumpId, false);
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/HeaterRodDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/HeaterRodDriver.java
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/InputDetectDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/InputDetectDriver.java
new file mode 100644
index 0000000..0f58464
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/InputDetectDriver.java
@@ -0,0 +1,37 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.IO.InputIOMId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class InputDetectDriver {
+
+ private final A8kCanBusService canBus;
+
+ public Boolean getIOState(InputIOMId ioid) throws HardwareException {
+ while (true) {
+ Boolean firstReadIO = priGetIOState(ioid);
+ Boolean secondReadIO = priGetIOState(ioid);
+ if (firstReadIO == secondReadIO) {
+ if (ioid.mirror) {
+ return !firstReadIO;
+ }
+ return firstReadIO;
+ } else {
+ log.warn("getIOState {} not match, retry", ioid);
+ }
+ }
+ }
+
+
+ private Boolean priGetIOState(InputIOMId ioid) throws HardwareException {
+ return canBus.callcmd(ioid.mid, CmdId.read_in_io, ioid.ioIndex).getContentI32(0) != 0;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/LeisaiServoDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/LeisaiServoDriver.java
new file mode 100644
index 0000000..9b54545
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/LeisaiServoDriver.java
@@ -0,0 +1,110 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.constants.ActionOvertimeConstant;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.A8kPacket;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.Servo.LeisaiRegIndex;
+import com.iflytop.handacid.hardware.type.Servo.LeisaiServoMId;
+import com.iflytop.handacid.hardware.type.Servo.LeisaiServoSpeedLevel;
+import com.iflytop.handacid.hardware.utils.ZJsonHelper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class LeisaiServoDriver {
+ final private A8kCanBusService canBus;
+ private final ActionOvertimeConstant actionOvertimeConstant;
+
+ public void moduleStop(LeisaiServoMId id) throws HardwareException {
+ log.info("moduleStop called with id: {}", id);
+ canBus.moduleStop(id.mid);
+ }
+
+ public void enable(LeisaiServoMId id, Integer enable) throws HardwareException {
+ log.info("enable called with id: {}, enable: {}", id, enable);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_enable, enable);
+ }
+
+ public Integer readPosition(LeisaiServoMId id) throws HardwareException {
+ log.info("readPosition called with id: {}", id);
+ A8kPacket packet = canBus.callcmd(id.mid, CmdId.leisai_servo_read_pos);
+ return packet.getContentI32(0);
+ }
+
+ public Integer readIoState(LeisaiServoMId id, Integer io_index) throws HardwareException {
+ log.info("readIoState called with id: {}, io_index: {}", id, io_index);
+ A8kPacket packet = canBus.callcmd(id.mid, CmdId.leisai_servo_read_io_state, io_index);
+ return packet.getContentI32(0);
+ }
+
+ public void moveTo(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer pos) throws HardwareException {
+ log.info("moveTo called with id: {}, speedLevel: {}, pos: {}", id, speedLevel, pos);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_move_to, speedLevel.ordinal(), pos);
+ }
+
+ public void moveBy(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer dpos) throws HardwareException {
+ log.info("moveBy called with id: {}, speedLevel: {}, dpos: {}", id, speedLevel, dpos);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_move_by, speedLevel.ordinal(), dpos);
+ }
+
+ public void rotate(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer direction) throws HardwareException {
+ log.info("rotate called with id: {}, speedLevel: {}, direction: {}", id, speedLevel, direction);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_rotate, speedLevel.ordinal(), direction);
+ }
+
+ public void moveToZero(LeisaiServoMId id) throws HardwareException {
+ log.info("moveToZero called with id: {}", id);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_move_to_zero);
+ }
+
+ public void setReg(LeisaiServoMId id, LeisaiRegIndex regindex, int val) throws HardwareException {
+ log.info("setReg called with id: {}, regindex: {}, val: {}", id, regindex, val);
+ canBus.moduleSetReg(id.mid, regindex.regIndex, val);
+ }
+
+ // ====== ===== ====== ===== ====== ===== Block ====== ===== ====== ===== ====== =====
+ public void moveToBlock(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer pos) throws HardwareException {
+ log.info("moveToBlock called with id: {}, speedLevel: {}, pos: {}", id, speedLevel, pos);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_move_to, speedLevel.ordinal(), pos);
+ waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.leisai_servo_move_to));
+ }
+
+ public void moveByBlock(LeisaiServoMId id, LeisaiServoSpeedLevel speedLevel, Integer dpos) throws HardwareException {
+ log.info("moveByBlock called with id: {}, speedLevel: {}, dpos: {}", id, speedLevel, dpos);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_move_by, speedLevel.ordinal(), dpos);
+ waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.leisai_servo_move_by));
+ }
+
+ public void moveToZeroBlock(LeisaiServoMId id) throws HardwareException {
+ log.info("moveToZeroBlock called with id: {}", id);
+ canBus.callcmd(id.mid, CmdId.leisai_servo_move_to_zero);
+ waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.leisai_servo_move_to_zero));
+ }
+
+ public void waitForMod(LeisaiServoMId id, Integer overtime) throws HardwareException {
+ canBus.waitForMod(id.mid, overtime);
+ }
+
+
+ public Integer getReg(LeisaiServoMId id, LeisaiRegIndex regindex) throws HardwareException {
+ log.info("getReg called with id: {}, regindex: {}", id, regindex);
+ return canBus.moduleGetReg(id.mid, regindex.regIndex);
+ }
+
+ public Object getAllReg(LeisaiServoMId id) throws HardwareException {
+ log.info("getAllReg called with id: {}", id);
+ ObjectNode node = ZJsonHelper.createObjectNode();
+ for (LeisaiRegIndex regIndex : LeisaiRegIndex.values()) {
+ Integer regVal = getReg(id, regIndex);
+ log.info("read reg {} -> {}", regIndex, regVal);
+ node.put(regIndex.name(), getReg(id, regIndex));
+ }
+ return node;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/LiquidDistributionArmDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/LiquidDistributionArmDriver.java
new file mode 100644
index 0000000..bae9a04
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/LiquidDistributionArmDriver.java
@@ -0,0 +1,84 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.constants.ActionOvertimeConstant;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.Servo.LiquidArmMId;
+import com.iflytop.handacid.hardware.type.Servo.LiquidArmRegIndex;
+import com.iflytop.handacid.hardware.utils.ZJsonHelper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class LiquidDistributionArmDriver {
+ final private A8kCanBusService canBus;
+ private final ActionOvertimeConstant actionOvertimeConstant;
+
+ public void liquidDistributionArmEnable(LiquidArmMId id, int enable) throws HardwareException {
+ log.info("liquidDistributionArmEnable called with id: {}, enable: {}", id, enable);
+ canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_enable, enable);
+ }
+
+ public void liquidDistributionArmMoveTo(LiquidArmMId id, int index) throws HardwareException {
+ log.info("liquidDistributionArmMoveTo called with id: {}, index: {}", id, index);
+ canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_move_to, index);
+ }
+
+ public int liquidDistributionReadPos(LiquidArmMId id) throws HardwareException {
+ log.info("liquidDistributionReadPos called with id: {}", id);
+ var packet = canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_read_pos);
+ return packet.getContentI32(0);
+ }
+
+ public void liquidDistributionSetCurPosAsPresetPos(LiquidArmMId id, int index) throws HardwareException {
+ log.info("liquidDistributionSetCurPosAsPresetPos called with id: {}, index: {}", id, index);
+ canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_set_cur_pos_as_preset_pos, index);
+ }
+
+ public void liquidDistributionSetCurPosAsMidPos(LiquidArmMId id) throws HardwareException {
+ log.info("liquidDistributionSetCurPosAsMidPos called with id: {}", id);
+ canBus.callcmd(id.mid, CmdId.liquid_distribution_arm_set_cur_pos_as_mid_pos);
+ }
+
+ public void moduleStop(LiquidArmMId id) throws HardwareException {
+ log.info("moduleStop called with id: {}", id);
+ canBus.moduleStop(id.mid);
+ }
+
+ // ====== ===== ====== ===== ====== ===== Block ====== ===== ====== ===== ====== =====
+ public void liquidDistributionArmMoveToBlock(LiquidArmMId id, int index) throws HardwareException {
+ liquidDistributionArmMoveTo(id, index);
+ waitForMod(id, actionOvertimeConstant.get(id.mid, CmdId.liquid_distribution_arm_move_to));
+ }
+
+
+ public void waitForMod(LiquidArmMId id, Integer overtime) throws HardwareException {
+ canBus.waitForMod(id.mid, overtime);
+ }
+
+ public void setReg(LiquidArmMId id, LiquidArmRegIndex regindex, int val) throws HardwareException {
+ log.info("setReg called with id: {}, regindex: {}, val: {}", id, regindex, val);
+ canBus.moduleSetReg(id.mid, regindex.regIndex, val);
+ }
+
+ public Integer getReg(LiquidArmMId id, LiquidArmRegIndex regindex) throws HardwareException {
+ log.info("getReg called with id: {}, regindex: {}", id, regindex);
+ return canBus.moduleGetReg(id.mid, regindex.regIndex);
+ }
+
+ public Object getAllReg(LiquidArmMId id) throws HardwareException {
+ log.info("getAllReg called with id: {}", id);
+ ObjectNode node = ZJsonHelper.createObjectNode();
+ for (LiquidArmRegIndex regIndex : LiquidArmRegIndex.values()) {
+ Integer regVal = getReg(id, regIndex);
+ log.info("read reg {} -> {}", regIndex, regVal);
+ node.put(regIndex.name(), getReg(id, regIndex));
+ }
+ return node;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/MiniServoDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/MiniServoDriver.java
new file mode 100644
index 0000000..573f915
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/MiniServoDriver.java
@@ -0,0 +1,213 @@
+package com.iflytop.handacid.hardware.drivers;
+
+
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.A8kPacket;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.RegIndex;
+import com.iflytop.handacid.hardware.type.Servo.MiniServoMId;
+import com.iflytop.handacid.hardware.type.Servo.MiniServoRegIndex;
+import com.iflytop.handacid.hardware.type.error.A8kEcode;
+import com.iflytop.handacid.hardware.type.error.AEHardwareError;
+import com.iflytop.handacid.hardware.utils.OS;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class MiniServoDriver {
+ final private A8kCanBusService canBus;
+ static public final Integer actionOvertime = 10000;
+
+
+ public void moduleStop(MiniServoMId id) throws HardwareException {
+ canBus.moduleStop(id.mid);
+ }
+
+ public void miniServoEnable(MiniServoMId id, int enable) throws HardwareException {
+ log.debug("{} miniServoEnable {}", id.mid, enable);
+ callcmd(id.mid, CmdId.mini_servo_enable, enable);
+ }
+
+ public void miniServoForceEnable(MiniServoMId id, int enable) {
+ try {
+ miniServoEnable(id, enable);
+ } catch (HardwareException e) {
+ log.error("miniServoForceEnable error", e);
+ }
+ }
+
+ public Boolean miniServoPing(MiniServoMId id) {
+ try {
+ miniServoReadPos(id);
+ } catch (HardwareException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public int miniServoReadPos(MiniServoMId id) throws HardwareException {
+ var packet = callcmd(id.mid, CmdId.mini_servo_read_pos);
+ return packet.getContentI32(0);
+ }
+
+
+ private void miniServoMoveTo(MiniServoMId id, int pos) throws HardwareException {
+ callcmd(id.mid, CmdId.mini_servo_move_to, pos);
+ }
+
+ public void miniServoMoveToBlock(MiniServoMId id, int pos) throws HardwareException {
+ log.debug("{} miniServoMoveTo {}", id.mid, pos);
+ int retry = 0;
+ do {
+ try {
+ miniServoMoveTo(id, pos);
+ canBus.waitForMod(id.mid, actionOvertime);
+ return;
+ } catch (HardwareException e) {
+ if (e.getError().code.equals(A8kEcode.LOW_ERROR_SUBDEVICE_OVERTIME)) {
+ log.warn("CALL miniServoMoveToBlock {} FAIL, SUB DEVICE OVERTIME", id);
+ if (retry > 3) {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+ OS.hsleep(1000);
+ retry++;
+ } while (true);
+ }
+
+
+ public void miniServoRotate(MiniServoMId id, int direction) throws HardwareException {
+ log.debug("{} miniServoRotate {}", id.mid, direction);
+ callcmd(id.mid, CmdId.mini_servo_rotate, direction);
+ }
+
+ public void miniServoActiveCfg(MiniServoMId id) throws HardwareException {
+ callcmd(id.mid, CmdId.mini_servo_active_cfg);
+ }
+
+ public void miniServoStop(MiniServoMId id) throws HardwareException {
+ callcmd(id.mid, CmdId.mini_servo_stop);
+ }
+
+ public void miniServoSetMidPoint(MiniServoMId id) throws HardwareException {
+ callcmd(id.mid, CmdId.mini_servo_set_mid_point);
+ }
+
+ public int miniServoReadIoState(MiniServoMId id) throws HardwareException {
+ var packet = callcmd(id.mid, CmdId.mini_servo_read_io_state);
+ return packet.getContentI32(0);
+ }
+
+ public void miniServoRotateBlock(MiniServoMId id, int direction) throws HardwareException {
+ miniServoRotate(id, direction);
+ canBus.waitForMod(id.mid, actionOvertime);
+ }
+
+ public void miniServoRotateWithTorque(MiniServoMId id, int torque) throws HardwareException {
+ log.debug("{} miniServoRotateWithTorque {}", id.mid, torque);
+ callcmd(id.mid, CmdId.mini_servo_rotate_with_torque, torque);
+ }
+
+ public void waitForMod(MiniServoMId mid) throws HardwareException {
+ canBus.waitForMod(mid.mid, actionOvertime);
+ }
+
+ public void waitForMod(MiniServoMId... mid) throws HardwareException {
+ for (MiniServoMId m : mid) {
+ waitForMod(m);
+ }
+ }
+
+
+ // kmini_servo_set_cur_pos
+ private void miniServoSetCurPos(MiniServoMId id, Integer pos) throws HardwareException {
+ log.debug("{} miniServoSetCurPos {}", id.mid, pos);
+ callcmd2(id.mid, CmdId.mini_servo_set_cur_pos, 5000, pos);
+ }
+
+
+
+ public void setReg(MiniServoMId id, MiniServoRegIndex regindex, int val) throws HardwareException {
+ log.debug("{} setReg {} {}", id.mid, regindex, val);
+ canBus.moduleSetReg(id.mid, regindex.regIndex, val);
+ }
+
+ public Integer getReg(MiniServoMId id, MiniServoRegIndex regindex) throws HardwareException {
+ return canBus.moduleGetReg(id.mid, regindex.regIndex);
+ }
+
+ public Integer getRegNoEx(MiniServoMId id, MiniServoRegIndex regindex) {
+ try {
+ return canBus.moduleGetReg(id.mid, regindex.regIndex);
+ } catch (HardwareException e) {
+ log.error("readRegNoEx error", e);
+ return null;
+ }
+ }
+
+ public void miniServoWaitIsNotMove(MiniServoMId id, int acitionOvertime) throws HardwareException {
+ long startedAt = System.currentTimeMillis();
+ do {
+ var isMove = canBus.moduleGetReg(id.mid, RegIndex.kreg_mini_servo_is_move);
+ if (isMove != 0) {
+ break;
+ }
+ long now = System.currentTimeMillis();
+ if (now - startedAt > acitionOvertime) {
+ throw HardwareException.of(new AEHardwareError(A8kEcode.LOW_ERROR_OVERTIME, id.mid, null));
+ }
+
+ } while (true);
+ }
+
+ private A8kPacket callcmd(MId id, CmdId cmdId, Integer... args) throws HardwareException {
+ int retry = 0;
+ do {
+ try {
+ return canBus.callcmd(id, cmdId, args);
+ } catch (HardwareException e) {
+ if (e.getError().code.equals(A8kEcode.LOW_ERROR_SUBDEVICE_OVERTIME)) {
+ log.warn("CALL CMD {} FAIL, SUB DEVICE OVERTIME", cmdId);
+ if (retry > 3) {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+ OS.hsleep(1000);
+ retry++;
+ } while (true);
+ }
+
+ private A8kPacket callcmd2(MId id, CmdId cmdId, Integer timeout, Integer... args) throws HardwareException {
+ int retry = 0;
+ do {
+ try {
+ return canBus.callcmd2(id, cmdId, timeout, args);
+ } catch (HardwareException e) {
+ if (e.getError().code.equals(A8kEcode.LOW_ERROR_SUBDEVICE_OVERTIME)) {
+ log.warn("CALL CMD2 {} FAIL, SUB DEVICE OVERTIME", cmdId);
+ if (retry > 3) {
+ throw e;
+ }
+ } else {
+ throw e;
+ }
+ }
+ OS.hsleep(1000);
+ retry++;
+ } while (true);
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/MiniServoDriverWrapper.java b/src/main/java/com/iflytop/handacid/hardware/drivers/MiniServoDriverWrapper.java
new file mode 100644
index 0000000..c7ad50b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/MiniServoDriverWrapper.java
@@ -0,0 +1,34 @@
+package com.iflytop.handacid.hardware.drivers;
+
+
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.Servo.MiniServoMId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import static java.lang.Math.abs;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MiniServoDriverWrapper {
+ private final MiniServoDriver miniServoDriver;
+ // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ====
+ public void setSpeed(MiniServoMId servoMid, double speed) {
+ log.info("[ {} ] Setting speed to {}", servoMid.mid.getDescription(), speed);
+ }
+
+ // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ====
+ public void moveTo(MiniServoMId servoMid, double position) throws HardwareException
+ {
+ miniServoDriver.miniServoEnable(servoMid, 1);
+ int servoPosition = (int) (abs(position));
+ miniServoDriver.miniServoMoveToBlock(servoMid, servoPosition);
+ log.info("[ {} ] moveTo {}, servo position {} end", servoMid, position, servoPosition);
+ }
+
+ public void stop(MiniServoMId servoMid) throws HardwareException {
+ miniServoDriver.miniServoStop(servoMid);
+ log.info("[ {} ] stop", servoMid.mid.getDescription());
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/ModuleEnableCtrlDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/ModuleEnableCtrlDriver.java
new file mode 100644
index 0000000..855c2e9
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/ModuleEnableCtrlDriver.java
@@ -0,0 +1,78 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.Servo.MiniServoMId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class ModuleEnableCtrlDriver {
+
+ private final StepMotorCtrlDriver stepMotorCtrlDriver;
+ private final MiniServoDriver miniServoDriver;
+ private final A8kCanBusService canBus;
+
+ public void forceDisableAllMotor() {
+ // StepMotorMId.values()
+ for (StepMotorMId stepMotorMId : StepMotorMId.values()) {
+ try {
+ stepMotorCtrlDriver.stepMotorEnable(stepMotorMId, 0);
+ } catch (HardwareException e) {
+ log.info("disableStepMotor: {} failed, {}", stepMotorMId, e.getMessage());
+ }
+ }
+
+ for (MiniServoMId miniServoMId : MiniServoMId.values()) {
+ try {
+ miniServoDriver.miniServoEnable(miniServoMId, 0);
+ } catch (HardwareException e) {
+ log.info("disableMiniServo: {} failed, {}", miniServoMId, e.getMessage());
+ }
+ }
+
+
+ }
+
+ public void disableAllModule() throws HardwareException {
+
+ for (var motorId : StepMotorMId.values()) {
+ try {
+ stepMotorCtrlDriver.stepMotorStop(motorId);
+ stepMotorCtrlDriver.stepMotorEnable(motorId, 0);
+ } catch (HardwareException ignored) {
+ }
+ }
+
+ for (var miniServoId : MiniServoMId.values()) {
+ try {
+ miniServoDriver.miniServoEnable(miniServoId, 0);
+ } catch (HardwareException ignored) {
+ }
+ }
+ }
+
+
+ public void enableAllModule() throws HardwareException {
+ try {
+ for (StepMotorMId stepMotorMId : StepMotorMId.values())
+ stepMotorCtrlDriver.stepMotorEnable(stepMotorMId, 1);
+
+
+ for (MiniServoMId miniServoMId : MiniServoMId.values())
+ miniServoDriver.miniServoEnable(miniServoMId, 1);
+
+
+ } catch (HardwareException e) {
+ forceDisableAllMotor();
+ throw e;
+ }
+
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/MotorWrapperDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/MotorWrapperDriver.java
new file mode 100644
index 0000000..3de2074
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/MotorWrapperDriver.java
@@ -0,0 +1,105 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.iflytop.handacid.hardware.config.StepMotorConfig;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.StepMotor.MotorDirect;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorRegIndex;
+import com.iflytop.handacid.hardware.utils.Math.StepMotorConverter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.security.InvalidParameterException;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MotorWrapperDriver {
+ private final StepMotorConfig config_;
+ private final StepMotorCtrlDriver stepMotorCtrlDriver_;
+
+ // ==== ==== ==== ==== ==== ==== Get ==== ==== ==== ==== ==== ====
+ private double getSpeed(StepMotorMId stepMotorMId)
+ {
+ // TODO: 从单片机中获取速度
+ return 0.0;
+ }
+
+ // ==== ==== ==== ==== ==== ==== Set ==== ==== ==== ==== ==== ====
+ public void setSpeed(StepMotorMId stepMotorMId, double speed) throws HardwareException {
+ // 检查速度是否合法
+ // 设置速度到单片机
+ int motorSpeed = (int) Math.abs(speed); // 转换速度为整数
+ stepMotorCtrlDriver_.setReg(stepMotorMId, StepMotorRegIndex.kreg_step_motor_default_velocity, motorSpeed);
+ }
+
+ // ==== ==== ==== ==== ==== ==== Ctrl ==== ==== ==== ==== ==== ====
+ public void motorEnable(StepMotorMId stepMotorMId, Boolean enable) throws HardwareException {
+ Integer i_enable = enable ? 1 : 0;
+ stepMotorCtrlDriver_.stepMotorEnable(stepMotorMId, i_enable);
+ }
+
+ public void openClamp(StepMotorMId stepMotorMId) throws HardwareException
+ {
+ log.info("Motor {} Open Clamp", stepMotorMId.mid.getDescription());
+ motorEnable(stepMotorMId, true);
+ stepMotorCtrlDriver_.stepMotorOpenClamp(stepMotorMId);
+ }
+
+ public void closeClamp(StepMotorMId stepMotorMId) throws HardwareException
+ {
+ log.info("Motor {} Close Clamp", stepMotorMId.mid.getDescription());
+ motorEnable(stepMotorMId, true);
+ stepMotorCtrlDriver_.stepMotorCloseClamp(stepMotorMId);
+ }
+
+ public void moveToHome(StepMotorMId stepMotorMId) throws HardwareException {
+ log.info("Motor {} Move To Home", stepMotorMId.mid.getDescription());
+ motorEnable(stepMotorMId, true);
+ stepMotorCtrlDriver_.stepMotorEasyMoveToZeroBlock(stepMotorMId);
+ }
+
+ public void moveTo(StepMotorMId stepMotorMId, double position) throws HardwareException {
+ // 检查位置是否合法
+ if (Math.abs(position) > Math.abs(config_.getMaxLimit(stepMotorMId))) {
+ log.error("Motor {} Move To position out of range, position {}, limit {}", stepMotorMId.mid.getDescription(), position, config_.getMaxLimit(stepMotorMId));
+ throw new InvalidParameterException("position is out of range");
+ }
+
+ motorEnable(stepMotorMId, true);
+ int motorPosition = StepMotorConverter.toMotorPosition(position);
+ log.info("Motor {} Move To position {}, Motor Position {}", stepMotorMId.mid.getDescription(), position, motorPosition);
+ stepMotorCtrlDriver_.stepMotorEasyMoveToBlock(stepMotorMId, motorPosition);
+ }
+
+ public void moveBy(StepMotorMId stepMotorMId, double distance) throws HardwareException {
+ // 检查位置是否合法
+ if (Math.abs(distance) > Math.abs(config_.getMaxLimit(stepMotorMId))) {
+ log.error("Motor {} Move By distance out of range, distance {}, limit {}", stepMotorMId.mid.getDescription(), distance, config_.getMaxLimit(stepMotorMId));
+ throw new InvalidParameterException("Distance is out of range");
+ }
+
+ motorEnable(stepMotorMId, true);
+ int motorPosition = StepMotorConverter.toMotorPosition(distance);
+ log.info("Motor {} Move By distance {}, Motor Position {}", stepMotorMId.mid.getDescription(), distance, motorPosition);
+ stepMotorCtrlDriver_.stepMotorEasyMoveByBlock(stepMotorMId, motorPosition);
+ }
+
+ public void rotate(StepMotorMId stepMotorMId, MotorDirect direct) throws Exception
+ {
+ motorEnable(stepMotorMId, true);
+ stepMotorCtrlDriver_.stepMotorEasyRotate(stepMotorMId, direct.getValue());
+ }
+
+ public void stop(StepMotorMId stepMotorMId) throws HardwareException {
+ log.info("Motor {} Stop", stepMotorMId.mid.getDescription());
+ stepMotorCtrlDriver_.stepMotorStop(stepMotorMId);
+ motorEnable(stepMotorMId, true);
+ }
+
+ public void moveToEnd(StepMotorMId stepMotorMId) throws HardwareException {
+ log.info("Motor {} Move To End", stepMotorMId.mid.getDescription());
+ stepMotorCtrlDriver_.stepMotorEasyMoveToEndPoint(stepMotorMId);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/OutputIOCtrlDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/OutputIOCtrlDriver.java
new file mode 100644
index 0000000..217bfc0
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/OutputIOCtrlDriver.java
@@ -0,0 +1,36 @@
+package com.iflytop.handacid.hardware.drivers;
+
+
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.IO.OutputIOMId;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class OutputIOCtrlDriver {
+ private final A8kCanBusService canBus;
+
+ public void setIOState(OutputIOMId IOOutput, Boolean state) throws HardwareException {
+ canBus.callcmd(IOOutput.mid, CmdId.write_out_io, IOOutput.ioIndex, state ? 1 : 0);
+ }
+
+ public boolean getIOState(OutputIOMId IOOutput) throws HardwareException {
+ boolean isOpen = canBus.callcmd(IOOutput.mid, CmdId.read_out_io, IOOutput.ioIndex).getContentI32(0) != 0;
+ if(IOOutput.mirror)
+ {
+ isOpen = !isOpen;
+ }
+ return isOpen;
+ }
+
+ public void open(OutputIOMId IOOutput) throws HardwareException {
+ this.setIOState(IOOutput, true);
+ }
+
+ public void close(OutputIOMId IOOutput) throws HardwareException {
+ this.setIOState(IOOutput, false);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/StepMotorCtrlDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/StepMotorCtrlDriver.java
new file mode 100644
index 0000000..4776f21
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/StepMotorCtrlDriver.java
@@ -0,0 +1,192 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.constants.ActionOvertimeConstant;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.A8kPacket;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.ModuleStatus;
+import com.iflytop.handacid.hardware.type.RegIndex;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorRegIndex;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorSpeedLevel;
+import com.iflytop.handacid.hardware.utils.ZJsonHelper;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class StepMotorCtrlDriver {
+ static Logger logger = org.slf4j.LoggerFactory.getLogger(StepMotorCtrlDriver.class);
+
+ final private A8kCanBusService canBus;
+
+ private final ActionOvertimeConstant actionOvertimeConstant;
+
+ public void stepMotorEasyMoveBy(StepMotorMId id, Integer dpos) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_move_by, dpos);
+ }
+
+ public void stepMotorEasyMoveTo(StepMotorMId id, Integer pos) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to, pos);
+ }
+
+ public void stepMotorEasyMoveToZero(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to_zero);
+ }
+
+ public void setCurrentPos(StepMotorMId id, Integer pos) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_set_current_pos, pos);
+ }
+
+ void stepMotorEasyMoveToZeroPointQuick(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to_zero_point_quick);
+ }
+
+ void stepMotorEasyMoveToEndPoint(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_move_to_end_point);
+ }
+
+ public void stepMotorEnable(StepMotorMId mid, Integer enable) throws HardwareException {
+ canBus.callcmd(mid.mid, CmdId.step_motor_enable, enable);
+ }
+
+ public void stepMotorOpenClamp(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_set_clamp, 1);
+ }
+
+ public void stepMotorCloseClamp(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_set_clamp, 0);
+ }
+
+ public Integer stepMotorReadPos(StepMotorMId id) throws HardwareException {
+ A8kPacket packet = canBus.callcmd(id.mid, CmdId.step_motor_read_pos);
+ return packet.getContentI32(0);
+ }
+
+ public Integer stepMotorReadEncPos(StepMotorMId id) throws HardwareException {
+ A8kPacket packet = canBus.callcmd(id.mid, CmdId.step_motor_read_enc_pos);
+ return packet.getContentI32(0);
+ }
+
+ public void stepMotorEasyRotate(StepMotorMId id, Integer direction) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_rotate, direction);
+ }
+
+ public void stepMotorEasyMoveByBlock(StepMotorMId id, Integer dpos) throws HardwareException {
+ logger.debug("stepMotorEasyMoveByBlock {} {}", id, dpos);
+ stepMotorEasyMoveBy(id, dpos);
+ canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_by));
+ }
+
+ public void waitForMod(StepMotorMId id, Integer overtime) throws HardwareException {
+ canBus.waitForMod(id.mid, overtime);
+ }
+
+ public void stepMotorEasyMoveToBlock(StepMotorMId id, Integer pos) throws HardwareException {
+ logger.debug("stepMotorEasyMoveToBlock {} {}", id, pos);
+ stepMotorEasyMoveTo(id, pos);
+ canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to));
+ }
+
+ public void stepMotorEasyMoveToZeroBlock(StepMotorMId id) throws HardwareException {
+ logger.debug("stepMotorEasyMoveToZeroBlock {}", id);
+ stepMotorEasyMoveToZero(id);
+ canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to_zero));
+ }
+
+ public Integer stepMotorReadPosByMoveToZeroBlock(StepMotorMId id) throws HardwareException {
+ stepMotorEnable(id, 1);
+ stepMotorEasyMoveToZeroBlock(id);
+ Integer nowpos = stepMotorReadPos(id);
+ Integer measurepos = -canBus.moduleGetReg(id.mid, RegIndex.kreg_step_motor_dpos) + nowpos;
+ stepMotorEnable(id, 0);
+ return measurepos;
+ }
+
+ public void stepMotorEasyMoveToZeroPointQuickBlock(StepMotorMId id) throws HardwareException {
+ stepMotorEasyMoveToZeroPointQuick(id);
+ canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to_zero_point_quick));
+ }
+
+ public void stepMotorStop(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_stop, 0);
+ }
+
+
+ // step_motor_move_by(0x021d, "STEP_MOTOR_MOVE_BY"), // (dpos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ // step_motor_move_to(0x021e, "STEP_MOTOR_MOVE_TO"), // (pos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ // step_motor_move_to_zero_point_quick(0x021f, "STEP_MOTOR_MOVE_TO_ZERO_POINT_QUICK"), // (speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ // step_motor_rotate(0x0220, "STEP_MOTOR_ROTATE"), //
+
+ public void moveByBlock(StepMotorMId id, Integer dpos, StepMotorSpeedLevel speedLevel) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_move_by, dpos, speedLevel.ordinal());
+ waitForMod(id, actionOvertimeConstant.get(id, CmdId.step_motor_move_by));
+ }
+
+ public void moveToBlock(StepMotorMId id, Integer pos, StepMotorSpeedLevel speedLevel) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_move_to, pos, speedLevel.ordinal());
+ waitForMod(id, actionOvertimeConstant.get(id, CmdId.step_motor_move_to));
+ }
+
+ public void moveToZeroQuickBlock(StepMotorMId id, StepMotorSpeedLevel speedLevel) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_move_to_zero_point_quick, speedLevel.ordinal());
+ waitForMod(id, actionOvertimeConstant.get(id, CmdId.step_motor_move_to_zero_point_quick));
+ }
+
+ public void rotate(StepMotorMId id, Integer direction, StepMotorSpeedLevel speedLevel) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_rotate, direction, speedLevel.ordinal());
+
+ }
+
+ public Boolean stepMotorReadIoState(StepMotorMId id, Integer ioindex) throws HardwareException {
+ var packet = canBus.callcmd(id.mid, CmdId.step_motor_read_io_state, ioindex);
+ return packet.getContentI32(0) != 0;
+ }
+
+ public void stepMotorEasyMoveToEndPointBlock(StepMotorMId id) throws HardwareException {
+ stepMotorEasyMoveToEndPoint(id);
+ canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_move_to_end_point));
+ }
+
+ void stepMotorEasyReciprocatingMotion(StepMotorMId id, Integer startpos, Integer endpos, Integer times) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.step_motor_easy_reciprocating_motion, startpos, endpos, times);
+ }
+
+ public void stepMotorEasyReciprocatingMotionBlock(StepMotorMId id, Integer startpos, Integer endpos, Integer times) throws HardwareException {
+ stepMotorEasyReciprocatingMotion(id, startpos, endpos, times);
+ canBus.waitForMod(id.mid, actionOvertimeConstant.get(id, CmdId.step_motor_easy_reciprocating_motion));
+ }
+
+ public Boolean isHasMoveToZero(StepMotorMId id) throws HardwareException {
+ return getReg(id, StepMotorRegIndex.kreg_step_motor_has_move_zero) != 0;
+ }
+
+ public void setReg(StepMotorMId id, StepMotorRegIndex regIndex, Integer val) throws HardwareException {
+ canBus.moduleSetReg(id.mid, regIndex.regIndex, val);
+ }
+
+ public Integer getReg(StepMotorMId id, StepMotorRegIndex regIndex) throws HardwareException {
+ return canBus.moduleGetReg(id.mid, regIndex.regIndex);
+ }
+
+ public void resetRegs(StepMotorMId id) throws HardwareException {
+ canBus.callcmd(id.mid, CmdId.module_reset_reg);
+ }
+
+ public ModuleStatus readStatus(StepMotorMId id) throws HardwareException {
+ return canBus.moduleGetStatus(id.mid);
+ }
+
+ public Object getAllReg(StepMotorMId id) throws HardwareException {
+ ObjectNode node = ZJsonHelper.createObjectNode();
+ for (StepMotorRegIndex regIndex : StepMotorRegIndex.values()) {
+ Integer regVal = getReg(id, regIndex);
+ logger.debug("read reg {} -> {}", regIndex, regVal);
+ node.put(regIndex.name(), getReg(id, regIndex));
+ }
+ return node;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/TecHeaterRodDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/TecHeaterRodDriver.java
new file mode 100644
index 0000000..c2c6c27
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/TecHeaterRodDriver.java
@@ -0,0 +1,93 @@
+package com.iflytop.handacid.hardware.drivers;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.A8kPacket;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.TecHeaterRegIndex;
+import com.iflytop.handacid.hardware.type.TecHeaterRodMId;
+import com.iflytop.handacid.hardware.utils.ZJsonHelper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class TecHeaterRodDriver {
+ final private A8kCanBusService canBus;
+ static final Double DEFAULT_TARGET_TEMPERATURE = 100.0;
+ static final Integer DEFAULT_CHANNEL = 1;
+
+ // application API
+ public void open(TecHeaterRodMId id, Double temperature) throws HardwareException {
+ this.enable(id, true);
+ this.setTargetTemperature(id, temperature);
+ }
+
+ public void close(TecHeaterRodMId id) throws HardwareException {
+ this.enable(id, false);
+ }
+
+ // driver API
+ public void openPower(TecHeaterRodMId id, boolean is_open) throws HardwareException {
+ log.info("openPower called with id: {}, is_open: {}", id, is_open);
+ Integer powerOn = is_open ? 1 : 0;
+ canBus.callcmd(id.mid, CmdId.tec103_heater_power_on, powerOn);
+ }
+
+ public void enable(TecHeaterRodMId id, boolean is_enable) throws HardwareException {
+ log.info("enable called with id: {}, is_enable: {}", id, is_enable);
+ Integer enable = is_enable ? 1 : 0;
+ canBus.callcmd(id.mid, CmdId.tec103_heater_enable, DEFAULT_CHANNEL, enable);
+ }
+
+ public void setTargetTemperature(TecHeaterRodMId id, Double targetTemperature) throws HardwareException {
+ Integer targetTemperatureInt = (int) (targetTemperature * DEFAULT_TARGET_TEMPERATURE);
+ log.info("setTargetTemperature called with id: {}, targetTemperature: {}", id, targetTemperature);
+ canBus.callcmd(id.mid, CmdId.tec103_heater_set_target_temperature, DEFAULT_CHANNEL, targetTemperatureInt);
+ }
+
+ public Double readTemperature(TecHeaterRodMId id) throws HardwareException {
+ log.info("readTemperature called with id: {}", id);
+ A8kPacket packet = canBus.callcmd(id.mid, CmdId.tec103_heater_read_temperature, DEFAULT_CHANNEL);
+ return packet.getContentI32(0) / DEFAULT_TARGET_TEMPERATURE;
+ }
+
+ public void factoryReset(TecHeaterRodMId id) throws HardwareException {
+ log.info("factoryReset called with id: {}", id);
+ canBus.callcmd(id.mid, CmdId.tec103_heater_factory_reset);
+ }
+
+ public void reset(TecHeaterRodMId id) throws HardwareException {
+ log.info("reset called with id: {}", id);
+ canBus.callcmd(id.mid, CmdId.liquid_valve_reset);
+ }
+
+
+ public void waitForMod(TecHeaterRodMId id, Integer overtime) throws HardwareException {
+ canBus.waitForMod(id.mid, overtime);
+ }
+
+ public void setReg(TecHeaterRodMId id, TecHeaterRegIndex regindex, int val) throws HardwareException {
+ log.info("setReg called with id: {}, regindex: {}, val: {}", id, regindex, val);
+ canBus.moduleSetReg(id.mid, regindex.regIndex, val);
+ }
+
+ public Integer getReg(TecHeaterRodMId id, TecHeaterRegIndex regindex) throws HardwareException {
+ log.info("getReg called with id: {}, regindex: {}", id, regindex);
+ return canBus.moduleGetReg(id.mid, regindex.regIndex);
+ }
+
+ public Object getAllReg(TecHeaterRodMId id) throws HardwareException {
+ log.info("getAllReg called with id: {}", id);
+ ObjectNode node = ZJsonHelper.createObjectNode();
+ for (TecHeaterRegIndex regIndex : TecHeaterRegIndex.values()) {
+ Integer regVal = getReg(id, regIndex);
+ log.info("read reg {} -> {}", regIndex, regVal);
+ node.put(regIndex.name(), getReg(id, regIndex));
+ }
+ return node;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/drivers/TricolorLightDriver.java b/src/main/java/com/iflytop/handacid/hardware/drivers/TricolorLightDriver.java
new file mode 100644
index 0000000..1850dec
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/drivers/TricolorLightDriver.java
@@ -0,0 +1,30 @@
+package com.iflytop.handacid.hardware.drivers;
+
+
+import com.iflytop.handacid.hardware.comm.can.A8kCanBusService;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.MId;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TricolorLightDriver {
+ private final A8kCanBusService canBus;
+ public enum Color {
+ RED, GREEN, BLUE
+ }
+
+ public void open(MId mid, Color color) throws HardwareException {
+ log.info("Open TricolorLight driver Color {}", color);
+ canBus.callcmd(mid, CmdId.tricolor_light_on, color.ordinal());
+ }
+
+ public void close(MId mid) throws HardwareException{
+ log.info("Close TricolorLight driver Color");
+ canBus.callcmd(mid, CmdId.tricolor_light_off);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/exception/HardwareException.java b/src/main/java/com/iflytop/handacid/hardware/exception/HardwareException.java
new file mode 100644
index 0000000..5fb176a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/exception/HardwareException.java
@@ -0,0 +1,63 @@
+package com.iflytop.handacid.hardware.exception;
+
+
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.error.A8kEcode;
+import com.iflytop.handacid.hardware.type.error.AECodeError;
+import com.iflytop.handacid.hardware.type.error.AEHardwareError;
+import com.iflytop.handacid.hardware.type.error.AppError;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Getter
+public class HardwareException extends Exception {
+ // 构造函数
+
+ public AppError error;
+
+
+ public HardwareException(A8kEcode ecodeType) {
+ super(String.format("%s", ecodeType));
+ this.error = new AppError(ecodeType);
+ }
+
+ public HardwareException(AppError error) {
+ super(String.format("%s", error.toString()));
+ this.error = error;
+ }
+
+ public static HardwareException of(A8kEcode errorCode, MId mid, CmdId cmdId) {
+ return new HardwareException(new AEHardwareError(errorCode, mid, cmdId));
+ }
+
+ public static HardwareException of(A8kEcode errorCode) {
+ return new HardwareException(new AppError(errorCode));
+ }
+
+ public static HardwareException of(A8kEcode errorCode, MId mid) {
+ return new HardwareException(new AEHardwareError(errorCode, mid, null));
+ }
+
+ public static HardwareException of(A8kEcode errorCode, String exmsg, Object... args) {
+ return new HardwareException(new AppError(errorCode, exmsg, args));
+ }
+
+ public static HardwareException of(AEHardwareError AEHardwareError) {
+ return new HardwareException(AEHardwareError);
+ }
+
+ public static HardwareException of(AECodeError AECodeError) {
+ return new HardwareException(AECodeError);
+ }
+
+
+ public static HardwareException ofAECodeError(String fmt, Object... args) {
+ return new HardwareException(new AECodeError(fmt, args));
+ }
+
+// public static HardwareException ofSimplePrompt(String fmt, Object... args) {
+// return new HardwareException(new AESimpleErrorPrompt(fmt, args));
+// }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/factory/A8kPacketFactory.java b/src/main/java/com/iflytop/handacid/hardware/factory/A8kPacketFactory.java
new file mode 100644
index 0000000..8d8fd34
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/factory/A8kPacketFactory.java
@@ -0,0 +1,51 @@
+package com.iflytop.handacid.hardware.factory;
+
+import com.iflytop.handacid.hardware.type.A8kPacket;
+import com.iflytop.handacid.hardware.type.CmdId;
+
+import java.util.List;
+
+public class A8kPacketFactory {
+
+ //typedef struct {
+ // uint8_t packetType;
+ // uint16_t cmdid;
+ // uint8_t moduleId;
+ // uint16_t index;
+ // uint8_t datalen;
+ // uint8_t data[];
+ // /* int8_t checksum;*/
+ //} zcr_cmd_header_t;
+
+ static public A8kPacket buildPacket(Integer moduleId, int packetType, Integer cmdId, Integer[] params) {
+ return A8kPacket.createPacket(moduleId, packetType, cmdId, params);
+ }
+
+ static public A8kPacket buildCMDPacket(Integer moduleId, Integer cmdId, Integer[] params) {
+ return buildPacket(moduleId, (byte) 0, cmdId, params);
+ }
+
+ static public A8kPacket buildCMDPacket(Integer moduleId, Integer cmdId, List params) {
+ Integer[] array = new Integer[params.size()];
+ params.toArray(array);
+ return buildPacket(moduleId, A8kPacket.PACKET_TYPE_CMD, cmdId, array);
+ }
+
+ static public A8kPacket buildReportPacket(Integer moduleId, Integer cmdId, Integer[] params) {
+ return buildPacket(moduleId, (byte) 3, cmdId, params);
+ }
+
+ // static public A8kPacket build_event_a8000_idcard_online() {
+ // return buildReportPacket(MId.A8kIdCardReader.toInt(), CmdId.event_a8000_idcard_online.index, new Integer[0]);
+ // }
+ //
+ // static public A8kPacket build_event_a8000_idcard_offline() {
+ // return buildReportPacket(MId.A8kIdCardReader.toInt(), CmdId.event_a8000_idcard_offline.index, new Integer[0]);
+ // }
+
+ public static void main(String[] args) {
+ var packet = buildCMDPacket(1, CmdId.module_get_reg.toInt(), List.of(1, 2, 3));
+ System.out.println(packet.toByteString());
+ }
+}
+
diff --git a/src/main/java/com/iflytop/handacid/hardware/service/AppEventBusService.java b/src/main/java/com/iflytop/handacid/hardware/service/AppEventBusService.java
new file mode 100644
index 0000000..3e0508e
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/service/AppEventBusService.java
@@ -0,0 +1,93 @@
+package com.iflytop.handacid.hardware.service;
+
+import com.iflytop.handacid.hardware.type.appevent.AppEvent;
+import jakarta.annotation.PostConstruct;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * 事件总线
+ *
+ */
+@Component
+public class AppEventBusService implements ApplicationListener {
+ public static final Logger logger = LoggerFactory.getLogger(AppEventBusService.class);
+
+ @FunctionalInterface
+ public interface AppEventListener {
+ void onAppEvent(AppEvent event);
+ }
+
+
+ Thread eventProcessorThread;
+ BlockingQueue eventQueue = new LinkedBlockingQueue<>();
+ List listeners = new ArrayList<>();
+
+ @PostConstruct
+ public void init() {
+
+ }
+
+ public void regListener(AppEventListener listener) {
+ listeners.add(listener);
+ }
+
+
+ public void pushEvent(AppEvent event) {
+ logger.info("pushEvent: {} {}", event.getClass().getSimpleName(), event);
+ eventQueue.add(event);
+ }
+
+// public void pushAppPromptEvent(String msg) {
+// pushEvent(new AppPromptEvent(ZAppPromoptFactory.buildNotifyPromopt(msg)));
+// }
+
+// public void pushAppExceptionEvent(Exception e) {
+// pushEvent(new AppExceptionEvent(e));
+// }
+
+// public void pushAppTubeholderSettingUpdateEvent() {
+// pushEvent(new AppTubeholderSettingUpdateEvent());
+// }
+
+ private void eventBusSchedule() {
+ while (!Thread.currentThread().isInterrupted()) {
+ AppEvent event = null;
+ try {
+ event = eventQueue.take();
+ } catch (InterruptedException ignored) {
+ }
+ callOnEvent(event);
+ }
+ }
+
+ private void callOnEvent(AppEvent event) {
+ // logger.info("Processing event: {}", event.getClass().getSimpleName());
+ for (AppEventListener listener : listeners) {
+ listener.onAppEvent(event);
+ }
+ }
+
+ @Override
+ public void onApplicationEvent(ContextRefreshedEvent event) {
+ logger.info("Spring context refreshed");
+ if (eventProcessorThread == null) {
+ eventProcessorThread = new Thread(new Runnable() {
+ public void run() {
+ logger.info("Starting event bus schedule");
+ eventBusSchedule();
+ }
+ });
+ eventProcessorThread.start();
+ }
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/service/GDDeviceStatusService.java b/src/main/java/com/iflytop/handacid/hardware/service/GDDeviceStatusService.java
new file mode 100644
index 0000000..bd4fa00
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/service/GDDeviceStatusService.java
@@ -0,0 +1,71 @@
+package com.iflytop.handacid.hardware.service;
+
+import com.iflytop.handacid.hardware.drivers.InputDetectDriver;
+import com.iflytop.handacid.hardware.drivers.MiniServoDriver;
+import com.iflytop.handacid.hardware.drivers.StepMotorCtrlDriver;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.IO.InputIOMId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import com.iflytop.handacid.hardware.type.driver.HeaterRodSlavedId;
+import com.iflytop.handacid.hardware.utils.Math.StepMotorConverter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class GDDeviceStatusService {
+ private final InputDetectDriver inputDetectDriver;
+ //private final HeaterRodDriver heaterRodDriver;
+ private final StepMotorCtrlDriver stepMotorCtrlDriver;
+ private final MiniServoDriver miniServoDriver;
+
+ /**
+ * 获取步进电机的原点状态
+ *
+ * @param stepMotorMId
+ * @return true 在原点 false 不在原点
+ */
+ public Boolean stepMotorIsOrigin(StepMotorMId stepMotorMId) throws HardwareException {
+ return stepMotorCtrlDriver.stepMotorReadIoState(stepMotorMId, 0);
+ }
+
+ /**
+ * 获取 拍子 试管架拍子 急停 到位信号
+ *
+ * @param inputIOMId
+ * @return
+ * @throws HardwareException
+ */
+ public Boolean getInputState(InputIOMId inputIOMId) throws HardwareException {
+ return inputDetectDriver.getIOState(inputIOMId);
+ }
+
+ /**
+ * 获取加热棒温度
+ *
+ * @param heaterRodSlavedId
+ * @return
+ * @throws Exception
+ */
+ public Double getHeaterRodTemperature(HeaterRodSlavedId heaterRodSlavedId) throws Exception {
+ return null;
+ //return heaterRodDriver.getTemperature(heaterRodSlavedId);
+ }
+
+ /**
+ * 获取电机位置
+ *
+ * @param stepMotorMId
+ * @return
+ * @throws HardwareException
+ */
+ public Double getStepMotorPostion(StepMotorMId stepMotorMId) throws HardwareException {
+ Integer motorPosition = stepMotorCtrlDriver.stepMotorReadPos(stepMotorMId);
+ Double realPosition = StepMotorConverter.toUserPosition(motorPosition);
+ return realPosition;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/service/IOService.java b/src/main/java/com/iflytop/handacid/hardware/service/IOService.java
new file mode 100644
index 0000000..c040365
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/service/IOService.java
@@ -0,0 +1,5 @@
+package com.iflytop.handacid.hardware.service;
+
+// TODO:
+public class IOService {
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/service/StepMotorService.java b/src/main/java/com/iflytop/handacid/hardware/service/StepMotorService.java
new file mode 100644
index 0000000..64ad18e
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/service/StepMotorService.java
@@ -0,0 +1,262 @@
+package com.iflytop.handacid.hardware.service;
+
+import com.iflytop.handacid.hardware.drivers.StepMotorCtrlDriver;
+import com.iflytop.handacid.hardware.exception.HardwareException;
+import com.iflytop.handacid.hardware.type.StepMotor.DeviceStepMotorId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorMId;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorRegIndex;
+import com.iflytop.handacid.hardware.type.StepMotor.StepMotorSpeedLevel;
+import com.iflytop.handacid.hardware.type.error.A8kEcode;
+import com.iflytop.handacid.hardware.type.error.AppError;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class StepMotorService {
+ private final StepMotorCtrlDriver stepMotorCtrlDriver_;
+
+ StepMotorSpeedLevel speedLevel_ = StepMotorSpeedLevel.DEFAULT;
+
+ private void throwDeviceNull(DeviceStepMotorId id) throws HardwareException {
+ throw new HardwareException(new AppError(A8kEcode.PE_PARAM_OUT_OF_RANGE, "StepMotorMId is null for id: " + id));
+ }
+
+ public void setStepMotorSpeedLevel(StepMotorSpeedLevel speedLevel) {
+ this.speedLevel_ = speedLevel;
+ }
+
+ public void enableMotor(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.stepMotorEnable(stepId, 1);
+ }
+
+ public void disableMotor(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.stepMotorEnable(stepId, 0);
+ }
+
+ public void stepMotorEasyMoveToZero(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.stepMotorEasyMoveToZero(stepId);
+ }
+
+ public void stepMotorEasyMoveToZeroPointQuickBlock(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.stepMotorEasyMoveToZeroPointQuickBlock(stepId);
+ }
+
+ public void stepMotorStop(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.stepMotorStop(stepId);
+ }
+
+ public void stepMotorEasyMoveByForward(DeviceStepMotorId id, Integer dpos) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ log.info("stepMotorEasyMoveForward: {} {}", id, dpos);
+ stepMotorCtrlDriver_.moveByBlock(stepId, dpos, speedLevel_);
+ }
+
+ public void stepMotorEasyMoveByBackward(DeviceStepMotorId id, Integer dpos) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ log.info("stepMotorEasyMoveBackward: {} {}", id, -dpos);
+ stepMotorCtrlDriver_.moveByBlock(stepId, -dpos, speedLevel_);
+ }
+
+ public void stepMotorEasyMoveTo(DeviceStepMotorId id, Integer pos) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ log.info("stepMotorEasyMoveTo: {} {}", id, pos);
+ stepMotorCtrlDriver_.moveToBlock(stepId, pos, speedLevel_);
+ }
+
+ public void rotateForward(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.rotate(stepId, 1, speedLevel_);
+ }
+
+ public void rotateBackward(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.rotate(stepId, -1, speedLevel_);
+ }
+
+ public Integer readEncPos(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ return stepMotorCtrlDriver_.stepMotorReadEncPos(stepId);
+ }
+
+ public Integer readPos(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ return stepMotorCtrlDriver_.stepMotorReadPos(stepId);
+ }
+
+ public void setReg(DeviceStepMotorId id, StepMotorRegIndex reg, Integer val) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, reg, val);
+ }
+
+ public void setStartAndStopVel(DeviceStepMotorId id, Integer v) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_vstart, v);
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_vstop, v);
+ }
+
+ public void setV1(DeviceStepMotorId id, Integer v) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_v1, v);
+ }
+
+ public void setA1AndD1(DeviceStepMotorId id, Integer acc) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_a1, acc);
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_d1, acc);
+ }
+
+ public void setAmaxAndDmax(DeviceStepMotorId id, Integer acc) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_amax, acc);
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_dmax, acc);
+ }
+
+ public void setDefaultVel(DeviceStepMotorId id, Integer v) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_default_velocity, v);
+ }
+
+ public void setSpeed(DeviceStepMotorId id, Integer low_speed, Integer mid_speed, Integer high_speed) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_low_velocity, low_speed);
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_mid_velocity, mid_speed);
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_high_velocity, high_speed);
+ }
+
+ public void setMres(DeviceStepMotorId id, Integer mres) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_mres, mres);
+ }
+
+ public void setIRUN(DeviceStepMotorId id, Integer irun) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_irun, irun);
+ }
+
+ public void setIHOLD(DeviceStepMotorId id, Integer ihold) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_ihold, ihold);
+ }
+
+ public void setOneCirclePulse(DeviceStepMotorId id, Integer pulse, Integer denominator) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_one_circle_pulse, pulse);
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_one_circle_pulse_denominator, denominator);
+ }
+
+ public void setDZero(DeviceStepMotorId id, Integer dzero) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ stepMotorCtrlDriver_.setReg(stepId, StepMotorRegIndex.kreg_step_motor_dzero_pos, dzero);
+ }
+
+ public Integer readReg(DeviceStepMotorId id, StepMotorRegIndex reg) {
+ try {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ return stepMotorCtrlDriver_.getReg(stepId, reg);
+ } catch (HardwareException e) {
+ return 0;
+ }
+ }
+
+ public Map readAllRegs(DeviceStepMotorId id) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ //return stepMotorCtrlDriver_.getAllReg(stepId); todo
+ return null;
+ }
+
+ public Boolean readIoState(DeviceStepMotorId id, Integer ioindex) throws HardwareException {
+ StepMotorMId stepId = id.getStepMotorMId();
+ if (stepId == null) {
+ throwDeviceNull(id);
+ }
+ return stepMotorCtrlDriver_.stepMotorReadIoState(stepId, ioindex);
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/A8kPacket.java b/src/main/java/com/iflytop/handacid/hardware/type/A8kPacket.java
new file mode 100644
index 0000000..104de6a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/A8kPacket.java
@@ -0,0 +1,227 @@
+package com.iflytop.handacid.hardware.type;
+
+import com.iflytop.handacid.common.utils.ByteArray;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class A8kPacket {
+
+ //#pragma pack(push, 1)
+ //typedef struct {
+ // uint8_t packetType;
+ // uint16_t cmdid;
+ // uint8_t moduleId;
+ // uint16_t index;
+ // uint8_t datalen;
+ // uint8_t data[];
+ // /* int8_t checksum;*/
+ //} zcr_cmd_header_t;
+ //
+ //#pragma pack(pop)
+ //typedef enum {
+ // kptv2_cmd = 0xA0,
+ // kptv2_ack = 0xA1,
+ // kptv2_error_ack = 0xA2,
+ // kptv2_event = 0xA3,
+ //} zcan_cmd_packet_type_t;
+ //
+
+ /**
+ *
+ * @WARNING
+ * 1. 修改这里时,需要注意连同createPacket一起修改
+ * 2. PACKET_MIN_LEN 比Header多一个字节,是因为还有一个字节的校验位
+ */
+
+ public static final int PACKET_TYPE_OFFSET = 0;
+ public static final int CMDID_OFFSET = 1;
+ public static final int MODULE_ID_OFFSET = 3;
+ public static final int INDEX_OFFSET = 4;
+ public static final int DATA_LEN_OFFSET = 6;
+ public static final int DATA_OFFSET = 7;
+
+ public static final int PACKET_MIN_LEN = 8;//
+
+ public static final int CMD_OVERTIME = 1500;
+
+
+ public static final int PACKET_TYPE_CMD = 0xA0;
+ public static final int PACKET_TYPE_ACK = 0xA1;
+ public static final int PACKET_TYPE_ERROR_ACK = 0xA2;
+ public static final int PACKET_TYPE_EVENT = 0xA3;
+
+ public static final Logger logger = LoggerFactory.getLogger(A8kPacket.class);
+
+ byte[] raw;
+
+ public A8kPacket(byte[] cmd) {
+ raw = new byte[cmd.length];
+ System.arraycopy(cmd, 0, raw, 0, cmd.length);
+ }
+
+ public void setPacketIndex(int packetIndex) {
+ ByteArray.setU16bit(raw, INDEX_OFFSET, packetIndex);
+ int checkcode = computeCheckcode();
+ ByteArray.setU8(raw, raw.length - 1, checkcode);
+ }
+
+
+ public int getPacketIndex() {
+ return ByteArray.readU16bit(raw, INDEX_OFFSET);
+ }
+
+ public int getCmdId() {
+ return ByteArray.readU16bit(raw, CMDID_OFFSET);
+ }
+
+ public int getPacketType() {
+ return ByteArray.readU8bit(raw, PACKET_TYPE_OFFSET);
+ }
+
+ public int getModuleId() {
+ return ByteArray.readU8bit(raw, MODULE_ID_OFFSET);
+ }
+
+ public int getDataLen() {
+ return ByteArray.readU8bit(raw, DATA_LEN_OFFSET);
+ }
+
+ public byte[] getCmdContent() {
+ if (raw.length < PACKET_MIN_LEN) {
+ return new byte[0];
+ }
+ byte[] cmdcontent = new byte[getDataLen()];
+ System.arraycopy(raw, DATA_OFFSET, cmdcontent, 0, getDataLen());
+ return cmdcontent;
+ }
+
+ public int getContentI32(int index) {
+ return ByteArray.read32bit(raw, DATA_OFFSET + index * 4);
+ }
+
+ public int getCheckcode() {
+ return ByteArray.readU8bit(raw, raw.length - 1);
+ }
+
+ public int computeCheckcode() {
+ int checkcode = 0;
+ for (int i = 0; i < raw.length - 1; i++) {
+ checkcode += raw[i];
+ }
+ return checkcode & 0xFF;
+ }
+
+ public String toByteString() {
+ return ByteArray.toByteString(raw);
+ }
+
+ public String toString() {
+ int packetType = getPacketType();
+ String ret = "";
+
+ CmdId cmdId = CmdId.valueOf(getCmdId());
+ Assert.isTrue(cmdId != null, "cmdId is null");
+ if (packetType == PACKET_TYPE_CMD) {
+
+ if (cmdId.equals(CmdId.module_get_reg) || cmdId.equals(CmdId.module_set_reg)) {
+ RegIndex regIndex = RegIndex.valueOf(getContentI32(0));
+ if (regIndex != null) {
+ ret = String.format("[CMD ] index:[%d] %s %s %s(%d) %d", getPacketIndex(), cmdId,
+ MId.valueOf(getModuleId()), regIndex, regIndex.index, getContentI32(1));
+ } else {
+ ret = String.format("[CMD ] index:[%d] %s unkown_index(%d) %d", getPacketIndex(), cmdId,
+ getContentI32(0), getContentI32(1));
+ }
+ } else {
+ if (cmdId.getCmdAttachType() == CmdId.ATTACH_IS_INT32) {
+ ret = String.format("[CMD ] index:[%d] (%s %s(%d) :param:[%s])", getPacketIndex(), cmdId,
+ MId.valueOf(getModuleId()), getModuleId(), formatInt32ATTACH(getCmdContent()));
+ } else {
+ ret = String.format("[CMD ] index:[%d] (%s %s(%d) :param:[%s])", getPacketIndex(), cmdId,
+ MId.valueOf(getModuleId()), getModuleId(), ByteArray.toByteString(getCmdContent()));
+ }
+ }
+
+ } else if (packetType == PACKET_TYPE_ACK) {
+ if (cmdId.getReceiptAttachType() == CmdId.ATTACH_IS_INT32) {
+ ret = String.format("[ACK ] index:[%d] (%s :[%s])", getPacketIndex(), cmdId,
+ formatInt32ATTACH(getCmdContent()));
+ } else {
+ ret = String.format("[ACK ] index:[%d] (%s :[%s])", getPacketIndex(), cmdId,
+ ByteArray.toByteString(getCmdContent()));
+ }
+ } else if (packetType == PACKET_TYPE_ERROR_ACK) {
+ ret = String.format("[EACK ] index:[%d] (%s :[%s])", getPacketIndex(), cmdId, (getContentI32(0)));
+ } else if (packetType == PACKET_TYPE_EVENT) {
+ if (cmdId.getCmdAttachType() == CmdId.ATTACH_IS_INT32) {
+ ret = String.format("[EVENT] index:[%d] (%s %s(%d) :[%s])", getPacketIndex(), cmdId,
+ MId.valueOf(getModuleId()), getModuleId(), formatInt32ATTACH(getCmdContent()));
+ } else {
+ ret = String.format("[EVENT] index:[%d] (%s %s(%d) :[%s])", getPacketIndex(), cmdId,
+ MId.valueOf(getModuleId()), getModuleId(), ByteArray.toByteString(getCmdContent()));
+ }
+ } else {
+ ret = String.format("Unknown packet type: %d", packetType);
+ }
+ return ret;
+ }
+
+ private String formatInt32ATTACH(byte[] attach) {
+ StringBuilder ret = new StringBuilder();
+ for (int i = 0; i < attach.length; i += 4) {
+ if (i + 3 >= attach.length)
+ break;
+ if (i != 0)
+ ret.append(",");
+ ret.append(String.format("%d", ByteArray.read32bit(attach, i)));
+ }
+ return ret.toString();
+ }
+
+ public Boolean isSupportPacket() {
+ CmdId cmdid = CmdId.valueOf(getCmdId());
+ if (cmdid == null) {
+ return false;
+ }
+ if (CmdId.module_get_reg.equals(cmdid) || CmdId.module_set_reg.equals(cmdid)) {
+ if (getPacketType() == PACKET_TYPE_CMD) {
+ RegIndex regIndex = RegIndex.valueOf(getContentI32(0));
+ return regIndex != null;
+ }
+ }
+ return true;
+ }
+
+
+ static public A8kPacket createPacket(Integer moduleId, int packetType, Integer cmdId, Integer[] params) {
+ int bufferSize = A8kPacket.PACKET_MIN_LEN + 4 * params.length;
+ ByteBuffer buffer = ByteBuffer.allocate(bufferSize);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.put((byte) (packetType & 0xff)); // packetType
+ buffer.putShort((short) (cmdId & 0xffff)); // cmdid
+ buffer.put((byte) (moduleId & 0xFF)); // moduleId
+ buffer.put((byte) 0x4C); // index
+ buffer.put((byte) 0x00); // index
+ buffer.put((byte) (params.length * 4)); // datalen
+ for (int value : params) {
+ buffer.putInt(value);
+ }
+ // int8_t checksum;
+ int checksum = 0;
+ for (int i = 0; i < bufferSize - 1; i++) {
+ checksum += buffer.get(i);
+ }
+ buffer.put((byte) checksum);
+ return new A8kPacket(buffer.array());
+ }
+
+ public static void main(String[] args) {
+ var packet = createPacket(41, A8kPacket.PACKET_TYPE_CMD, CmdId.module_stop.index, new Integer[]{});
+ logger.info("{}", packet.toByteString());
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/CeramicPumpDriverSlaveId.java b/src/main/java/com/iflytop/handacid/hardware/type/CeramicPumpDriverSlaveId.java
new file mode 100644
index 0000000..608d063
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/CeramicPumpDriverSlaveId.java
@@ -0,0 +1,35 @@
+package com.iflytop.handacid.hardware.type;
+
+import com.iflytop.handacid.common.enums.Device;
+
+public enum CeramicPumpDriverSlaveId {
+
+ CERAMIC_PUMP1_ID(Device.CERAMIC_PUMP_1, 0x11),
+ CERAMIC_PUMP2_ID(Device.CERAMIC_PUMP_2, 0x12),
+ ;
+
+ private final Device cmdDevice;
+ private final Integer slaveId;
+
+ CeramicPumpDriverSlaveId(Device cmdDevice, Integer slaveId) {
+ this.cmdDevice = cmdDevice;
+ this.slaveId = slaveId;
+ }
+
+ public Device getCmdDevice() {
+ return cmdDevice;
+ }
+
+ public Integer getSlaveId() {
+ return slaveId;
+ }
+
+ static public CeramicPumpDriverSlaveId getByCmdDevice(Device cmdDevice) {
+ for (CeramicPumpDriverSlaveId value : CeramicPumpDriverSlaveId.values()) {
+ if (value.cmdDevice == cmdDevice) {
+ return value;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/CmdId.java b/src/main/java/com/iflytop/handacid/hardware/type/CmdId.java
new file mode 100644
index 0000000..af64f4e
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/CmdId.java
@@ -0,0 +1,198 @@
+package com.iflytop.handacid.hardware.type;
+
+
+public enum CmdId {
+ NotSet(0xFFFF, "NOACTION"),//
+
+ board_reset(0x0000, "复位板子"),//
+ event_bus_reg_change_report(0x0064, "寄存器修改事件"),//
+ //
+
+ module_ping(0x0101, "MODULE_PING"),
+ module_stop(0x0102, "MODULE_STOP"),
+
+ module_get_error(0x0110, "MODULE_GET_ERROR"),
+ module_get_detail_error(0x0111, "MODULE_GET_DETAIL_ERROR"),
+ module_clear_error(0x0112, "MODULE_CLEAR_ERROR"),
+
+ module_set_reg(0x0120, "MODULE_SET_REG"),
+ module_get_reg(0x0121, "MODULE_GET_REG"),
+ module_reset_reg(0x0123, "MODULE_RESET_REG"),
+
+ module_get_version(0x0130, "MODULE_GET_VERSION"),
+ module_get_type(0x0131, "MODULE_GET_TYPE"),
+ module_get_status(0x0132, "MODULE_GET_STATUS"),
+
+ // IO 板卡 1
+
+ // IO 板卡 2
+
+ // 电机通用控制
+ step_motor_enable(0x0201, "STEP_MOTOR_ENABLE"),//
+ step_motor_read_pos(0x020b, "STEP_MOTOR_READ_POS"),//
+ step_motor_read_enc_pos(0x020c, "STEP_MOTOR_READ_ENC_POS"),//
+ step_motor_easy_rotate(0x0211, "STEP_MOTOR_EASY_ROTATE"),//
+ step_motor_easy_move_by(0x0212, "STEP_MOTOR_EASY_MOVE_BY"),//
+ step_motor_easy_move_to(0x0213, "STEP_MOTOR_EASY_MOVE_TO"),//
+ step_motor_easy_move_to_zero(0x0214, "STEP_MOTOR_EASY_MOVE_TO_ZERO"),//
+ step_motor_easy_set_current_pos(0x0215, "STEP_MOTOR_EASY_SET_CURRENT_POS"),//
+ step_motor_easy_move_to_io(0x0216, "STEP_MOTOR_EASY_MOVE_TO_IO"),//
+ step_motor_stop(0x0228, "STEP_MOTOR_STOP"),//
+ step_motor_move_by(0x021d, "STEP_MOTOR_MOVE_BY"), // (dpos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ step_motor_move_to(0x021e, "STEP_MOTOR_MOVE_TO"), // (pos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ step_motor_move_to_zero_point_quick(0x021f, "STEP_MOTOR_MOVE_TO_ZERO_POINT_QUICK"), // (speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ step_motor_rotate(0x0220, "STEP_MOTOR_ROTATE"), //
+
+ step_motor_active_cfg(0x0229, "STEP_MOTOR_ACTIVE_CFG"),//
+ step_motor_read_io_state(0x022a, "STEP_MOTOR_READ_IO_STATE"),//
+ step_motor_easy_move_to_end_point(0x022c, "STEP_MOTOR_EASY_MOVE_TO_END_POINT"),//
+ step_motor_read_tmc5130_status(0x0232, "STEP_MOTOR_READ_TMC5130_STATUS"),//
+ step_motor_read_tmc5130_state(0x0233, "STEP_MOTOR_READ_TMC5130_STATE"),//
+ step_motor_read_io_index_in_stm32(0x0238, "STEP_MOTOR_READ_IO_INDEX_IN_STM32"),//
+ step_motor_set_subdevice_reg(0x0239, "STEP_MOTOR_SET_SUBDEVICE_REG"),//
+ step_motor_get_subdevice_reg(0x023a, "STEP_MOTOR_GET_SUBDEVICE_REG"),//
+ step_motor_easy_reciprocating_motion(0x022d, "STEP_MOTOR_EASY_RECIPROCATING_MOTION"),//
+ step_motor_easy_move_to_zero_point_quick(0x022e, "STEP_MOTOR_EASY_MOVE_TO_ZERO_POINT_QUICK"),
+ step_motor_set_clamp(0x022f, "STEP_MOTOR_SET_CLAMP"),//
+
+
+ mini_servo_enable(0x3601, "MINI_SERVO_ENABLE"),
+ mini_servo_read_pos(0x3602, "MINI_SERVO_READ_POS"),
+ mini_servo_active_cfg(0x3603, "MINI_SERVO_ACTIVE_CFG"),
+ mini_servo_stop(0x3604, "MINI_SERVO_STOP"),
+ mini_servo_set_mid_point(0x3607, "MINI_SERVO_SET_MID_POINT"),
+ mini_servo_read_io_state(0x3608, "MINI_SERVO_READ_IO_STATE"),
+ mini_servo_move_to(0x3609, "MINI_SERVO_MOVE_TO"),
+ mini_servo_rotate(0x360a, "MINI_SERVO_ROTATE"),
+ mini_servo_rotate_with_torque(0x360b, "MINI_SERVO_ROTATE_WITH_TORQUE"),
+ mini_servo_set_cur_pos(0x360c, "MINI_SERVO_SET_CUR_POS"),
+
+ leisai_servo_enable(0x3701, "LEISAI_SERVO_ENABLE"),//
+ leisai_servo_read_pos(0x3702, "LEISAI_SERVO_READ_POS"),//
+ leisai_servo_read_io_state(0x3703, "LEISAI_SERVO_READ_IO_STATE"),//
+ leisai_servo_move_by(0x3704, "LEISAI_SERVO_MOVE_BY"), // (dpos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ leisai_servo_move_to(0x3705, "LEISAI_SERVO_MOVE_TO"), // (pos,speedlevel)->null speedlevel=0,1,2,3(default,low,mid,high)
+ leisai_servo_rotate(0x3706, "LEISAI_SERVO_ROTATE"), // (direction,speedlevel)-> null
+ leisai_servo_move_to_zero(0x3707, "LEISAI_SERVO_MOVE_TO_ZERO"), // (null)-> null
+
+ liquid_distribution_arm_enable(0x3801, "LIQUID_DISTRIBUTION_ARM_ENABLE"),//
+ liquid_distribution_arm_move_to(0x3802, "LIQUID_DISTRIBUTION_ARM_MOVE_TO"), // (pos)->null pos=(-1,0,1...16) -1代表未知未知,0代表待机位置,1...16分别对应16加酸孔位
+ liquid_distribution_arm_read_pos(0x3803, "LIQUID_DISTRIBUTION_ARM_READ_POS"), // (null)->pos pos=(-1,0,1...16) -1代表未知未知,0代表待机位置,1...16分别对应16加酸孔位
+ liquid_distribution_arm_set_cur_pos_as_mid_pos(0x3804, "LIQUID_DISTRIBUTION_ARM_SET_CUR_POS_AS_MID_POS"), // (null)->null 校准舵机
+ liquid_distribution_arm_set_cur_pos_as_preset_pos(0x3805, "LIQUID_DISTRIBUTION_ARM_SET_CUR_POS_AS_PRESET_POS"), // 设置当前位置作为预设位置(posindex)->null pos=(0,1...16) 0代表未知未知,1...16分别对应16加酸孔位
+
+ read_in_io(0x4001, "READ_IN_IO"),
+ write_out_io(0x4002, "WRITE_OUT_IO"),
+ read_muti_in_io(0x4003, "READ_MUTI_IN_IO"),
+ read_in_io_index_in_stm32(0x4004, "READ_IN_IO_INDEX_IN_STM32"),
+ read_out_io_index_in_stm32(0x4005, "READ_OUT_IO_INDEX_IN_STM32"),
+ read_out_io(0x4006, "READ_OUT_IO"),
+
+ tricolor_light_on(0x5101, "TRICOLOR_LIGHT_ON"),
+ tricolor_light_off(0x5102, "TRICOLOR_LIGHT_OFF"),
+
+ pwm_light_on(0x5201, "PWM_LIGHT_ON"),
+ pwm_light_off(0x5202, "PWM_LIGHT_OFF"),
+
+ adc_enable_log(0x05301, "ADC_ENABLE_LOG"),
+ adc_read_adc(0x05302, "ADC_READ_ADC"),
+ adc_read_multi_adc(0x05303, "ADC_READ_MULTI_ADC"),
+
+ // Chime Buzzer
+ chime_buzzer_turn_on_info(0x5601, "CHIME_BUZZER_TURN_ON_INFO"),
+ chime_buzzer_turn_on_alarm(0x5602, "CHIME_BUZZER_TURN_ON_ALARM"),
+ chime_buzzer_turn_off(0x5603, "CHIME_BUZZER_TURN_OFF"),
+
+ // Liquid Valve
+ liquid_valve_read_valve_pos(0x05701, "LIQUID_VALVE_READ_VALVE_POS"),
+ liquid_valve_read_motor_state(0x05702, "LIQUID_VALVE_READ_MOTOR_STATE"),
+ liquid_valve_switch_valve(0x5703, "LIQUID_VALVE_SWITCH_VALVE"),
+ liquid_valve_stop(0x5704, "LIQUID_VALVE_STOP"),
+ liquid_valve_reset(0x5705, "LIQUID_VALVE_RESET"),
+ liquid_valve_home_reset(0x5706, "LIQUID_VALVE_HOME_RESET"),
+ liquid_valve_force_stop(0x5707, "LIQUID_VALVE_FORCE_STOP"),
+
+ tec103_heater_power_on(0x5801, "TEC103_HEATER_POWER_ON"),
+ tec103_heater_enable(0x5802, "TEC103_HEATER_ENABLE"),
+ tec103_heater_set_target_temperature(0x5803, "TEC103_HEATER_SET_TARGET_TEMPERATURE"),
+ tec103_heater_read_temperature(0x5804, "TEC103_HEATER_READ_TEMPERATURE"),
+ tec103_heater_factory_reset(0x5805, "TEC103_HEATER_FACTORY_RESET"),
+ ;
+
+ public final static int ATTACH_IS_BYTES = 1;
+ public final static int ATTACH_IS_INT32 = 2;
+ public final int index;
+ public final String chName;
+
+ CmdId(int index, String chname) {
+ this.index = index;
+ this.chName = chname;
+ }
+
+ public int toInt() {
+ return index;
+ }
+
+ public static CmdId valueOf(int val) {
+ CmdId[] values = CmdId.values();
+ for (CmdId e : values) {
+ if (e.toInt() == val)
+ return e;
+ }
+ return null;
+ }
+
+ public static String toString(int val) {
+ CmdId[] values = CmdId.values();
+ for (CmdId e : values) {
+ if (e.toInt() == val) {
+ return e.toString();
+ }
+ }
+ return "unkown(" + val + ")";
+ }
+
+ public String getChname() {
+ return chName;
+ }
+
+ public Boolean eq(Integer index) {
+ return this.index == index;
+ }
+
+
+ public int getCmdAttachType() {
+ return switch (this) {
+ default -> ATTACH_IS_INT32;
+ };
+ }
+
+ public int getReceiptAttachType() {
+ return switch (this) {
+ default -> ATTACH_IS_INT32;
+ };
+ }
+
+ public boolean isTrace() {
+ return true;
+ }
+
+ public boolean isActionCmd() {
+
+ return switch (this) {
+ case module_get_status,
+ module_get_error,
+ module_get_reg,
+ module_set_reg,
+ step_motor_read_io_state,
+ read_in_io,
+ read_muti_in_io,
+ read_in_io_index_in_stm32,
+ read_out_io_index_in_stm32,
+ adc_read_adc,
+ adc_read_multi_adc -> false;
+ default -> true;
+ };
+ }
+}
+
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/IO/InputIOMId.java b/src/main/java/com/iflytop/handacid/hardware/type/IO/InputIOMId.java
new file mode 100644
index 0000000..be40b15
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/IO/InputIOMId.java
@@ -0,0 +1,34 @@
+package com.iflytop.handacid.hardware.type.IO;
+
+import com.iflytop.handacid.hardware.type.MId;
+
+public enum InputIOMId {
+ HEAT_MODULE01_EXIST("HEAT_MODULE01_EXIST [ 加热模1传感器存在 ]", MId.IO1_IO, 0,false),
+ HEAT_MODULE02_EXIST("HEAT_MODULE02_EXIST [ 加热模2传感器存在 ]", MId.IO1_IO, 1,false),
+ HEAT_MODULE03_EXIST("HEAT_MODULE03_EXIST [ 加热模3传感器存在 ]", MId.IO1_IO, 2,false),
+ HEAT_MODULE04_EXIST("HEAT_MODULE04_EXIST [ 加热模4传感器存在 ]", MId.IO1_IO, 3,false),
+ HEAT_MODULE05_EXIST("HEAT_MODULE05_EXIST [ 加热模5传感器存在 ]", MId.IO1_IO, 4,false),
+ HEAT_MODULE06_EXIST("HEAT_MODULE06_EXIST [ 加热模6传感器存在 ]", MId.IO1_IO, 5,false),
+ CAP01_EXIST("CAP01_EXIST [ 拍子存放位拍子1到位 ]", MId.IO1_IO, 6,false),
+ CAP02_EXIST("CAP02_EXIST [ 拍子存放位拍子2到位 ]", MId.IO1_IO, 7,false),
+ CAP03_EXIST("CAP03_EXIST [ 拍子存放位拍子3到位 ]", MId.IO1_IO, 8,false),
+ CAP04_EXIST("CAP04_EXIST [ 拍子存放位拍子4到位 ]", MId.IO1_IO, 9,false),
+ CAP05_EXIST("CAP05_EXIST [ 拍子存放位拍子5到位 ]", MId.IO1_IO, 10,false),
+ CAP06_EXIST("CAP06_EXIST [ 拍子存放位拍子6到位 ]", MId.IO1_IO, 11,false),
+
+ E_STOP("E_STOP [ 急停 ]", MId.IO1_IO, 18,false),
+ ;
+
+ final public String description;
+ final public MId mid;
+ final public int ioIndex;
+ final public boolean mirror;
+
+ InputIOMId(String description, MId mid, int ioIndex, boolean mirror) {
+ this.description = description;
+ this.mid = mid;
+ this.ioIndex = ioIndex;
+ this.mirror = mirror;
+
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/IO/OutputIOMId.java b/src/main/java/com/iflytop/handacid/hardware/type/IO/OutputIOMId.java
new file mode 100644
index 0000000..10d8500
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/IO/OutputIOMId.java
@@ -0,0 +1,28 @@
+package com.iflytop.handacid.hardware.type.IO;
+
+import com.iflytop.handacid.hardware.type.MId;
+
+
+public enum OutputIOMId {
+ DO_FAN1("FAN1", MId.IO1_IO, 0, false),
+ DO_FAN2("FAN2", MId.IO1_IO, 1, false),
+ DO_FAN3("FAN3", MId.IO1_IO, 2, false),
+ DO_FAN4("FAN4", MId.IO1_IO, 3, false),
+ SYSTEM_POWER("SYSTEM_POWER", MId.IO1_IO, 4, false),
+ ;
+
+ final public String description;
+ final public MId mid;
+ final public int ioIndex;
+ final public boolean mirror;
+
+ OutputIOMId(String description, MId mid, int ioIndex, boolean mirror) {
+ this.description = description;
+ this.mid = mid;
+ this.ioIndex = ioIndex;
+ this.mirror = mirror;
+
+ }
+
+}
+
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/MId.java b/src/main/java/com/iflytop/handacid/hardware/type/MId.java
new file mode 100644
index 0000000..f460cd4
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/MId.java
@@ -0,0 +1,87 @@
+package com.iflytop.handacid.hardware.type;
+
+/**
+ * MID 现版本 取值范围 0-255
+ */
+public enum MId {
+ NotSet(0, "未设置"),
+ IO1Board(5, "台面 IO 板模块"),
+ IO1_IO(6, "台面 IO 板模块"),
+ IO1_TriColorLight(7, "三色灯"),
+ IO1_ADC(9, "台面 ADC 板模块"),
+
+ RobotArmBoard(40, "机械臂控制板"),
+ DualRobot1M(41, "机械臂X轴电机"),
+ DualRobot2M(42, "机械臂Y轴电机"),
+ RobotArmZM(43, "机械臂Z轴电机"),
+ RobotArmClawSV(44, "机械臂夹爪伺服"),
+
+ BrushlessPumpBoard(50, "无刷泵控制板"),
+ BrushlessPump1M(51, "无刷泵1号电机"),
+ BrushlessPump2M(52, "无刷泵2号电机"),
+ BrushlessPump3M(53, "无刷泵3号电机"),
+ BrushlessPump4M(54, "无刷泵4号电机"),
+ BrushlessPump5M(55, "无刷泵5号电机"),
+ BrushlessPump6M(56, "无刷泵6号电机"),
+ BrushlessPump7M(57, "无刷泵7号电机"),
+ BrushlessPump8M(58, "无刷泵8号电机"),
+ BrushlessPump9M(59, "无刷泵9号电机"),
+ BrushlessPump10M(60, "无刷泵10号电机"),
+
+
+ StepPumpBoard(65, "步进泵控制板"),
+ StepPump1M(66, "步进泵1号"),
+ StepPump2M(67, "步进泵2号"),
+ StepPump3M(68, "步进泵3号"),
+
+
+ TitratorBoard(75, "滴定仪控制板"),
+ TitratorStir1M(76, "滴定仪搅拌1泵"),
+ TitratorStir2M(77, "滴定仪搅拌2泵"),
+ Titrator1M(78, "滴定仪1泵"),
+ Titrator2M(79, "滴定仪2泵"),
+ HeaterRod1(80, "加热棒1"),
+ HeaterRod2(81, "加热棒2"),
+ ;
+
+ final public String description;
+ final public int index;
+
+ MId(int index, String description) {
+ this.description = description;
+ this.index = index;
+ }
+
+ public int toInt() {
+ return index;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+
+ public static MId valueOf(Integer val) {
+ return valueOf(val.intValue());
+ }
+
+ public static MId valueOf(int val) {
+ MId[] values = MId.values();
+ for (MId e : values) {
+ if (e.toInt() == val)
+ return e;
+ }
+ return null;
+ }
+
+ public static String toString(int val) {
+ MId[] values = MId.values();
+ for (MId e : values) {
+ if (e.toInt() == val) {
+ return e.toString();
+ }
+ }
+ return "unknown(" + val + ")";
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/ModuleStatus.java b/src/main/java/com/iflytop/handacid/hardware/type/ModuleStatus.java
new file mode 100644
index 0000000..1575d2c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/ModuleStatus.java
@@ -0,0 +1,25 @@
+package com.iflytop.handacid.hardware.type;
+
+import org.springframework.util.Assert;
+
+public enum ModuleStatus {
+ IDLE(0), //
+ BUSY(1), //
+ ERROR(2),//
+ ;
+
+ final public int index;
+
+ ModuleStatus(int index) {
+ this.index = index;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public static ModuleStatus valueOf(int value) { // 手写的从int到enum的转换函数
+ Assert.isTrue((value >= 0 && value <= 2), String.format("(value=%s value >= 0 && value <= 2)", value));
+ return values()[value];
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/ModuleType.java b/src/main/java/com/iflytop/handacid/hardware/type/ModuleType.java
new file mode 100644
index 0000000..b65be80
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/ModuleType.java
@@ -0,0 +1,33 @@
+package com.iflytop.handacid.hardware.type;
+
+public enum ModuleType {
+ Board(1), // 板子
+
+ TMCStepMotor(2), // 步进电机
+ MiniServo(3), // 舵机
+ IO(4), // IO扩展
+
+ TriColorLightCtrl(101), //
+ PwmLightCtrl(102), //
+ ADC(103), // ADC扩展
+ LeiSaiServo(104), // 雷赛伺服
+ LiquidDistributionArm(105), // 液体分配机械臂控制
+
+ UNKNOWN(999); // 未知类型
+ public final int code;
+
+ ModuleType(int val) {
+ this.code = val;
+ }
+
+ static public ModuleType of(int val) {
+ for (ModuleType type : ModuleType.values()) {
+ if (type.code == val) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/RegIndex.java b/src/main/java/com/iflytop/handacid/hardware/type/RegIndex.java
new file mode 100644
index 0000000..b4e6675
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/RegIndex.java
@@ -0,0 +1,233 @@
+package com.iflytop.handacid.hardware.type;
+
+/**
+ * @brief 寄存器索引
+ */
+public enum RegIndex {
+
+ /***********************************************************************************************************************
+ * 步进电机控制器 *
+ ***********************************************************************************************************************/
+ /***********************************************************************************************************************
+ * step_motor start *
+ ***********************************************************************************************************************/
+
+ kreg_step_motor_pos(10101), // 机器人x坐标
+ kreg_step_motor_is_enable(10102), // 是否使能
+ kreg_step_motor_dpos(10103), // 执行完上一条指令后的相对位移
+ kreg_step_motor_has_move_zero(10104), // 是否回零
+ // kreg_step_motor_shift(10150), // x偏移
+ kreg_step_motor_shaft(10151), // x轴是否反转
+ kreg_step_motor_one_circle_pulse(10152), // x轴一圈脉冲数
+ kreg_step_motor_one_circle_pulse_denominator(10153), // 设置一圈脉冲数的分母
+ kreg_step_motor_default_velocity(10154), // 默认速度
+ kreg_step_motor_low_velocity(10155), // 低速
+ kreg_step_motor_mid_velocity(10156), // 中速
+ kreg_step_motor_high_velocity(10157), // 高速
+ kreg_step_motor_ihold(10158), // 步进电机电流配置
+ kreg_step_motor_irun(10159), // 步进电机电流配置
+ kreg_step_motor_iholddelay(10160), // 步进电机电流配置
+ kreg_step_motor_iglobalscaler(10161), // 步进电机电流配置
+ kreg_step_motor_mres(10162), // 电机细分
+ kreg_step_motor_run_to_zero_speed(10173), // 回零速度
+ kreg_step_motor_look_zero_edge_speed(10175), // 找零边缘速度
+ kreg_step_motor_max_d(10178), // 最大限制距离
+ kreg_step_motor_min_d(10179), // 最小限制距离
+ kreg_step_motor_in_debug_mode(10180), // 驱动器处于调试模式
+ kreg_step_motor_vstart(10181), // a1起作用的速度
+ kreg_step_motor_a1(10182), //
+ kreg_step_motor_amax(10183), //
+ kreg_step_motor_v1(10184), //
+ kreg_step_motor_dmax(10185), //
+ kreg_step_motor_d1(10186), //
+ kreg_step_motor_vstop(10187), //
+ kreg_step_motor_tzerowait(10188), //
+ kreg_step_motor_enc_resolution(10189), // 编码器分辨率 1000,1024,4000,4096,16384
+ kreg_step_motor_enable_enc(10190), //
+ kreg_step_motor_dzero_pos(10191), // 驱动器处于调试模式
+ kret_step_motor_pos_devi_tolerance(10192), // 位置偏差容忍度
+ kret_step_motor_io_trigger_append_distance(10193), // 移动到io时,附加的距离
+
+ /***********************************************************************************************************************
+ * step_motor end *
+ ***********************************************************************************************************************/
+
+
+ /***********************************************************************************************************************
+ * servo *
+ ***********************************************************************************************************************/
+
+ kreg_mini_servo_pos(10201), // 位置
+ kreg_mini_servo_limit_velocity(10251), // 限制速度
+ kreg_mini_servo_limit_torque(10252), // 限制扭矩
+ kreg_mini_servo_protective_torque(10253), // 保护扭矩
+ kreg_mini_servo_is_move(10254), // 是否在运动
+ kreg_mini_servo_status(10255), // 舵机状态
+ kreg_mini_servo_voltage(10256), // 电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V
+ kreg_mini_servo_current(10257), // 电流 反馈当前工作电流值,单位为ma
+ kreg_mini_servo_temperature(10258), // 温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度
+ kreg_mini_servo_loadvalue(10259), // 负载值 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000
+ kreg_mini_servo_target_pos_tolerance(10260), // 目标位置容忍度
+
+
+ kreg_mini_servo_firmware_main_version(10500), // 固件主版本号
+ kreg_mini_servo_firmware_sub_version(10501), // 固件次版本号
+ kreg_mini_servo_servo_main_version(10503), // 舵机主版本号
+ kreg_mini_servo_servo_sub_version(10504), // 舵机次版本号
+ kreg_mini_servo_servo_min_angle(10509), // 最小角度限制
+ kreg_mini_servo_servo_max_angle(10511), // 最大角度限制
+ kreg_mini_servo_servo_max_temp(10513), // 最高温度上限
+ kreg_mini_servo_servo_max_voltage(10514), // 最高输入电压
+ kreg_mini_servo_servo_min_voltage(10515), // 最低输入电压
+ kreg_mini_servo_servo_max_torque(10516), // 最大扭矩
+ kreg_mini_servo_servo_unload_condition(10519), // 卸载条件
+ kreg_mini_servo_servo_p(10521), // P 比例系
+ kreg_mini_servo_servo_d(10522), // D 微分系
+ kreg_mini_servo_servo_i(10523), // I
+ kreg_mini_servo_servo_min_start(10524), // 最小启动
+ kreg_mini_servo_servo_cw_dead_zone(10526), // 顺时针不灵敏区
+ kreg_mini_servo_servo_ccw_dead_zone(10527), // 逆时针不灵敏
+ kreg_mini_servo_servo_protect_current(10528), // 保护电流
+ kreg_mini_servo_servo_protect_torque(10534), // 保护扭矩 0->100 ,触发后,需要写入与组转方向相反的位置指令,进行解除
+ kreg_mini_servo_servo_protect_time(10535), // 保护时间
+ kreg_mini_servo_servo_overload_torque(10536), // 过载扭矩
+ kreg_mini_servo_servo_speed_p(10537), // 速度闭环P比例参数
+ kreg_mini_servo_servo_overload_time(10538), // 过流保护时间
+ kreg_mini_servo_servo_speed_i(10539), // 速度闭环I积分参数
+ kreg_mini_servo_servo_torque_switch(10540), // 扭矩开关
+ kreg_mini_servo_servo_acc(10541), // 加速度
+ kreg_mini_servo_servo_target_pos(10542), // 目标位置
+ kreg_mini_servo_servo_run_time(10544), // 运行时间
+ kreg_mini_servo_servo_run_speed(10546), // 运行速度
+ kreg_mini_servo_servo_torque_limit(10548), // 转矩限制
+ kreg_mini_servo_servo_lock_flag(10555), // 锁标志
+ kreg_mini_servo_servo_current_pos(10556), // 当前位置
+ kreg_mini_servo_servo_current_speed(10558), // 当前速度
+ kreg_mini_servo_servo_current_load(10560), // 当前负载 bit10为方向位 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000
+ kreg_mini_servo_servo_current_voltage(10562), // 当前电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V
+ kreg_mini_servo_servo_current_temp(10563), // 当前温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度
+ kreg_mini_servo_servo_status(10565), // 舵机状态
+ kreg_mini_servo_servo_move_flag(10566), // 移动标志
+ kreg_mini_servo_servo_current_current(10569), // 当前电流 反馈当前工作电流值,单位为6.26.5mA,最大可反馈电流为500*6.5mA=3250mA
+
+
+ kreg_liquid_distribution_arm_enable(10600), // 电机使能
+ kreg_liquid_distribution_arm_pos0_d0(10610), // 位置0 舵机0位置
+ kreg_liquid_distribution_arm_pos0_d1(10611), // 位置0 舵机1位置
+ kreg_liquid_distribution_arm_pos1_d0(10612), // 位置1 舵机0位置
+ kreg_liquid_distribution_arm_pos1_d1(10613), // 位置1 舵机1位置
+ kreg_liquid_distribution_arm_pos2_d0(10614), // 位置2 舵机0位置
+ kreg_liquid_distribution_arm_pos2_d1(10615), // 位置2 舵机1位置
+ kreg_liquid_distribution_arm_pos3_d0(10616), // 位置3 舵机0位置
+ kreg_liquid_distribution_arm_pos3_d1(10617), // 位置3 舵机1位置
+ kreg_liquid_distribution_arm_pos4_d0(10618), // 位置4 舵机0位置
+ kreg_liquid_distribution_arm_pos4_d1(10619), // 位置4 舵机1位置
+ kreg_liquid_distribution_arm_pos5_d0(10620), // 位置5 舵机0位置
+ kreg_liquid_distribution_arm_pos5_d1(10621), // 位置5 舵机1位置
+ kreg_liquid_distribution_arm_pos6_d0(10622), // 位置6 舵机0位置
+ kreg_liquid_distribution_arm_pos6_d1(10623), // 位置6 舵机1位置
+ kreg_liquid_distribution_arm_pos7_d0(10624), // 位置7 舵机0位置
+ kreg_liquid_distribution_arm_pos7_d1(10625), // 位置7 舵机1位置
+ kreg_liquid_distribution_arm_pos8_d0(10626), // 位置8 舵机0位置
+ kreg_liquid_distribution_arm_pos8_d1(10627), // 位置8 舵机1位置
+ kreg_liquid_distribution_arm_pos9_d0(10628), // 位置9 舵机0位置
+ kreg_liquid_distribution_arm_pos9_d1(10629), // 位置9 舵机1位置
+ kreg_liquid_distribution_arm_pos10_d0(10630), // 位置10 舵机0位置
+ kreg_liquid_distribution_arm_pos10_d1(10631), // 位置10 舵机1位置
+ kreg_liquid_distribution_arm_pos11_d0(10632), // 位置11 舵机0位置
+ kreg_liquid_distribution_arm_pos11_d1(10633), // 位置11 舵机1位置
+ kreg_liquid_distribution_arm_pos12_d0(10634), // 位置12 舵机0位置
+ kreg_liquid_distribution_arm_pos12_d1(10635), // 位置12 舵机1位置
+ kreg_liquid_distribution_arm_pos13_d0(10636), // 位置13 舵机0位置
+ kreg_liquid_distribution_arm_pos13_d1(10637), // 位置13 舵机1位置
+ kreg_liquid_distribution_arm_pos14_d0(10638), // 位置14 舵机0位置
+ kreg_liquid_distribution_arm_pos14_d1(10639), // 位置14 舵机1位置
+ kreg_liquid_distribution_arm_pos15_d0(10640), // 位置15 舵机0位置
+ kreg_liquid_distribution_arm_pos15_d1(10641), // 位置15 舵机1位置
+ kreg_liquid_distribution_arm_pos16_d0(10642), // 位置16 舵机0位置
+ kreg_liquid_distribution_arm_pos16_d1(10643), // 位置16 舵机1位置
+
+ kreg_leisai_servo_is_enable(10702), // 是否使能
+ kreg_leisai_servo_has_move_zero(10703), // 是否回零
+ kreg_leisai_servo_default_velocity(10704), // 默认速度
+ kreg_leisai_servo_low_velocity(10705), // 低速
+ kreg_leisai_servo_mid_velocity(10706), // 中速
+ kreg_leisai_servo_high_velocity(10707), // 高速
+ kreg_leisai_servo_acc_time(10708), // 加速度时间
+ kreg_leisai_servo_dcc_time(10709), // 减速度时间
+ kreg_tec103_heater1_target_temp(10800), // 目标温度
+ kreg_tec103_heater1_real_temp(10801), // 读取温度
+ kreg_tec103_heater1_sensor_r(10802), // 查询传感器电阻值
+ kreg_tec103_heater1_polynomial(10803), // 温度求解模型选择
+ kreg_tec103_heater1_ntc_b(10804), // NTC B值
+ kreg_tec103_heater1_ntc_r0(10805), // NTC R0值
+ kreg_tec103_heater1_output_enable(10807), // 使能
+ kreg_tec103_heater1_output_max_duty(10808), // 最大输出占空比
+ kreg_tec103_heater1_output_mode(10809), // 输出模式
+ kreg_tec103_heater1_output_pol(10810), // 输出极性
+ kreg_tec103_heater1_output_percent(10811), // 输出电压百分比
+ kreg_tec103_heater1_pwm_freq(10812), // PWM引脚输出频率
+ kreg_tec103_heater1_over_temp_protect_mode(10813), // 传感器过温保护模式
+ kreg_tec103_heater1_p(10814), // P参数
+ kreg_tec103_heater1_i(10815), // I参数
+ kreg_tec103_heater1_d(10816), // D参数
+ kreg_tec103_heater1_pid_self_tune(10817), // PID自整定
+ kreg_tec103_heater2_target_temp(10830), // 目标温度
+ kreg_tec103_heater2_real_temp(10831), // 读取温度
+ kreg_tec103_heater2_sensor_r(10832), // 查询传感器电阻值
+ kreg_tec103_heater2_polynomial(10833), // 温度求解模型选择
+ kreg_tec103_heater2_ntc_b(10834), // NTC B值
+ kreg_tec103_heater2_ntc_r0(10835), // NTC R0值
+ kreg_tec103_heater2_output_enable(10837), // 使能
+ kreg_tec103_heater2_output_max_duty(10838), // 最大输出占空比
+ kreg_tec103_heater2_output_mode(10839), // 输出模式
+ kreg_tec103_heater2_output_pol(10840), // 输出极性
+ kreg_tec103_heater2_output_percent(10841), // 输出电压百分比
+ kreg_tec103_heater2_pwm_freq(10842), // PWM引脚输出频率
+ kreg_tec103_heater2_over_temp_protect_mode(10843), // 传感器过温保护模式
+ kreg_tec103_heater2_p(10844), // P参数
+ kreg_tec103_heater2_i(10845), // I参数
+ kreg_tec103_heater2_d(10846), // D参数
+ kreg_tec103_heater2_pid_self_tune(10847), // PID自整定
+ kreg_tec103_heater_version(10860), // 版本号
+ kreg_tec103_heater_address(10861), // 从机地址
+ kreg_tec103_heater_baud_rate(10862), // 波特率
+ kreg_tec103_heater_e_code(10863), // 错误码
+
+ ;
+
+ public final int index;
+ public final Boolean trace;
+
+ RegIndex(int regIndex) {
+ this.index = regIndex;
+ this.trace = true;
+ }
+
+ RegIndex(int regIndex, Boolean trace) {
+ this.index = regIndex;
+ this.trace = trace;
+ }
+
+ public static RegIndex valueOf(int val) {
+ RegIndex[] values = RegIndex.values();
+ for (RegIndex regindex : values) {
+ if (regindex.index == val) {
+ return regindex;
+ }
+ }
+ return null;
+ }
+
+ public static RegIndex fromString(String regStr)
+ {
+ RegIndex[] values = RegIndex.values();
+ for (RegIndex regindex : values) {
+ if (regindex.toString().equalsIgnoreCase(regStr)) {
+ return regindex;
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiRegIndex.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiRegIndex.java
new file mode 100644
index 0000000..de8e31f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiRegIndex.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+import com.iflytop.handacid.hardware.type.RegIndex;
+
+public enum LeisaiRegIndex {
+ kreg_leisai_servo_is_enable(RegIndex.kreg_leisai_servo_is_enable),
+ kreg_leisai_servo_has_move_zero(RegIndex.kreg_leisai_servo_has_move_zero),
+ kreg_leisai_servo_default_velocity(RegIndex.kreg_leisai_servo_default_velocity),
+ kreg_leisai_servo_low_velocity(RegIndex.kreg_leisai_servo_low_velocity),
+ kreg_leisai_servo_mid_velocity(RegIndex.kreg_leisai_servo_mid_velocity),
+ kreg_leisai_servo_high_velocity(RegIndex.kreg_leisai_servo_high_velocity),
+ kreg_leisai_servo_acc_time(RegIndex.kreg_leisai_servo_acc_time), // 加速度时间
+ kreg_leisai_servo_dcc_time(RegIndex.kreg_leisai_servo_dcc_time), // 减速度时间
+ ;
+
+ public final RegIndex regIndex;
+
+ LeisaiRegIndex(RegIndex regIndex) {
+ this.regIndex = regIndex;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiServoMId.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiServoMId.java
new file mode 100644
index 0000000..8d2abe2
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiServoMId.java
@@ -0,0 +1,15 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+import com.iflytop.handacid.hardware.type.MId;
+import org.springframework.util.Assert;
+
+public enum LeisaiServoMId {
+ ;
+
+ final public MId mid;
+
+ LeisaiServoMId(MId mid) {
+ Assert.isTrue(this.name().equals(mid.name()), "LeisaiServoMId Init fail");
+ this.mid = mid;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiServoSpeedLevel.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiServoSpeedLevel.java
new file mode 100644
index 0000000..4cbe4eb
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LeisaiServoSpeedLevel.java
@@ -0,0 +1,9 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+public enum LeisaiServoSpeedLevel {
+ DEFAULT,
+ LOW,
+ MID,
+ HIGH,
+ ;
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/LiquidArmMId.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LiquidArmMId.java
new file mode 100644
index 0000000..9764d17
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LiquidArmMId.java
@@ -0,0 +1,15 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+import com.iflytop.handacid.hardware.type.MId;
+import org.springframework.util.Assert;
+
+public enum LiquidArmMId {
+ ;
+
+ final public MId mid;
+
+ LiquidArmMId(MId mid) {
+ Assert.isTrue(this.name().equals(mid.name()), "LiquidDistributionArm Init fail");
+ this.mid = mid;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/LiquidArmRegIndex.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LiquidArmRegIndex.java
new file mode 100644
index 0000000..c24a56a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/LiquidArmRegIndex.java
@@ -0,0 +1,47 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+import com.iflytop.handacid.hardware.type.RegIndex;
+
+public enum LiquidArmRegIndex {
+ kreg_liquid_distribution_arm_enable(RegIndex.kreg_liquid_distribution_arm_enable), // 电机使能
+ kreg_liquid_distribution_arm_pos0_d0(RegIndex.kreg_liquid_distribution_arm_pos0_d0), // 位置0 舵机0位置
+ kreg_liquid_distribution_arm_pos0_d1(RegIndex.kreg_liquid_distribution_arm_pos0_d1), // 位置0 舵机1位置
+ kreg_liquid_distribution_arm_pos1_d0(RegIndex.kreg_liquid_distribution_arm_pos1_d0), // 位置1 舵机0位置
+ kreg_liquid_distribution_arm_pos1_d1(RegIndex.kreg_liquid_distribution_arm_pos1_d1), // 位置1 舵机1位置
+ kreg_liquid_distribution_arm_pos2_d0(RegIndex.kreg_liquid_distribution_arm_pos2_d0), // 位置2 舵机0位置
+ kreg_liquid_distribution_arm_pos2_d1(RegIndex.kreg_liquid_distribution_arm_pos2_d1), // 位置2 舵机1位置
+ kreg_liquid_distribution_arm_pos3_d0(RegIndex.kreg_liquid_distribution_arm_pos3_d0), // 位置3 舵机0位置
+ kreg_liquid_distribution_arm_pos3_d1(RegIndex.kreg_liquid_distribution_arm_pos3_d1), // 位置3 舵机1位置
+ kreg_liquid_distribution_arm_pos4_d0(RegIndex.kreg_liquid_distribution_arm_pos4_d0), // 位置4 舵机0位置
+ kreg_liquid_distribution_arm_pos4_d1(RegIndex.kreg_liquid_distribution_arm_pos4_d1), // 位置4 舵机1位置
+ kreg_liquid_distribution_arm_pos5_d0(RegIndex.kreg_liquid_distribution_arm_pos5_d0), // 位置5 舵机0位置
+ kreg_liquid_distribution_arm_pos5_d1(RegIndex.kreg_liquid_distribution_arm_pos5_d1), // 位置5 舵机1位置
+ kreg_liquid_distribution_arm_pos6_d0(RegIndex.kreg_liquid_distribution_arm_pos6_d0), // 位置6 舵机0位置
+ kreg_liquid_distribution_arm_pos6_d1(RegIndex.kreg_liquid_distribution_arm_pos6_d1), // 位置6 舵机1位置
+ kreg_liquid_distribution_arm_pos7_d0(RegIndex.kreg_liquid_distribution_arm_pos7_d0), // 位置7 舵机0位置
+ kreg_liquid_distribution_arm_pos7_d1(RegIndex.kreg_liquid_distribution_arm_pos7_d1), // 位置7 舵机1位置
+ kreg_liquid_distribution_arm_pos8_d0(RegIndex.kreg_liquid_distribution_arm_pos8_d0), // 位置8 舵机0位置
+ kreg_liquid_distribution_arm_pos8_d1(RegIndex.kreg_liquid_distribution_arm_pos8_d1), // 位置8 舵机1位置
+ kreg_liquid_distribution_arm_pos9_d0(RegIndex.kreg_liquid_distribution_arm_pos9_d0), // 位置9 舵机0位置
+ kreg_liquid_distribution_arm_pos9_d1(RegIndex.kreg_liquid_distribution_arm_pos9_d1), // 位置9 舵机1位置
+ kreg_liquid_distribution_arm_pos10_d0(RegIndex.kreg_liquid_distribution_arm_pos10_d0), // 位置10 舵机0位置
+ kreg_liquid_distribution_arm_pos10_d1(RegIndex.kreg_liquid_distribution_arm_pos10_d1), // 位置10 舵机1位置
+ kreg_liquid_distribution_arm_pos11_d0(RegIndex.kreg_liquid_distribution_arm_pos11_d0), // 位置11 舵机0位置
+ kreg_liquid_distribution_arm_pos11_d1(RegIndex.kreg_liquid_distribution_arm_pos11_d1), // 位置11 舵机1位置
+ kreg_liquid_distribution_arm_pos12_d0(RegIndex.kreg_liquid_distribution_arm_pos12_d0), // 位置12 舵机0位置
+ kreg_liquid_distribution_arm_pos12_d1(RegIndex.kreg_liquid_distribution_arm_pos12_d1), // 位置12 舵机1位置
+ kreg_liquid_distribution_arm_pos13_d0(RegIndex.kreg_liquid_distribution_arm_pos13_d0), // 位置13 舵机0位置
+ kreg_liquid_distribution_arm_pos13_d1(RegIndex.kreg_liquid_distribution_arm_pos13_d1), // 位置13 舵机1位置
+ kreg_liquid_distribution_arm_pos14_d0(RegIndex.kreg_liquid_distribution_arm_pos14_d0), // 位置14 舵机0位置
+ kreg_liquid_distribution_arm_pos14_d1(RegIndex.kreg_liquid_distribution_arm_pos14_d1), // 位置14 舵机1位置
+ kreg_liquid_distribution_arm_pos15_d0(RegIndex.kreg_liquid_distribution_arm_pos15_d0), // 位置15 舵机0位置
+ kreg_liquid_distribution_arm_pos15_d1(RegIndex.kreg_liquid_distribution_arm_pos15_d1), // 位置15 舵机1位置
+ kreg_liquid_distribution_arm_pos16_d0(RegIndex.kreg_liquid_distribution_arm_pos16_d0), // 位置16 舵机0位置
+ kreg_liquid_distribution_arm_pos16_d1(RegIndex.kreg_liquid_distribution_arm_pos16_d1); // 位置16 舵机1位置
+
+ public final RegIndex regIndex;
+
+ LiquidArmRegIndex(RegIndex regIndex) {
+ this.regIndex = regIndex;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoId.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoId.java
new file mode 100644
index 0000000..254a303
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoId.java
@@ -0,0 +1,31 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+public enum MiniServoId {
+ claw(MiniServoMId.RobotArmClawSV, "夹爪"),
+ ;
+
+ private MiniServoMId servoMId;
+ private String description;
+
+ public MiniServoMId getServoMId() {
+ return servoMId;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ MiniServoId(MiniServoMId servoMId, String description) {
+ this.servoMId = servoMId;
+ this.description = description;
+ }
+
+ public static MiniServoId getById(MiniServoMId servoMId) {
+ for (MiniServoId deviceServoId : MiniServoId.values()) {
+ if (deviceServoId.getServoMId() == servoMId) {
+ return deviceServoId;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoMId.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoMId.java
new file mode 100644
index 0000000..827d692
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoMId.java
@@ -0,0 +1,17 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+import com.iflytop.handacid.hardware.type.MId;
+
+public enum MiniServoMId {
+ NotSet(MId.NotSet),//
+ RobotArmClawSV(MId.RobotArmClawSV),
+ ;
+
+ final public MId mid;
+
+ MiniServoMId(MId mid) {
+// Assert.isTrue(this.name().equals(mid.name()),"MiniServoMid Init fail");
+ this.mid = mid;
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoRegIndex.java b/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoRegIndex.java
new file mode 100644
index 0000000..a7a049c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/Servo/MiniServoRegIndex.java
@@ -0,0 +1,71 @@
+package com.iflytop.handacid.hardware.type.Servo;
+
+import com.iflytop.handacid.hardware.type.RegIndex;
+
+public enum MiniServoRegIndex {
+ // kreg_module_version(RegIndex.kreg_module_version), // 模块版本
+ // kreg_module_type(RegIndex.kreg_module_type), // 模块类型
+ // kreg_module_status(RegIndex.kreg_module_status), // 0idle,1busy,2error
+ // kreg_module_errorcode(RegIndex.kreg_module_errorcode), // inited_flag
+
+ kreg_mini_servo_pos(RegIndex.kreg_mini_servo_pos), // 位置
+ kreg_mini_servo_limit_velocity(RegIndex.kreg_mini_servo_limit_velocity), // 限制速度
+ kreg_mini_servo_limit_torque(RegIndex.kreg_mini_servo_limit_torque), // 限制扭矩
+ kreg_mini_servo_protective_torque(RegIndex.kreg_mini_servo_protective_torque), // 保护扭矩
+ kreg_mini_servo_is_move(RegIndex.kreg_mini_servo_is_move), // 是否在运动
+
+
+ kreg_mini_servo_status(RegIndex.kreg_mini_servo_status), // 舵机状态
+ kreg_mini_servo_voltage(RegIndex.kreg_mini_servo_voltage), // 电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V
+ kreg_mini_servo_current(RegIndex.kreg_mini_servo_current), // 电流 反馈当前工作电流值,单位为ma
+ kreg_mini_servo_temperature(RegIndex.kreg_mini_servo_temperature), // 温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度
+ kreg_mini_servo_loadvalue(RegIndex.kreg_mini_servo_loadvalue), // 负载值 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000
+ kreg_mini_servo_target_pos_tolerance(RegIndex.kreg_mini_servo_target_pos_tolerance), // 目标位置容忍度
+
+
+ kreg_mini_servo_firmware_main_version(RegIndex.kreg_mini_servo_firmware_main_version), // 固件主版本号
+ kreg_mini_servo_firmware_sub_version(RegIndex.kreg_mini_servo_firmware_sub_version), // 固件次版本号
+ kreg_mini_servo_servo_main_version(RegIndex.kreg_mini_servo_servo_main_version), // 舵机主版本号
+ kreg_mini_servo_servo_sub_version(RegIndex.kreg_mini_servo_servo_sub_version), // 舵机次版本号
+ kreg_mini_servo_servo_min_angle(RegIndex.kreg_mini_servo_servo_min_angle), // 最小角度限制
+ kreg_mini_servo_servo_max_angle(RegIndex.kreg_mini_servo_servo_max_angle), // 最大角度限制
+ kreg_mini_servo_servo_max_temp(RegIndex.kreg_mini_servo_servo_max_temp), // 最高温度上限
+ kreg_mini_servo_servo_max_voltage(RegIndex.kreg_mini_servo_servo_max_voltage), // 最高输入电压
+ kreg_mini_servo_servo_min_voltage(RegIndex.kreg_mini_servo_servo_min_voltage), // 最低输入电压
+ kreg_mini_servo_servo_max_torque(RegIndex.kreg_mini_servo_servo_max_torque), // 最大扭矩
+ kreg_mini_servo_servo_unload_condition(RegIndex.kreg_mini_servo_servo_unload_condition), // 卸载条件
+ kreg_mini_servo_servo_p(RegIndex.kreg_mini_servo_servo_p), // P 比例系
+ kreg_mini_servo_servo_d(RegIndex.kreg_mini_servo_servo_d), // D 微分系
+ kreg_mini_servo_servo_i(RegIndex.kreg_mini_servo_servo_i), // I
+ kreg_mini_servo_servo_min_start(RegIndex.kreg_mini_servo_servo_min_start), // 最小启动
+ kreg_mini_servo_servo_cw_dead_zone(RegIndex.kreg_mini_servo_servo_cw_dead_zone), // 顺时针不灵敏区
+ kreg_mini_servo_servo_ccw_dead_zone(RegIndex.kreg_mini_servo_servo_ccw_dead_zone), // 逆时针不灵敏
+ kreg_mini_servo_servo_protect_current(RegIndex.kreg_mini_servo_servo_protect_current), // 保护电流
+ kreg_mini_servo_servo_protect_torque(RegIndex.kreg_mini_servo_servo_protect_torque), // 保护扭矩 0->100 ,触发后,需要写入与组转方向相反的位置指令,进行解除
+ kreg_mini_servo_servo_protect_time(RegIndex.kreg_mini_servo_servo_protect_time), // 保护时间
+ kreg_mini_servo_servo_overload_torque(RegIndex.kreg_mini_servo_servo_overload_torque), // 过载扭矩
+ kreg_mini_servo_servo_speed_p(RegIndex.kreg_mini_servo_servo_speed_p), // 速度闭环P比例参数
+ kreg_mini_servo_servo_overload_time(RegIndex.kreg_mini_servo_servo_overload_time), // 过流保护时间
+ kreg_mini_servo_servo_speed_i(RegIndex.kreg_mini_servo_servo_speed_i), // 速度闭环I积分参数
+ kreg_mini_servo_servo_torque_switch(RegIndex.kreg_mini_servo_servo_torque_switch), // 扭矩开关
+ kreg_mini_servo_servo_acc(RegIndex.kreg_mini_servo_servo_acc), // 加速度
+ kreg_mini_servo_servo_target_pos(RegIndex.kreg_mini_servo_servo_target_pos), // 目标位置
+ kreg_mini_servo_servo_run_time(RegIndex.kreg_mini_servo_servo_run_time), // 运行时间
+ kreg_mini_servo_servo_run_speed(RegIndex.kreg_mini_servo_servo_run_speed), // 运行速度
+ kreg_mini_servo_servo_torque_limit(RegIndex.kreg_mini_servo_servo_torque_limit), // 转矩限制
+ kreg_mini_servo_servo_lock_flag(RegIndex.kreg_mini_servo_servo_lock_flag), // 锁标志
+ kreg_mini_servo_servo_current_pos(RegIndex.kreg_mini_servo_servo_current_pos), // 当前位置
+ kreg_mini_servo_servo_current_speed(RegIndex.kreg_mini_servo_servo_current_speed), // 当前速度
+ kreg_mini_servo_servo_current_load(RegIndex.kreg_mini_servo_servo_current_load), // 当前负载 bit10为方向位 输出驱动电机的当前占空比电压,单位为0.1%,取值0-1000
+ kreg_mini_servo_servo_current_voltage(RegIndex.kreg_mini_servo_servo_current_voltage), // 当前电压 反馈当前舵机工作电压,反馈精度为0.1V,即120*0.1=12V
+ kreg_mini_servo_servo_current_temp(RegIndex.kreg_mini_servo_servo_current_temp), // 当前温度 反馈当前舵机内部工作温度,反馈精度为1摄氏度
+ kreg_mini_servo_servo_status(RegIndex.kreg_mini_servo_servo_status), // 舵机状态
+ kreg_mini_servo_servo_move_flag(RegIndex.kreg_mini_servo_servo_move_flag), // 移动标志
+ kreg_mini_servo_servo_current_current(RegIndex.kreg_mini_servo_servo_current_current), // 当前电流 反馈当前工作电流值,单位为6.26.5mA,最大可反馈电流为500*6.5mA=3250mA
+ ;
+ public final RegIndex regIndex;
+
+ MiniServoRegIndex(RegIndex regIndex) {
+ this.regIndex = regIndex;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/DeviceStepMotorId.java b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/DeviceStepMotorId.java
new file mode 100644
index 0000000..2b09d1a
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/DeviceStepMotorId.java
@@ -0,0 +1,31 @@
+package com.iflytop.handacid.hardware.type.StepMotor;
+
+
+public enum DeviceStepMotorId {
+ ;
+
+ private StepMotorMId stepMotorMId;
+ private String description;
+
+ public StepMotorMId getStepMotorMId() {
+ return stepMotorMId;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ DeviceStepMotorId(StepMotorMId stepMotorMId, String description) {
+ this.stepMotorMId = stepMotorMId;
+ this.description = description;
+ }
+
+ public static DeviceStepMotorId getById(StepMotorMId stepMotorMId) {
+ for (DeviceStepMotorId deviceStepMotorId : DeviceStepMotorId.values()) {
+ if (deviceStepMotorId.getStepMotorMId() == stepMotorMId) {
+ return deviceStepMotorId;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/MotorDirect.java b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/MotorDirect.java
new file mode 100644
index 0000000..7f5c57f
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/MotorDirect.java
@@ -0,0 +1,17 @@
+package com.iflytop.handacid.hardware.type.StepMotor;
+
+public enum MotorDirect {
+ FORWARD(1), // 正转
+ BACKWARD(0), // 反转
+ ;
+
+ private final int value;
+
+ MotorDirect(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorMId.java b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorMId.java
new file mode 100644
index 0000000..23b7f7c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorMId.java
@@ -0,0 +1,32 @@
+package com.iflytop.handacid.hardware.type.StepMotor;
+
+import com.iflytop.handacid.hardware.type.MId;
+
+public enum StepMotorMId {
+ DualRobot1M(MId.DualRobot1M),
+ DualRobot2M(MId.DualRobot2M),
+ RobotArmZM(MId.RobotArmZM),
+ BrushlessPump1M(MId.BrushlessPump1M),
+ BrushlessPump2M(MId.BrushlessPump2M),
+ BrushlessPump3M(MId.BrushlessPump3M),
+ BrushlessPump4M(MId.BrushlessPump4M),
+ BrushlessPump5M(MId.BrushlessPump5M),
+ BrushlessPump6M(MId.BrushlessPump6M),
+ BrushlessPump7M(MId.BrushlessPump7M),
+ BrushlessPump8M(MId.BrushlessPump8M),
+ BrushlessPump9M(MId.BrushlessPump9M),
+ BrushlessPump10M(MId.BrushlessPump10M),
+ StepPump1M(MId.StepPump1M),
+ StepPump2M(MId.StepPump2M),
+ StepPump3M(MId.StepPump3M),
+ TitratorStir1M(MId.TitratorStir1M),
+ TitratorStir2M(MId.TitratorStir2M),
+ Titrator1M(MId.Titrator1M),
+ Titrator2M(MId.Titrator2M)
+ ;
+ final public MId mid;
+
+ StepMotorMId(MId mid) {
+ this.mid = mid;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorRegIndex.java b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorRegIndex.java
new file mode 100644
index 0000000..ebc009b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorRegIndex.java
@@ -0,0 +1,56 @@
+package com.iflytop.handacid.hardware.type.StepMotor;
+
+import com.iflytop.handacid.hardware.type.RegIndex;
+
+public enum StepMotorRegIndex {
+ // kreg_module_version(RegIndex.kreg_module_version), // 模块版本
+ // kreg_module_type(RegIndex.kreg_module_type), // 模块类型
+ // kreg_module_status(RegIndex.kreg_module_status), // 0idle,1busy,2error
+ // kreg_module_errorcode(RegIndex.kreg_module_errorcode), // inited_flag
+ /***********************************************************************************************************************
+ * step_motor *
+ ***********************************************************************************************************************/
+ kreg_step_motor_pos(RegIndex.kreg_step_motor_pos), // 机器人x坐标
+ kreg_step_motor_is_enable(RegIndex.kreg_step_motor_is_enable), // 是否使能
+ kreg_step_motor_dpos(RegIndex.kreg_step_motor_dpos), // 执行完上一条指令后的相对位移
+ kreg_step_motor_has_move_zero(RegIndex.kreg_step_motor_has_move_zero), // 是否已经移动到零点
+ kreg_step_motor_shaft(RegIndex.kreg_step_motor_shaft), // x轴是否反转
+ kreg_step_motor_one_circle_pulse(RegIndex.kreg_step_motor_one_circle_pulse), // x轴一圈脉冲数
+ kreg_step_motor_one_circle_pulse_denominator(RegIndex.kreg_step_motor_one_circle_pulse_denominator), // 设置一圈脉冲数的分母
+ kreg_step_motor_default_velocity(RegIndex.kreg_step_motor_default_velocity), // 默认速度
+ kreg_step_motor_low_velocity(RegIndex.kreg_step_motor_low_velocity),
+ kreg_step_motor_mid_velocity(RegIndex.kreg_step_motor_mid_velocity),
+ kreg_step_motor_high_velocity(RegIndex.kreg_step_motor_high_velocity),
+ kreg_step_motor_ihold(RegIndex.kreg_step_motor_ihold), // 步进电机电流配置
+ kreg_step_motor_irun(RegIndex.kreg_step_motor_irun), // 步进电机电流配置
+ kreg_step_motor_iholddelay(RegIndex.kreg_step_motor_iholddelay), // 步进电机电流配置
+ kreg_step_motor_iglobalscaler(RegIndex.kreg_step_motor_iglobalscaler), // 步进电机电流配置
+ kreg_step_motor_mres(RegIndex.kreg_step_motor_mres), // 步进电机电流配置
+
+ kreg_step_motor_run_to_zero_speed(RegIndex.kreg_step_motor_run_to_zero_speed), // 回零速度
+ kreg_step_motor_look_zero_edge_speed(RegIndex.kreg_step_motor_look_zero_edge_speed), // 找零边缘速度
+ kreg_step_motor_max_d(RegIndex.kreg_step_motor_max_d), // 最大限制距离
+ kreg_step_motor_min_d(RegIndex.kreg_step_motor_min_d), // 最小限制距离
+ kreg_step_motor_in_debug_mode(RegIndex.kreg_step_motor_in_debug_mode), // 驱动器处于调试模式
+ kreg_step_motor_vstart(RegIndex.kreg_step_motor_vstart), // a1起作用的速度
+ kreg_step_motor_a1(RegIndex.kreg_step_motor_a1), //
+ kreg_step_motor_amax(RegIndex.kreg_step_motor_amax), //
+ kreg_step_motor_v1(RegIndex.kreg_step_motor_v1), //
+ kreg_step_motor_dmax(RegIndex.kreg_step_motor_dmax), //
+ kreg_step_motor_d1(RegIndex.kreg_step_motor_d1), //
+ kreg_step_motor_vstop(RegIndex.kreg_step_motor_vstop), //
+ kreg_step_motor_tzerowait(RegIndex.kreg_step_motor_tzerowait), //
+ kreg_step_motor_enc_resolution(RegIndex.kreg_step_motor_enc_resolution), // 编码器分辨率 1000,1024,4000,4096,16384
+ kreg_step_motor_enable_enc(RegIndex.kreg_step_motor_enable_enc), //
+ kreg_step_motor_dzero_pos(RegIndex.kreg_step_motor_dzero_pos), // 驱动器处于调试模式
+ kret_step_motor_pos_devi_tolerance(RegIndex.kret_step_motor_pos_devi_tolerance), // 位置偏差容忍度
+ kret_step_motor_io_trigger_append_distance(RegIndex.kret_step_motor_io_trigger_append_distance), // 移动到io时,附加的距离
+
+
+ ;
+ public final RegIndex regIndex;
+
+ StepMotorRegIndex(RegIndex regIndex) {
+ this.regIndex = regIndex;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorSpeedLevel.java b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorSpeedLevel.java
new file mode 100644
index 0000000..397dbe5
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/StepMotor/StepMotorSpeedLevel.java
@@ -0,0 +1,9 @@
+package com.iflytop.handacid.hardware.type.StepMotor;
+
+public enum StepMotorSpeedLevel {
+ DEFAULT,
+ LOW,
+ MID,
+ HIGH,
+ ;
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/TecHeaterRegIndex.java b/src/main/java/com/iflytop/handacid/hardware/type/TecHeaterRegIndex.java
new file mode 100644
index 0000000..00152ef
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/TecHeaterRegIndex.java
@@ -0,0 +1,33 @@
+package com.iflytop.handacid.hardware.type;
+
+
+public enum TecHeaterRegIndex {
+ kreg_tec103_heater1_target_temp(RegIndex.kreg_tec103_heater1_target_temp),
+ kreg_tec103_heater1_real_temp(RegIndex.kreg_tec103_heater1_real_temp),
+ kreg_tec103_heater1_sensor_r(RegIndex.kreg_tec103_heater1_sensor_r),
+ kreg_tec103_heater1_polynomial(RegIndex.kreg_tec103_heater1_polynomial),
+ kreg_tec103_heater1_ntc_b(RegIndex.kreg_tec103_heater1_ntc_b),
+ kreg_tec103_heater1_ntc_r0(RegIndex.kreg_tec103_heater1_ntc_r0),
+ kreg_tec103_heater1_output_enable(RegIndex.kreg_tec103_heater1_output_enable),
+ kreg_tec103_heater1_output_max_duty(RegIndex.kreg_tec103_heater1_output_max_duty),
+ kreg_tec103_heater1_output_mode(RegIndex.kreg_tec103_heater1_output_mode),
+ kreg_tec103_heater1_output_pol(RegIndex.kreg_tec103_heater1_output_pol),
+ kreg_tec103_heater1_output_percent(RegIndex.kreg_tec103_heater1_output_percent),
+ kreg_tec103_heater1_pwm_freq(RegIndex.kreg_tec103_heater1_pwm_freq),
+ kreg_tec103_heater1_over_temp_protect_mode(RegIndex.kreg_tec103_heater1_over_temp_protect_mode),
+ kreg_tec103_heater1_p(RegIndex.kreg_tec103_heater1_p),
+ kreg_tec103_heater1_i(RegIndex.kreg_tec103_heater1_i),
+ kreg_tec103_heater1_d(RegIndex.kreg_tec103_heater1_d),
+ kreg_tec103_heater1_pid_self_tune(RegIndex.kreg_tec103_heater1_pid_self_tune),
+ kreg_tec103_heater_version(RegIndex.kreg_tec103_heater_version),
+ kreg_tec103_heater_address(RegIndex.kreg_tec103_heater_address),
+ kreg_tec103_heater_baud_rate(RegIndex.kreg_tec103_heater_baud_rate),
+ kreg_tec103_heater_e_code(RegIndex.kreg_tec103_heater_e_code),
+ ;
+
+ public final RegIndex regIndex;
+
+ TecHeaterRegIndex(RegIndex regIndex) {
+ this.regIndex = regIndex;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/TecHeaterRodMId.java b/src/main/java/com/iflytop/handacid/hardware/type/TecHeaterRodMId.java
new file mode 100644
index 0000000..52f27cb
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/TecHeaterRodMId.java
@@ -0,0 +1,16 @@
+package com.iflytop.handacid.hardware.type;
+
+import org.springframework.util.Assert;
+
+public enum TecHeaterRodMId {
+ HeaterRod1(MId.HeaterRod1),
+ HeaterRod2(MId.HeaterRod2),
+ ;
+
+ final public MId mid;
+
+ TecHeaterRodMId(MId mid) {
+ Assert.isTrue(this.name().equals(mid.name()), "IO1_ChimeBuzzer Init fail");
+ this.mid = mid;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/appevent/A8kCanBusOnConnectEvent.java b/src/main/java/com/iflytop/handacid/hardware/type/appevent/A8kCanBusOnConnectEvent.java
new file mode 100644
index 0000000..0efb7fc
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/appevent/A8kCanBusOnConnectEvent.java
@@ -0,0 +1,7 @@
+package com.iflytop.handacid.hardware.type.appevent;
+
+public class A8kCanBusOnConnectEvent extends AppEvent {
+ public A8kCanBusOnConnectEvent() {
+ super(A8kCanBusOnConnectEvent.class.getSimpleName());
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/appevent/AppEvent.java b/src/main/java/com/iflytop/handacid/hardware/type/appevent/AppEvent.java
new file mode 100644
index 0000000..a6e8ddf
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/appevent/AppEvent.java
@@ -0,0 +1,13 @@
+package com.iflytop.handacid.hardware.type.appevent;
+
+import java.util.Date;
+import java.util.UUID;
+
+public class AppEvent {
+ public String typeName;
+ public Integer timestamp = (int) (new Date().getTime() / 1000);
+ public String eventId = UUID.randomUUID().toString();
+ public AppEvent(String typeName) {
+ this.typeName = typeName;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/db/SubModuleRegInitialValue.java b/src/main/java/com/iflytop/handacid/hardware/type/db/SubModuleRegInitialValue.java
new file mode 100644
index 0000000..01ba567
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/db/SubModuleRegInitialValue.java
@@ -0,0 +1,22 @@
+package com.iflytop.handacid.hardware.type.db;
+
+import com.iflytop.handacid.hardware.type.MId;
+import com.iflytop.handacid.hardware.type.RegIndex;
+
+import java.io.Serializable;
+
+public class SubModuleRegInitialValue implements Serializable {
+ public int id = 0;
+ public MId mid;
+ public RegIndex regIndex;
+ public Integer regInitVal;
+
+ public SubModuleRegInitialValue() {
+ }
+
+ public SubModuleRegInitialValue(MId mid, RegIndex regIndex, Integer regInitVal) {
+ this.mid = mid;
+ this.regIndex = regIndex;
+ this.regInitVal = regInitVal;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/driver/HeaterRodSlavedId.java b/src/main/java/com/iflytop/handacid/hardware/type/driver/HeaterRodSlavedId.java
new file mode 100644
index 0000000..6bd0c6b
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/driver/HeaterRodSlavedId.java
@@ -0,0 +1,48 @@
+package com.iflytop.handacid.hardware.type.driver;
+
+
+import com.iflytop.handacid.common.enums.Device;
+import com.iflytop.handacid.hardware.type.MId;
+
+public enum HeaterRodSlavedId {
+ HEATER_ROD1_ID(Device.HEAT_ROD_1, 1, MId.IO1_ADC, 1),
+ HEATER_ROD2_ID(Device.HEAT_ROD_2, 2, MId.IO1_ADC, 2),
+ ;
+
+ private final Device device;
+ private final Integer slaveId;
+ private final MId mid;
+ private final Integer adcCurrentIndex; // ADC电流索引
+
+ HeaterRodSlavedId(Device device, Integer slaveId, MId mid, Integer adcCurrentIndex) {
+ this.device = device;
+ this.slaveId = slaveId;
+ this.mid = mid;
+ this.adcCurrentIndex = adcCurrentIndex;
+ }
+
+ public Device getCmdDevice() {
+ return device;
+ }
+
+ public Integer getSlaveId() {
+ return slaveId;
+ }
+
+ public MId getMid() {
+ return mid;
+ }
+
+ public Integer getAdcCurrentIndex() {
+ return adcCurrentIndex;
+ }
+
+ static public HeaterRodSlavedId getByCmdDevice(Device device) {
+ for (HeaterRodSlavedId value : HeaterRodSlavedId.values()) {
+ if (value.device == device) {
+ return value;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/error/A8kEcode.java b/src/main/java/com/iflytop/handacid/hardware/type/error/A8kEcode.java
new file mode 100644
index 0000000..e555143
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/error/A8kEcode.java
@@ -0,0 +1,255 @@
+package com.iflytop.handacid.hardware.type.error;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 错误分类
+ * 10,
+ * 为代码错误,代码没有BUG的情况下,这个错误时不会出现的,前端打印出错误,和错误携带的栈信息,方便产品维护时,非程序员能够提供详细的错误的信息。
+ *
+ *
+ *
+ *
+ *
+ */
+@Slf4j
+public enum A8kEcode {
+
+ SUC(0),
+ //
+ // 代码错误,代码没有BUG的情况下,前端直接打印错误信息和栈信息即可
+ //
+ CODEERROR(10), //代码错误
+ ERROR_WORK_MODE(11), //工作模式错误
+ ZAPP_INTERRUPT_EXCEPTION(12),
+ ERROR_OPERATION(13), //操作错误
+ IP_FORMAT_ERROR(14), //IP地址格式错误
+ SHUTDOWN_ERROR_DEVICE_IS_WORKING(15), //设备正在工作中,不允许关机
+ LOGIN_OUT_ERROR_DEVICE_IS_WORKING(16), //设备正在工作中,不允许退出登录
+ DEVICE_IS_BUSY(17), //设备正在工作中,不允许操作
+ SYS_EXCEPTION(18), //系统异常
+ DEVICE_NOT_INIT(19), //设备未初始化
+ APPE_PAUSE_OPERATION_NOT_SUPPORT_IN_ENGINEER_TASK(20), //
+ APPE_CONTINUE_OPERATION_NOT_SUPPORT_IN_ENGINEER_TASK(21), //
+
+ //
+ // 参数错误
+ //
+ PE_PARAM_OUT_OF_RANGE(100),//参数超出范围
+ //
+ // 用户错误
+ //
+ USR_ALREADY_EXIST(110),//用户已存在
+ USR_NOT_EXIT(111), //用户不存在
+ USR_PASSWORD_ERROR(112),//用户密码错误
+ USR_PASSWORD_IS_EMPTY(113),//用户密码为空
+ USR_OLD_PASSWORD_ERROR(114),//用户旧密码错误
+
+ //
+ // ID卡错误
+ //
+ APPE_A8K_ID_CARD_NOT_MOUNTED(120), //ID卡未挂载
+ //
+ // 试管配置管理服务相关错误码
+ //
+ APPE_TUBE_HOLDER_SETTING_IS_LOCKED(130),//试管架设置被锁定,已锁定的,说明当前后台正在使用这个配置,是不允许修改
+ APPE_TUBEHOLDER_SETTING_ERROR(131), //试管架配置错误
+
+ //
+ // 急诊错误
+ //
+ APPE_EMERGENCY_SAMPLE_IS_PROCESSING(141), //添加急诊样本失败,急诊样本还没有处理完成 (这个错误发生在前端,没有做好状态检测)
+ APPE_ADD_EMERGENCY_ACTION_IS_NOT_ALLOWED_WHEN_WORKING(142),//添加急诊样本失败,设备正在运行中 (这个错误发生在前端,没有做好状态检测)
+ //
+ // 设备操作操作
+ //
+ APPE_DEVICE_IS_IN_FATAL_ERROR(150),//设备处于严重错误,请关机后重启设备进行恢复
+ APPE_DEVICE_INIT_CHECK_FAIL(151),//设备初始化检查失败
+
+ APPE_DO_ACTION_FAIL_DEVICE_IS_WORKING(152),//设备正在工作中,不允许执行操作
+ APPE_CONSUMABLES_IS_IN_USE_NOT_ALLOW_UNSTALL(153),//耗材正在使用中,不允许卸载
+
+ DEVICE_STARTUP_IN_PROGRESS(154), // 设备启动中,请稍后
+ //
+ // 业务流程中的错误
+ // 1. 点击开始运行后,设备在运行过程中出现的错误
+ // 2. 下面错误,中文情况下,直接显示注释信息即可
+ //
+ //扫描耗材错误
+ APPE_NO_UNINSTALLED_CONSUMABLE(160),//没有未安装的耗材
+
+
+ APPE_TIP_G1_SETTING_NUM_MISMATCH_ERROR(161),//Tip设置数量错误
+ APPE_TIP_G2_SETTING_NUM_MISMATCH_ERROR(162),//Tip设置数量错误
+ APPE_TIP_G3_SETTING_NUM_MISMATCH_ERROR(163),//Tip设置数量错误
+
+ APPE_INACTIVE_TUBEHOLDER_SETTING_EXISTS(164),//有试管架配置未激活
+
+
+ //入料阶段错误
+ APPE_SCAN_TUBEHOLDER_TYPE_TIMEOUT(200),//扫描试管架类型超时
+ APPE_TUBE_HOLDER_TYPE_IS_NOT_SUPPORT(201),//试管架类型不支持
+ APPE_INFEED_OVERTIME_FAIL(202),//入料超时失败
+
+ //样本处理过程中的错误
+ APPE_PUT_TIP_FAIL(210),//丢弃Tip失败
+ APPE_TAKE_TIP_FAIL(211),//取Tip失败
+ APPE_DETECT_SAMPLE_FAIL(212),// 检测样本失败
+ APPE_TAKE_LARGE_BUFFER_LIQUID_FAIL(213),//取大瓶缓冲液失败
+ APPE_TAKE_SAMPLE_FAIL(214),//取大瓶缓冲液失败
+
+
+ //出料阶段错误
+ APPE_EJECT_TUBEHOLDER_TIMEOUT(220),//弹出试管架超时
+ APPE_NO_TUBE_IN_HOLDER(221),//试管架中没有试管
+ APPE_CONSUME_NOT_ENOUGH(222),//耗材不足
+ APPE_TIP_NOT_ENOUGH(223),//tip不足
+
+ //运行中的公用错误
+ APPE_A8K_PROJ_CARD_PARSE_ERROR(300), //ID卡解析错误
+ APPE_A8K_PROJ_CARD_EXPIRYED(301), //ID卡过期
+ APPE_A8K_PROJ_ID_IS_EMPTY(302), //项目ID为空
+ PROJ_CARD_ERROR_WRONG_UNSUPPORTED(303), //项目不支持
+ APPE_PULLERM_INIT_POS_ERROR(305), //初始化阶段,拉板电机没有处于零点位置
+ APPE_PUSHERM_INIT_POS_ERROR(306), //初始化阶段,推板电机没有处于零点位置
+ APPE_PLATE_STUCK_DETECTOR_SENSOR_TRIGGER(307), //卡板检测传感器触发
+ APPE_TUBE_X_CHANNEL_IS_NOT_EMPTY(308), //试管架通道有异物
+ APPE_PLATE_BOX_NOT_COVER(309), //板夹仓未盖
+ APPE_DEVICE_NOT_INITED(310), //设备未初始化
+ APPE_SHAKE_MODULE_CLAMPM_NOT_IN_ZERO_POS(311), //摇匀模组夹紧模块没有在零点位置
+ APPE_INCUBATION_PLATE_OUTLET_STUCK_DETECTOR_SENSOR_IS_TRIGGER(312), //孵育板出料口卡板检测传感器触发
+ APPE_INCUBATION_PLATE_INLET_STUCK_DETECTOR_SENSOR_IS_TRIGGER(313), //孵育板入料口卡板检测传感器触发
+
+
+ //
+ // ID卡检验错误
+ //
+ PROJ_CARD_ERROR_EXPIRYED(500),//ID卡验证错误,过期
+ PROJ_CARD_ERROR_WRONG_FUNCTION_NUM(500),//ID卡验证错误,函数数量错误
+ PROJ_CARD_ERROR_WRONG_OPT_NUM(500),//ID卡验证错误,光学数量错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_X_TYPE(500),//ID卡验证错误,公式X类型错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_JUDGE_X_TYPE(500),//ID卡验证错误,公式X类型错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_L_X_TYPE(500),//ID卡验证错误,公式X类型错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_H_X_TYPE(500),//ID卡验证错误,公式X类型错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_X_SCOPE(500),//ID卡验证错误,公式X的范围错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_LIMIT_SCOPE(500),//ID卡验证错误,光学数量错误
+ PROJ_CARD_ERROR_WRONG_FUNCTION_TYPE(500),//ID卡验证错误,公式类型错误
+
+ PROJ_CARD_ERROR_WRONG_RESULT_UNIT_TYPE(500),//ID卡验证错误,Unit类型错误
+ PROJ_CARD_ERROR_WRONG_RESULT_UNIT2_COEFFICIENT(500),//ID卡验证错误,Unit2转换系数错误
+ PROJ_CARD_ERROR_WRONG_RESULT_UNIT3_COEFFICIENT(500),//ID卡验证错误,Unit3转换系数错误
+
+ // PROJ_CARD_ERROR_WRONG_UNSUPPORTED(520),//ID卡验证错误,不支持的ID卡
+ PROJ_CARD_ERROR_BUILD_IN_PROJ_INFO_ERROR(521),//ID卡验证错误,内置项目信息错误
+ PROJ_CARD_ERROR_ID_CARD_VERSION_IS_LOWER_THAN_STORED(522),//ID卡验证错误,ID卡版本低于存储的版本
+
+
+ //
+ // LowBoard 底层错误,直接打印错误英文
+ //
+ LOW_ERROR_HARDWARE_ERROR_START(1000),
+ LOW_ERROR_BOARD_COMMON_ERROR(1001),
+ LOW_ERROR_PARAM_OUT_OF_RANGE(1102),
+ LOW_ERROR_CMD_NOT_SUPPORT(1103),
+ LOW_ERROR_DEVICE_IS_BUSY(1104),
+ LOW_ERROR_DEVICE_IS_OFFLINE(1105),
+ LOW_ERROR_OVERTIME(1106),
+ LOW_ERROR_NOACK(1107),
+ LOW_ERROR_ERRORACK(1108),
+ LOW_ERROR_DEVICE_OFFLINE(1109),
+ LOW_ERROR_SUBDEVICE_OVERTIME(1111),
+ LOW_ERROR_BUFFER_NOT_ENOUGH(1112),
+ LOW_ERROR_CMD_PARAM_NUM_ERROR(1114),
+ LOW_ERROR_CHECKCODE_IS_ERROR(1115),
+ LOW_ERROR_ILLEGAL_OPERATION(1116),
+ LOW_ERROR_ACTION_OVERTIME(1117),
+ LOW_ERROR_MODULE_OPEATION_BREAK_BY_USER(1202),
+ LOW_ERROR_MODULE_NOT_FIND_REG(1207),
+ LOW_ERROR_XYMOTOR_X_FIND_ZERO_EDGE_FAIL(1306),
+ LOW_ERROR_XYMOTOR_Y_FIND_ZERO_EDGE_FAIL(1307),
+ LOW_ERROR_XYMOTOR_NOT_ENABLE(1308),
+ LOW_ERROR_XYMOTOR_TARGET_POS_OUTOF_RANGE(1309),
+ LOW_ERROR_XYMOTOR_NOT_MOVE_TO_ZERO(1310),
+ LOW_ERROR_PIPETTE_ERROR_NO_ERROR(1400),
+ LOW_ERROR_PIPETTE_ERROR_INIT_FAIL(1401),
+ LOW_ERROR_PIPETTE_ERROR_INVALID_CMD(1402),
+ LOW_ERROR_PIPETTE_ERROR_INVALID_ARG(1403),
+ LOW_ERROR_PIPETTE_ERROR_PRESSURE_SENSOR_ERROR(1404),
+ LOW_ERROR_PIPETTE_ERROR_OVER_PRESSURE(1405),
+ LOW_ERROR_PIPETTE_ERROR_LLD_ERROR(1406), //一般为lld时移液枪吸液过快,导致很快到达最大行程
+ LOW_ERROR_PIPETTE_ERROR_DEVICE_NOT_INIT(1407),
+ LOW_ERROR_PIPETTE_ERROR_TIP_POP_ERROR(1408),
+ LOW_ERROR_PIPETTE_ERROR_PUMP_OVERLOAD(1409),
+ LOW_ERROR_PIPETTE_ERROR_TIP_DROP(1410),
+ LOW_ERROR_PIPETTE_ERROR_CAN_BUS_ERROR(1411),
+ LOW_ERROR_PIPETTE_ERROR_INVALID_CHECKSUM(1412),
+ LOW_ERROR_PIPETTE_ERROR_EEPROM_ERROR(1413),
+ LOW_ERROR_PIPETTE_ERROR_CMD_BUFFER_EMPTY(1414),
+ LOW_ERROR_PIPETTE_ERROR_CMD_BUFFER_OVERFLOW(1415),
+ LOW_ERROR_PIPETTE_ERROR_TIP_BLOCK(1416),
+ LOW_ERROR_PIPETTE_ERROR_AIR_SUCTION(1417),
+ LOW_ERROR_PIPETTE_ERROR_BUBBLE(1418),
+ LOW_ERROR_PIPETTE_ERROR_VOLUME_ERROR(1419),
+ LOW_ERROR_PIPETTE_ERROR_TIP_ALREADY_LOAD(1420),
+ LOW_ERROR_PIPETTE_ERROR_TIP_LOAD_FAIL(1421),
+ LOW_ERROR_PIPETTE_ERROR_NO_TIP_WHEN_LLD(1422),
+ LOW_ERROR_PIPETTE_ERROR_UNINITED(1501),
+ LOW_ERROR_PIPETTE_ERROR_NOT_LLD_PREPARE(1502),
+ LOW_ERROR_PIPETTE_ERROR_TIPISLOAD_WHEN_LLD_PREPARE(1500),
+ LOW_ERROR_PIPETTE_ERROR_PUMP_LOAD_VAL_IS_NOT_EMPTY(1503),
+
+ LOW_ERROR_STEP_MOTOR_NOT_FOUND_ZERO_POINT(1600),
+ LOW_ERROR_STEP_MOTOR_NOT_GO_ZERO(1601),
+ LOW_ERROR_STEP_MOTOR_OVER_TEMPERATURE(1602),
+ LOW_ERROR_STEP_MOTOR_OVER_VOLTAGE(1603),
+ LOW_ERROR_STEP_MOTOR_RUN_OVERTIME(1604),
+ LOW_ERROR_STEP_MOTOR_NOT_ENABLE(1605),
+ LOW_ERROR_STEP_MOTOR_IOINDEX_OUT_OF_RANGE(1606),
+ LOW_ERROR_STEP_MOTOR_SUBIC_RESET(1607),
+ LOW_ERROR_STEP_MOTOR_DRV_ERR(1608),
+ LOW_ERROR_STEP_MOTOR_UV_CP(1609),
+ LOW_ERROR_STEP_MOTOR_NOT_FOUND_POINT_EDGE(1610),
+ LOW_ERROR_STEP_MOTOR_LOST_STEP(1611),
+ LOW_ERROR_STEP_MOTOR_NOT_MOVE_TO_ZERO(1612),
+ LOW_ERROR_STEP_MOTOR_OT(1613),
+ LOW_ERROR_STEP_MOTOR_OTPW(1614),
+ LOW_ERROR_STEP_MOTOR_S2GA(1615),
+ LOW_ERROR_STEP_MOTOR_S2GB(1616),
+ LOW_ERROR_STEP_MOTOR_OLA(1617),
+ LOW_ERROR_STEP_MOTOR_OLB(1618),
+ LOW_ERROR_MINI_SERVO_NOT_ENABLE(1700),
+ LOW_ERROR_MINI_SERVO_MODE_NOT_SUPPORT(1701),
+ LOW_ERROR_FAN_HARDWARE_FAULT(1800),
+ LOW_ERROR_WATER_COOLING_FAN_ERROR(1900),
+ LOW_ERROR_WATER_COOLING_TEMPERATURE_SENSOR_ERROR(1902),
+ LOW_ERROR_WATER_COOLING_PUMP_IS_ERROR(1903),
+ LOW_ERROR_WATER_COOLING_PELTER_IS_ERROR(1904),
+ //
+ // 底层扩展错误码,由java定义
+ //
+ LOW_EXT_ERROR_UNKOWN_INDEX_ERROR(5000), //代码错误,未知错误
+ LOW_EXT_ERROR_CMD_NOT_SUPPORT(5001), //代码错误,未知错误
+ LOW_EXT_ERROR_MOTOR_AT_WRONG_POS(5002),//电机在错误的位置
+ ;
+
+ public final int index;
+ public int rawindex = 0;
+
+ A8kEcode(int index) {
+ this.index = index;
+ }
+
+
+ static public A8kEcode fromInt(int index) {
+ for (var e : A8kEcode.values()) {
+ if (e.index == index) {
+ return e;
+ }
+ }
+ log.error("未知错误码:{}", index);
+ A8kEcode.LOW_EXT_ERROR_UNKOWN_INDEX_ERROR.rawindex = index;
+ return A8kEcode.LOW_EXT_ERROR_UNKOWN_INDEX_ERROR;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/error/AECodeError.java b/src/main/java/com/iflytop/handacid/hardware/type/error/AECodeError.java
new file mode 100644
index 0000000..7af85f9
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/error/AECodeError.java
@@ -0,0 +1,37 @@
+package com.iflytop.handacid.hardware.type.error;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
+public class AECodeError extends AppError {
+ @JsonIgnore
+ public StackTraceElement[] stackTraceElements;
+
+ public AECodeError(String exmsg) {
+ super(A8kEcode.CODEERROR);
+ this.exmsg = exmsg;
+ stackTraceElements = Thread.currentThread().getStackTrace();
+ }
+
+ public AECodeError(String fmt, Object... args) {
+ super(A8kEcode.CODEERROR);
+ this.exmsg = String.format(fmt, args);
+ stackTraceElements = Thread.currentThread().getStackTrace();
+ }
+
+ public AECodeError(Exception e) {
+ super(A8kEcode.CODEERROR);
+ this.exmsg = e.getMessage();
+ stackTraceElements = e.getStackTrace();
+ }
+
+
+ public static void main(String[] args) {
+ try {
+ throw new Exception("test");
+ } catch (Exception e) {
+ AECodeError aeCodeError = new AECodeError(e);
+// System.out.println(ZJsonHelper.objToPrettyJson(aeCodeError));
+ }
+
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/error/AEHardwareError.java b/src/main/java/com/iflytop/handacid/hardware/type/error/AEHardwareError.java
new file mode 100644
index 0000000..12a387c
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/error/AEHardwareError.java
@@ -0,0 +1,29 @@
+package com.iflytop.handacid.hardware.type.error;
+
+import com.iflytop.handacid.hardware.type.CmdId;
+import com.iflytop.handacid.hardware.type.MId;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+public class AEHardwareError extends AppError {
+ @Schema(description = "模块id(辅助调试,直接原始显示就行)")
+ public MId mid;
+ @Schema(description = "命令id(辅助调试,直接原始显示就行)")
+ public CmdId cmdId;
+
+ public String rawtxcmd;
+ public String rawrxcmd;
+
+ public AEHardwareError(A8kEcode errorCode, MId mid, CmdId cmdId, String rawtxcmd, String rawrxcmd) {
+ super(errorCode);
+ this.mid = mid;
+ this.cmdId = cmdId;
+ this.rawtxcmd = rawtxcmd;
+ this.rawrxcmd = rawrxcmd;
+ }
+
+ public AEHardwareError(A8kEcode errorCode, MId mid, CmdId cmdId) {
+ super(errorCode);
+ this.mid = mid;
+ this.cmdId = cmdId;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/error/AppError.java b/src/main/java/com/iflytop/handacid/hardware/type/error/AppError.java
new file mode 100644
index 0000000..92403eb
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/error/AppError.java
@@ -0,0 +1,38 @@
+package com.iflytop.handacid.hardware.type.error;
+
+import cn.hutool.json.JSONUtil;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+import java.io.Serializable;
+
+public class AppError implements Serializable {
+ @Schema(description = "错误码")
+ public A8kEcode code;
+ public String type;
+ @Schema(description = "额外信息")
+ public String exmsg;
+
+ public AppError(A8kEcode errorCode) {
+ this.code = errorCode;
+ this.type = this.getClass().getSimpleName();
+ }
+
+ public AppError(A8kEcode errorCode, String exmsg, Object... args) {
+ this.code = errorCode;
+ this.type = this.getClass().getSimpleName();
+ this.exmsg = String.format(exmsg, args);
+ }
+
+ public String toString() {
+ return JSONUtil.toJsonStr(this);
+ }
+
+ public Boolean eq(A8kEcode... ecode) {
+ for (A8kEcode e : ecode) {
+ if (e == code) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/hardware/type/error/AppErrorCode.java b/src/main/java/com/iflytop/handacid/hardware/type/error/AppErrorCode.java
new file mode 100644
index 0000000..5b27ed2
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/type/error/AppErrorCode.java
@@ -0,0 +1,21 @@
+package com.iflytop.handacid.hardware.type.error;
+
+public enum AppErrorCode {
+ APP_OK(200, "操作成功"),
+ FAIL(500, "操作失败"),
+ PARAM_ERROR(400, "参数错误");
+
+ private int code;
+ private String msg;
+ private AppErrorCode(int code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ public int getCode() {
+ return code;
+ }
+ public String getMsg() {
+ return msg;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/CommonPage.java b/src/main/java/com/iflytop/handacid/hardware/utils/CommonPage.java
new file mode 100644
index 0000000..5839982
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/CommonPage.java
@@ -0,0 +1,25 @@
+package com.iflytop.handacid.hardware.utils;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+public class CommonPage {
+
+ private Integer pageNum; //第几页
+ private Integer pageSize; //一页展示几条数据
+ private Integer totalPage; //总页数
+ private long total; //总条数
+ private List list; //数据
+
+ public CommonPage(Integer pageNum, Integer pageSize, Integer totalPage, long total, List list) {
+ this.pageNum = pageNum;
+ this.pageSize = pageSize;
+ this.totalPage = totalPage;
+ this.total = total;
+ this.list = list;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/Math/ServoPositionConverter.java b/src/main/java/com/iflytop/handacid/hardware/utils/Math/ServoPositionConverter.java
new file mode 100644
index 0000000..a2e0fca
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/Math/ServoPositionConverter.java
@@ -0,0 +1,84 @@
+package com.iflytop.handacid.hardware.utils.Math;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ServoPositionConverter {
+ // 舵机位置相关常量
+ private static final double MIN_SERVO_POSITION = 1920;
+ private static final double MAX_SERVO_POSITION = 3000;
+
+ // 4096量程相关常量
+ private static final double MIN_SERVO_4096_POSITION = 0.0;
+ private static final double MAX_SERVO_4096_POSITION = 4096.0;
+
+ // 3600量程相关常量
+ private static final double MIN_SERVO_3600_POSITION = 0.0;
+ private static final double MAX_SERVO_3600_POSITION = 3600.0;
+
+ /**
+ * 将0-4096范围内的值映射到1920-3000的舵机位置,再映射到0-3600的目标量程
+ * @param value 输入值,范围0-4096
+ * @return 对应的3600量程值
+ */
+ public static double convert4096To3600(double value) {
+ // 限制输入值在有效范围内
+ value = Math.max(MIN_SERVO_4096_POSITION, Math.min(MAX_SERVO_4096_POSITION, value));
+
+ // 第一步:将0-4096的输入值映射到1920-3000的舵机位置
+ double servoPosition = MIN_SERVO_POSITION +
+ (value - MIN_SERVO_4096_POSITION) *
+ (MAX_SERVO_POSITION - MIN_SERVO_POSITION) /
+ (MAX_SERVO_4096_POSITION - MIN_SERVO_4096_POSITION);
+
+ // 第二步:将1920-3000的舵机位置映射到0-3600的目标量程
+ double result = MIN_SERVO_3600_POSITION +
+ (servoPosition - MIN_SERVO_POSITION) *
+ (MAX_SERVO_3600_POSITION - MIN_SERVO_3600_POSITION) /
+ (MAX_SERVO_POSITION - MIN_SERVO_POSITION);
+
+ return result;
+ }
+
+ /**
+ * 将0-3600范围内的值反向映射回0-4096的原始量程
+ * @param value 输入值,范围0-3600
+ * @return 对应的4096量程值
+ */
+ public static double convert3600To4096(double value) {
+ // 限制输入值在有效范围内
+ value = Math.max(MIN_SERVO_3600_POSITION, Math.min(MAX_SERVO_3600_POSITION, value));
+
+ // 第一步:将0-3600的值反向映射到1920-3000的舵机位置
+ double servoPosition = MIN_SERVO_POSITION +
+ (value - MIN_SERVO_3600_POSITION) *
+ (MAX_SERVO_POSITION - MIN_SERVO_POSITION) /
+ (MAX_SERVO_3600_POSITION - MIN_SERVO_3600_POSITION);
+
+ // 第二步:将1920-3000的舵机位置反向映射到0-4096的原始量程
+ double result = MIN_SERVO_4096_POSITION +
+ (servoPosition - MIN_SERVO_POSITION) *
+ (MAX_SERVO_4096_POSITION - MIN_SERVO_4096_POSITION) /
+ (MAX_SERVO_POSITION - MIN_SERVO_POSITION);
+
+ return result;
+ }
+
+ /**
+ * 测试转换功能的示例方法
+ */
+ public static void main(String[] args) {
+ // 测试从4096到3600的转换
+ double value4096 = 2048; // 中间值
+ double value3600 = convert4096To3600(value4096);
+ System.out.printf("4096量程的 %.2f 对应3600量程的 %.2f%n", value4096, value3600);
+
+ // 测试反向转换
+ double reversedValue = convert3600To4096(value3600);
+ System.out.printf("3600量程的 %.2f 反向映射回4096量程的 %.2f%n", value3600, reversedValue);
+
+ // 测试边界值
+ System.out.printf("4096量程的 0 对应3600量程的 %.2f%n", convert4096To3600(0));
+ System.out.printf("4096量程的 4096 对应3600量程的 %.2f%n", convert4096To3600(4096));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/Math/StepMotorConverter.java b/src/main/java/com/iflytop/handacid/hardware/utils/Math/StepMotorConverter.java
new file mode 100644
index 0000000..e551d60
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/Math/StepMotorConverter.java
@@ -0,0 +1,81 @@
+package com.iflytop.handacid.hardware.utils.Math;
+
+/**
+ * @author iflytop
+ * @date 2023/10/12
+ * @description 步进电机助手类
+ * lead[S](导程): mm
+ * rps(转速): r/s
+ * rpm(转速): r/min
+ * r(转数): r
+ *
+ */
+public class StepMotorConverter {
+ static private final double kScale = 100.0;
+
+ /* **** **** **** **** **** **** **** **** **** **** ****
+ * 位置转换
+ * 1. 用户位置 X
+ * 2. 用户单位距离 unit_X
+ * 3. R = X * kScale / motor_scale * motor_lead
+ * 4. unit_X(1mm/1ml) = R / S = X * kScale / motor_scale * motor_lead / S
+ * 5. kScale == 10000.0 && motor_lead == 1
+ * 6. unit_X(1mm/1ml) = X * kScale / motor_scale / S (X=1, 即 X = unit_X, 保证 kScale / motor_scale / S == 1)
+ * 7. K = S / motor_scale
+ *
+ **** **** **** **** **** **** **** **** **** **** **** */
+
+ /**
+ * 用户设置位置转换为控制器需要的位置
+ * @param position
+ * @return
+ */
+ public static int toMotorPosition(double position)
+ {
+ return (int)(position * kScale);
+ }
+
+ /**
+ * 控制器位置转换为用户设置位置
+ * @param motorPosition
+ * @return
+ */
+ public static double toUserPosition(int motorPosition)
+ {
+ return (double)motorPosition / kScale;
+ }
+
+ /* **** **** **** **** **** **** **** **** **** **** ****
+ * 速度转换
+ * 1. 用户速度 X/s
+ * 2. 电机速度 R/min
+ * 3. X/s = rps * S
+ * 4. rps = X/s / S
+ * 5. rps = rpm / 60.0
+ * 6. rpm = X/s * 60.0
+ * 7. rpm = X/s / S * 60.0
+ *
+ **** **** **** **** **** **** **** **** **** **** **** */
+
+ /**
+ * 用户设置速度转换为控制器需要的速度
+ * 用户速度 U_Speed xx/s
+ * 控制器速度 rpm
+ * S 导程
+ */
+ public static int toMotorSpeed(double speed, double S)
+ {
+ return (int)speed;
+ }
+
+ /**
+ * 控制器速度转换为用户设置速度
+ * 控制器速度 rpm
+ * 用户速度 U_Speed xx/s
+ * S 导程
+ */
+ public static double toUserSpeed(int motorSpeed, double S)
+ {
+ return (double) motorSpeed;
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/OS.java b/src/main/java/com/iflytop/handacid/hardware/utils/OS.java
new file mode 100644
index 0000000..1b8988d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/OS.java
@@ -0,0 +1,24 @@
+package com.iflytop.handacid.hardware.utils;
+
+public class OS {
+
+ public static void hsleep(int ms) {
+ forceSleep(ms);
+ }
+
+ public static void forceSleep(Integer mills) {
+ Long start = System.currentTimeMillis();
+ long end = start + mills;
+ while (System.currentTimeMillis() < end) {
+ int left = (int) (end - System.currentTimeMillis());
+ threadSleep(left);
+ }
+ }
+
+ public static void threadSleep(Integer mills) {
+ try {
+ Thread.sleep(mills);
+ } catch (InterruptedException ignored) {
+ }
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/ZCSVUtils.java b/src/main/java/com/iflytop/handacid/hardware/utils/ZCSVUtils.java
new file mode 100644
index 0000000..4f6a591
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/ZCSVUtils.java
@@ -0,0 +1,78 @@
+package com.iflytop.handacid.hardware.utils;
+
+
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.text.csv.CsvReader;
+import cn.hutool.core.text.csv.CsvUtil;
+import cn.hutool.core.text.csv.CsvWriter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+public class ZCSVUtils {
+
+
+ public static void writeCSV(String filePath, Class type, List objects) {
+ File file = new File(filePath);
+ try (
+ CsvWriter csvWriter = CsvUtil.getWriter(file.getAbsoluteFile(), StandardCharsets.UTF_8);
+ ) {
+
+ List headers = new ArrayList<>();
+ var fields = type.getDeclaredFields();
+ for (var field : fields) {
+ headers.add(field.getName());
+ }
+
+ String[] headersArray = new String[headers.size()];
+ headers.toArray(headersArray);
+
+
+ csvWriter.write(headersArray);
+
+ for (var object : objects) {
+ String[] row = new String[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ fields[i].setAccessible(true);
+ row[i] = String.format("%s", fields[i].get(object));
+ }
+
+
+ // log.info("row: {}", row);
+ csvWriter.write(row);
+ }
+ csvWriter.flush();
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static List readCSV(String filePath, Class clazz) {
+ File file = new File(filePath);
+ final CsvReader reader = CsvUtil.getReader();
+ String csvContent = ResourceUtil.readUtf8Str(file.getAbsolutePath());
+ return reader.read(csvContent, clazz);
+ }
+
+ public static List readCSVContent(String csvContent, Class clazz) {
+ final CsvReader reader = CsvUtil.getReader();
+ return reader.read(csvContent, clazz);
+ }
+
+ public static List readCSVFromResource(String filePath, Class clazz) {
+ String csvContent = ResourceUtil.readUtf8Str(filePath);
+ final CsvReader reader = CsvUtil.getReader();
+ return reader.read(csvContent, clazz);
+ }
+
+ public static Boolean isResourceExist(String filePath) {
+ var resource = ResourceUtil.getResource(filePath);
+ return resource != null;
+ }
+
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/ZJsonHelper.java b/src/main/java/com/iflytop/handacid/hardware/utils/ZJsonHelper.java
new file mode 100644
index 0000000..d1c54bd
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/ZJsonHelper.java
@@ -0,0 +1,122 @@
+package com.iflytop.handacid.hardware.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public class ZJsonHelper {
+ public static String objectToJson(Object obj) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+
+ return mapper.writeValueAsString(obj);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String objToPrettyJson(Object obj) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ObjectNode createObjectNode() {
+ return new ObjectMapper().createObjectNode();
+ }
+
+ public static ObjectNode readTree(String node) {
+ ObjectMapper mapper = new ObjectMapper();
+ if(node == null) {
+ return null;
+ }
+ if(node.isEmpty()) {
+ return null;
+ }
+ try {
+ return (ObjectNode) mapper.readTree(node);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ public static T objectFromJson(String node, Class tClass) {
+ return objectFromJson(readTree(node), tClass);
+ }
+
+
+ public static T objectFromJson(ObjectNode node, Class tClass) {
+ ObjectMapper mapper = new ObjectMapper();
+ if (node == null) {
+ return null;
+ }
+
+
+ if (tClass.getTypeName().equals(Integer.class.getTypeName())) {
+ if (node.get("value") == null)
+ return null;
+ return (T) Integer.valueOf(node.get("value").asInt());
+ }
+ if (tClass.getTypeName().equals(String.class.getTypeName())) {
+ if (node.get("value") == null)
+ return null;
+ return (T) node.get("value").asText();
+ }
+ if (tClass.getTypeName().equals(Boolean.class.getTypeName())) {
+ if (node.get("value") == null)
+ return null;
+ return (T) Boolean.valueOf(node.get("value").asBoolean());
+ }
+ if (tClass.getTypeName().equals(Double.class.getTypeName())) {
+ if (node.get("value") == null)
+ return null;
+ return (T) Double.valueOf(node.get("value").asDouble());
+ }
+ if (tClass.getTypeName().equals(Float.class.getTypeName())) {
+ if (node.get("value") == null)
+ return null;
+ return (T) Float.valueOf(node.get("value").floatValue());
+ }
+
+ if (tClass.isEnum()) {
+ if (node.get("value") == null)
+ return null;
+ return (T) Enum.valueOf((Class) tClass, node.get("value").asText());
+ }
+
+
+ try {
+ return (T) mapper.treeToValue(node, tClass);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static ObjectNode createObjectNode(Object obj) {
+ ObjectMapper ObjectMapper = new ObjectMapper();
+ if (obj == null) {
+ return null;
+ }
+ if (obj instanceof Integer) {
+ return ObjectMapper.createObjectNode().put("value", (Integer) obj);
+ }
+ if (obj instanceof String) {
+ return ObjectMapper.createObjectNode().put("value", (String) obj);
+ }
+ if (obj instanceof Boolean) {
+ return ObjectMapper.createObjectNode().put("value", (Boolean) obj);
+ }
+ if (obj instanceof Double) {
+ return ObjectMapper.createObjectNode().put("value", (Double) obj);
+ }
+ if (obj instanceof Float) {
+ return ObjectMapper.createObjectNode().put("value", (Float) obj);
+ }
+ if (obj instanceof Enum) {
+ return ObjectMapper.createObjectNode().put("value", obj.toString());
+ }
+ return ObjectMapper.valueToTree(obj);
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/ZList.java b/src/main/java/com/iflytop/handacid/hardware/utils/ZList.java
new file mode 100644
index 0000000..8f732df
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/ZList.java
@@ -0,0 +1,11 @@
+package com.iflytop.handacid.hardware.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class ZList {
+ //List.of 方法不支持 removeIf, 代码中尽量全部使用 ZList.of
+ public static ArrayList of(T... args) {
+ return new ArrayList<>(Arrays.asList(args));
+ }
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/ZSqlite.java b/src/main/java/com/iflytop/handacid/hardware/utils/ZSqlite.java
new file mode 100644
index 0000000..6975a2d
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/ZSqlite.java
@@ -0,0 +1,209 @@
+package com.iflytop.handacid.hardware.utils;
+
+import com.iflytop.handacid.hardware.constants.FilePathConstant;
+import jakarta.annotation.Nullable;
+import lombok.SneakyThrows;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.sql.ResultSet;
+import java.util.List;
+
+public class ZSqlite {
+ private static final Logger log = LoggerFactory.getLogger(ZSqlite.class);
+ JdbcTemplate jdbcTemplate;
+ Class tClass;
+ public String tableName;
+
+ public ZSqlite() {
+ }
+
+ public void init(JdbcTemplate jdbcTemplate, String tableName, Class tClass, boolean foreceCreate) {
+ this.tableName = tableName;
+ this.tClass = tClass;
+ this.jdbcTemplate = jdbcTemplate;
+ if (foreceCreate) {
+ this.forceDeleteTable();
+ }
+
+ if (!this.isTableExist()) {
+ this.createTable();
+ tryInitDBData();
+ }
+ }
+
+ public void init(JdbcTemplate jdbcTemplate, String tableName, Class tClass) {
+ init(jdbcTemplate, tableName, tClass, false);
+ }
+
+
+ public List getAll() {
+ return jdbcTemplate.query("select * from " + tableName, this::rowMapperList);
+ }
+
+ public List getAllDesc() {
+ return jdbcTemplate.query("select * from " + tableName + " order by id desc", this::rowMapperList);
+ }
+
+ public CommonPage getPageDesc(Integer pageNum, Integer pageSize) {
+ //倒叙
+ String sql = "select * from " + tableName + " order by id desc";
+ return queryPage(pageNum, pageSize, sql);
+ }
+
+
+ public CommonPage queryPage(Integer pageNum, Integer pageSize, String sql, @Nullable Object... args) {
+ sql = sql + " limit " + (pageNum - 1) * pageSize + "," + pageSize;
+ List list = jdbcTemplate.query(sql, this::rowMapperList, args);
+ Integer total = jdbcTemplate.queryForObject("select count(*) from " + tableName, Integer.class);
+ if (total == null) {
+ total = 0;
+ }
+ return new CommonPage<>(pageNum, pageSize, total / pageSize + 1, total, list);
+ }
+
+ public List getPage(Integer pageNum, Integer pageSize) {
+ //倒叙
+ String sql = "select * from " + tableName + " limit " + (pageNum - 1) * pageSize + "," + pageSize;
+ List list = jdbcTemplate.query(sql, this::rowMapperList);
+ //总数
+ Integer total = jdbcTemplate.queryForObject("select count(*) from " + tableName, Integer.class);
+ if (total == null) {
+ total = 0;
+ }
+ return list;
+ }
+
+ public T findById(int id) {
+ List list = jdbcTemplate.query("select * from " + tableName + " where id = ?", this::rowMapperList, id);
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+
+ @SneakyThrows public T rowMapperList(ResultSet rs, int rowNum) {
+ var ret = ZSqliteJdbcHelper.rowMapper(rs, tClass);
+ return tClass.cast(ret);
+ }
+
+ // @SneakyThrows public T rowMapperOne(ResultSet rs) {
+ // // return (T) ZSqliteJdbcHelper.rowMapper(rs, tClass);
+ // return tClass.cast(ZSqliteJdbcHelper.rowMapper(rs, tClass));
+ // }
+
+ public Boolean isTableExist() {
+ return ZSqliteJdbcHelper.isTableExist(jdbcTemplate, tableName);
+ }
+
+ public void deleteTable() {
+ ZSqliteJdbcHelper.deleteTable(jdbcTemplate, tableName);
+ }
+
+ public void forceDeleteTable() {
+ ZSqliteJdbcHelper.forceDeleteTable(jdbcTemplate, tableName);
+ }
+
+ public void createTable() {
+ ZSqliteJdbcHelper.createTable(jdbcTemplate, tableName, tClass);
+ }
+
+ public void delete(int id) {
+ ZSqliteJdbcHelper.delete(jdbcTemplate, tableName, id);
+ }
+
+ public void add(T obj) {
+ if (obj == null) {
+ return;
+ }
+ ZSqliteJdbcHelper.addObj(jdbcTemplate, tableName, tClass, obj);
+ }
+
+ public void addAll(List list) {
+ for (var val : list) {
+ add(val);
+ }
+ }
+
+ public void update(T obj) {
+ ZSqliteJdbcHelper.updateObj(jdbcTemplate, tableName, tClass, obj);
+ }
+
+ public void deleteAll() {
+ ZSqliteJdbcHelper.deleteTable(jdbcTemplate, tableName);
+ ZSqliteJdbcHelper.createTable(jdbcTemplate, tableName, tClass);
+ }
+
+ public T queryOne(String sql, @Nullable Object... args) {
+ List list = jdbcTemplate.query(sql, this::rowMapperList, args);
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+ public List queryList(String sql, @Nullable Object... args) {
+ return jdbcTemplate.query(sql, this::rowMapperList, args);
+ }
+
+ public void update(String sql, @Nullable Object... args) {
+ jdbcTemplate.update(sql, args);
+ }
+
+
+ public void export() {
+ ZCSVUtils.writeCSV(getExportPATH(), tClass, getAll());
+ }
+
+ /**
+ * 从CSV文件导入数据到数据库 !WARNING: 会删除原有数据
+ * @param csvcontent CSV文件内容
+ */
+ public void importFromCSV(String csvcontent, boolean forceDelete) {
+ if (forceDelete) {
+ deleteAll();
+ List list = ZCSVUtils.readCSV(csvcontent, tClass);
+ list.forEach(this::add);
+ } else {
+ List list = ZCSVUtils.readCSV(csvcontent, tClass);
+ for (var val : list) {
+ var obj = tClass.cast(val);
+ try {
+ var id = tClass.getField("id");
+ id.setAccessible(true);
+ int idVal = (int) id.get(obj);
+
+ if (idVal == 0) {
+ add(obj);
+ } else {
+ update(obj);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+
+ public String getExportPATH() {
+ return String.format("%s/%s.csv", FilePathConstant.FILE_DB_EXPORT_PATH, tableName);
+ }
+
+
+ public void resetDBData() {
+ deleteAll();
+ tryInitDBData();
+ }
+
+ private void tryInitDBData() {
+ String csvPath = String.format("db/%s.csv", tableName);
+ if (ZCSVUtils.isResourceExist(csvPath)) {
+ var list = ZCSVUtils.readCSVFromResource(String.format("db/%s.csv", tableName), tClass);
+ addAll(list);
+ }
+ }
+
+}
diff --git a/src/main/java/com/iflytop/handacid/hardware/utils/ZSqliteJdbcHelper.java b/src/main/java/com/iflytop/handacid/hardware/utils/ZSqliteJdbcHelper.java
new file mode 100644
index 0000000..4a95ec7
--- /dev/null
+++ b/src/main/java/com/iflytop/handacid/hardware/utils/ZSqliteJdbcHelper.java
@@ -0,0 +1,284 @@
+package com.iflytop.handacid.hardware.utils;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.slf4j.Logger;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class ZSqliteJdbcHelper {
+ static Logger logger = org.slf4j.LoggerFactory.getLogger(ZSqliteJdbcHelper.class);
+
+ static public boolean isTableExist(JdbcTemplate jdbcTemplate, String tableName) {
+ String sql = "select * from sqlite_master where type = 'table' and name = '" + tableName + "';";
+ List result = jdbcTemplate.query(sql, (ResultSet rs, int rowNum) -> {
+ return rs.toString();
+ });
+ return !result.isEmpty();
+ }
+
+ static public void deleteTable(JdbcTemplate jdbcTemplate, String tableName) {
+ String sql = "drop table " + tableName + ";";
+ jdbcTemplate.execute(sql);
+ }
+
+ static public void forceDeleteTable(JdbcTemplate jdbcTemplate, String tableName) {
+ if (isTableExist(jdbcTemplate, tableName)) {
+ deleteTable(jdbcTemplate, tableName);
+ }
+ }
+
+ static public void createTable(JdbcTemplate jdbcTemplate, String tableName, Class> tClass) {
+ StringBuilder sql = new StringBuilder("create table " + tableName + " (");
+ Boolean hasId = false;
+ for (java.lang.reflect.Field field : tClass.getDeclaredFields()) {
+ if (field.getName().equals("id")) {
+ hasId = true;
+ }
+ sql.append(" '").append(field.getName()).append("' ");
+ if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) {
+ sql.append("integer,");
+ } else if (field.getType().equals(Double.class) || field.getType().equals(Float.class)) {
+ sql.append("real,");
+ } else if (field.getType().equals(Boolean.class)) {
+ sql.append("integer,");
+ } else if (field.getType().equals(String.class)) {
+ sql.append("text,");
+ } else if (field.getType().equals(Date.class)) {
+ sql.append("text,");
+ } else if (field.getType().isEnum()) {
+ sql.append("text,");
+ } else {
+ sql.append("text,");
+ }
+ }
+ if (!hasId) {
+ throw new RuntimeException("id field not found in class " + tClass.getName());
+ }
+
+ sql.append(" PRIMARY KEY ('id' DESC));");
+ jdbcTemplate.execute(sql.toString());
+ }
+
+ //从数据库中读取数据
+ static public Object rowMapper(ResultSet rs, Class> tClass) throws SQLException, InstantiationException, IllegalAccessException {
+ Object obj = tClass.newInstance();
+ for (java.lang.reflect.Field field : tClass.getDeclaredFields()) {
+
+ if (!field.getName().equals("id") && rs.getObject(field.getName()) == null) {
+ field.set(obj, null);
+ continue;
+ }
+
+ if (field.getType().equals(Integer.class)) {
+
+ field.set(obj, rs.getInt(field.getName()));
+ } else if (field.getType().equals(int.class)) {
+ field.set(obj, rs.getInt(field.getName()));
+ } else if (field.getType().equals(Float.class)) {
+ field.set(obj, rs.getFloat(field.getName()));
+ } else if (field.getType().equals(Double.class)) {
+ field.set(obj, rs.getDouble(field.getName()));
+ } else if (field.getType().equals(Boolean.class)) {
+ field.set(obj, rs.getBoolean(field.getName()));
+ } else if (field.getType().equals(String.class)) {
+ field.set(obj, rs.getString(field.getName()));
+ } else if (field.getType().equals(Date.class)) {
+ if (rs.getString(field.getName()) == null || rs.getString(field.getName()).isEmpty())
+ field.set(obj, null);
+ else
+ field.set(obj, new Date(rs.getLong(field.getName())));
+ } else if (field.getType().equals(ObjectNode.class)) {
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ String val = rs.getString(field.getName());
+ if (val != null && !val.isEmpty()) {
+ // logger.info("val: {}", val);
+ field.set(obj, objectMapper.readTree(val));
+ } else {
+ field.set(obj, null);
+ }
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ } else if (field.getType().isEnum()) {
+ Method methodValueOf = null;
+ try {
+ methodValueOf = field.getType().getMethod("valueOf", String.class);
+ if (rs.getString(field.getName()) == null || rs.getString(field.getName()).isEmpty())
+ field.set(obj, null);
+ else {
+ field.set(obj, methodValueOf.invoke(null, rs.getString(field.getName())));
+ }
+ } catch (NoSuchMethodException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ try {
+ ObjectMapper objectMapper = new ObjectMapper();
+ String json = rs.getString(field.getName());
+ if (json == null || json.isEmpty()) {
+ field.set(obj, null);
+ } else {
+
+ if (field.getType().equals(List.class)) {
+ Type type = field.getGenericType();
+ if (type instanceof ParameterizedType parameterizedType) {
+ Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+ if (actualTypeArguments.length == 1) {
+ Class> clazz = (Class>) actualTypeArguments[0];
+ field.set(obj, objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz)));
+ }
+ }
+ } else {
+ field.set(obj, objectMapper.readValue(json, field.getType()));
+ }
+
+
+ }
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return obj;
+ }
+
+ //存储数据到数据库
+ static public List