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 buildArgs(Class tClass, Object obj) { + // Object[] args = new Object[filedCount]; + List args = new ArrayList<>(); + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + try { + if (field.getName().equals("id")) + continue; + + if (field.get(obj) == null) { + args.add(null); + continue; + } + + field.setAccessible(true); + if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) { + // args[i] = field.getInt(obj); + args.add(field.get(obj)); + } else if (field.getType().equals(Double.class) || field.getType().equals(Float.class)) { + // args.add( field.getDouble(obj)); + args.add(field.get(obj)); + } else if (field.getType().equals(Boolean.class)) { + Boolean value = (Boolean) field.get(obj); + args.add(value ? 1 : 0); + } else if (field.getType().equals(String.class)) { + args.add(field.get(obj)); + } else if (field.getType().equals(Date.class)) { + Date date = (Date) field.get(obj); + // 按照 yyyy-MM-dd HH:mm:ss 存储 + // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + if (date == null) + args.add(""); + else + args.add(date.getTime()); + } else if (field.getType().isEnum()) { + args.add(field.get(obj).toString()); + } else if (field.getType().equals(ObjectNode.class)) { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); + objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE); + args.add(objectMapper.writeValueAsString(field.get(obj))); + } else { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE); + objectMapper.setVisibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE); + args.add(objectMapper.writeValueAsString(field.get(obj))); + } + } catch (IllegalAccessException | JsonProcessingException e) { + logger.error("", e); + args.add(null); + } + } + return args; + } + + public static void delete(JdbcTemplate jdbcTemplate, String tableName, int id) { + jdbcTemplate.update("delete from " + tableName + " where id = ?", id); + } + + public static void addObj(JdbcTemplate jdbcTemplate, String tableName, Class tClass, Object obj) { + StringBuilder sql = new StringBuilder("insert into " + tableName + "("); + StringBuilder values = new StringBuilder(" values("); + + int idval = 0; + + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + if (field.getName().equals("id")) { + if (field.getType().equals(Integer.class) || field.getType().equals(int.class)) { + field.setAccessible(true); + try { + idval = field.getInt(obj); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + continue; + } + sql.append(field.getName()).append(","); + values.append("?,"); + } + if (idval != 0) { + sql.append("id,"); + values.append("?,"); + } + + sql.deleteCharAt(sql.length() - 1).append(")"); + values.deleteCharAt(values.length() - 1).append(")"); + sql.append(values); + + var args = buildArgs(tClass, obj); + if (idval != 0) + args.add(idval); + + jdbcTemplate.update(sql.toString(), args.toArray()); + } + + public static void updateObj(JdbcTemplate jdbcTemplate, String tableName, Class tClass, Object obj) { + StringBuilder sql = new StringBuilder("update " + tableName + " set "); + int id = 0; + + for (java.lang.reflect.Field field : tClass.getDeclaredFields()) { + if (field.getName().equals("id")) { + field.setAccessible(true); + try { + id = field.getInt(obj); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + continue; + } + sql.append(field.getName()).append(" = ?,"); + } + sql.deleteCharAt(sql.length() - 1).append(" where id = ?"); + List args = buildArgs(tClass, obj); + args.add(id); + + // System.out.println(sql); + // System.out.println(args); + + jdbcTemplate.update(sql.toString(), args.toArray()); + } + + +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..789841b --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,33 @@ +server: + servlet: + context-path: / + encoding: + charset: utf-8 + port: 8080 + +#开启 SQL 打印,调试时方便查看 +logging: + level: + root: INFO + org.mybatis: DEBUG + +device.enableCanBus: true + +iflytophald: + ip: 127.0.0.1 + cmdch.port: 19004 + datach.port: 19005 + +#window config +#modbus: +# port: COM18 +# baudrate: 9600 + +#工控机 config +modbus: + port: ttyS1 + baudrate: 9600 + +photo: + url: http://192.168.8.168/static/photo + path: /home/firefly/package/photo \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..c04b527 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,25 @@ +server: + servlet: + context-path: / + port: 8080 + +device.enableCanBus: true + +iflytophald: + ip: 127.0.0.1 + cmdch.port: 19004 + datach.port: 19005 + +#window config +#modbus: +# port: COM18 +# baudrate: 9600 + +#工控机 config +modbus: + port: ttyS1 + baudrate: 9600 + +photo: + url: http://192.168.8.168/static/photo + path: /home/firefly/package/photo \ No newline at end of file diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..75f28ab --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,31 @@ +server: + servlet: + context-path: / + port: 8080 + +#开启 SQL 打印,调试时方便查看 +logging: + level: + root: INFO + org.mybatis: DEBUG + +device.enableCanBus: true + +iflytophald: + ip: 127.0.0.1 + cmdch.port: 19004 + datach.port: 19005 + +#window config +#modbus: +# port: COM18 +# baudrate: 9600 + +#工控机 config +modbus: + port: ttyS1 + baudrate: 9600 + +photo: + url: http://192.168.8.168/static/photo + path: /home/firefly/package/photo \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..300c0ed --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,21 @@ +spring: + profiles: + active: dev + application: + name: hand-acid + datasource: + url: jdbc:sqlite:db/app.db + driver-class-name: org.sqlite.JDBC + sql: + init: + #always embedded never + mode: always + schema-locations: classpath:/sql/init.sql + +mybatis-plus: + configuration: + # 如果需要加载 XML 文件(自定义 SQL),可配置 mapper-locations: + mapper-locations: classpath*:mapper/*.xml + +springdoc: + default-flat-param-object: true diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..0e6c853 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,58 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + ${LOG_PATH}/sys-info.log + + ${LOG_PATH}/history/info/sys-info.%d{yyyy-MM-dd}.%i.log.zip + 20MB + 7 + 200MB + + + ${LOG_PATTERN} + + + INFO + + + + + + ${LOG_PATH}/sys-error.log + + ${LOG_PATH}/history/error/sys-error.%d{yyyy-MM-dd}.%i.log.zip + 20MB + 7 + 200MB + + + ${LOG_PATTERN} + + + ERROR + + + + + + + + + + + + + + diff --git a/src/main/resources/sql/init.sql b/src/main/resources/sql/init.sql new file mode 100644 index 0000000..2f531c1 --- /dev/null +++ b/src/main/resources/sql/init.sql @@ -0,0 +1,196 @@ +/* + Navicat Premium Dump SQL + + Source Server : app + Source Server Type : SQLite + Source Server Version : 3045000 (3.45.0) + Source Schema : main + + Target Server Type : SQLite + Target Server Version : 3045000 (3.45.0) + File Encoding : 65001 + + Date: 28/07/2025 20:52:42 +*/ + +PRAGMA foreign_keys = false; + +-- ---------------------------- +-- Table structure for audit_record +-- ---------------------------- +DROP TABLE IF EXISTS "audit_record"; +CREATE TABLE "audit_record" ( + "id" INTEGER NOT NULL, + "user_id" INTEGER, + "user_name" TEXT, + "solution_id" INTEGER, + "solution_name" TEXT, + "channel_id" integer, + "volume" TEXT, + "create_time" DATE, + "update_time" DATE, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Records of audit_record +-- ---------------------------- + +-- ---------------------------- +-- Table structure for channel +-- ---------------------------- +DROP TABLE IF EXISTS "channel"; +CREATE TABLE "channel" ( + "id" INTEGER NOT NULL, + "name" TEXT, + "solution_id" INTEGER, + "create_time" DATE, + "update_time" DATE, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Records of channel +-- ---------------------------- + +-- ---------------------------- +-- Table structure for formulation +-- ---------------------------- +DROP TABLE IF EXISTS "formulation"; +CREATE TABLE "formulation" ( + "id" INTEGER NOT NULL, + "name" TEXT, + "solution_id" INTEGER, + "solution_name" TEXT, + "concentration" TEXT, + "scale" TEXT, + "revolutions" TEXT, + "create_time" DATE, + "update_time" DATE, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Records of formulation +-- ---------------------------- + +-- ---------------------------- +-- Table structure for receive_record +-- ---------------------------- +DROP TABLE IF EXISTS "receive_record"; +CREATE TABLE "receive_record" ( + "id" INTEGER NOT NULL, + "user_id" INTEGER, + "user_name" TEXT, + "solution_id" INTEGER, + "solution_name" TEXT, + "channel_id" integer, + "volume" TEXT, + "create_time" DATE, + "update_time" DATE, + PRIMARY KEY ("id") +); + +-- ---------------------------- +-- Records of receive_record +-- ---------------------------- + +-- ---------------------------- +-- Table structure for solution +-- ---------------------------- +DROP TABLE IF EXISTS "solution"; +CREATE TABLE "solution" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "name" TEXT, + "create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "update_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ---------------------------- +-- Records of solution +-- ---------------------------- +INSERT INTO "solution" VALUES (1, '乙酸-乙酸铵', '2025-07-26T18:47:18.611194300', '2025-07-26T18:47:18.612194300'); +INSERT INTO "solution" VALUES (2, '硫代硫酸钠', '2025-07-26T18:47:36.173404600', '2025-07-26T18:47:36.173404600'); + +-- ---------------------------- +-- Table structure for system_config +-- ---------------------------- +DROP TABLE IF EXISTS "system_config"; +CREATE TABLE "system_config" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "key" TEXT, + "value" TEXT, + "create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "update_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ---------------------------- +-- Records of system_config +-- ---------------------------- + +-- ---------------------------- +-- Table structure for system_log +-- ---------------------------- +DROP TABLE IF EXISTS "system_log"; +CREATE TABLE "system_log" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "title" TEXT, + "content" TEXT, + "create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "update_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ---------------------------- +-- Records of system_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for user +-- ---------------------------- +DROP TABLE IF EXISTS "user"; +CREATE TABLE "user" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "username" TEXT, + "nickname" TEXT, + "password" TEXT, + "role" TEXT, + "fixed_user" TEXT, + "deleted" TEXT, + "create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + "update_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE ("username" ASC) +); + +-- ---------------------------- +-- Records of user +-- ---------------------------- +INSERT INTO "user" VALUES (1, 'admin', 'Admin', '9973', 'ADMIN', 'ENABLE', 'DISABLE', '2025-07-26 06:46:04', '2025-07-26 06:46:04'); +INSERT INTO "user" VALUES (2, 'test', 'test', '9973', 'ADMIN', 'ENABLE', 'DISABLE', '2025-07-26 06:46:04', '2025-07-26 06:46:04'); + +-- ---------------------------- +-- Table structure for zapp_sub_module_reg_initial_value +-- ---------------------------- +DROP TABLE IF EXISTS "zapp_sub_module_reg_initial_value"; +CREATE TABLE "zapp_sub_module_reg_initial_value" ( + "id" integer, + "mid" text, + "regIndex" text, + "regInitVal" integer, + PRIMARY KEY ("id" DESC) +); + +-- ---------------------------- +-- Records of zapp_sub_module_reg_initial_value +-- ---------------------------- + +-- ---------------------------- +-- Auto increment value for solution +-- ---------------------------- +UPDATE "sqlite_sequence" SET seq = 2 WHERE name = 'solution'; + +-- ---------------------------- +-- Auto increment value for user +-- ---------------------------- +UPDATE "sqlite_sequence" SET seq = 38 WHERE name = 'user'; + +PRAGMA foreign_keys = true; diff --git a/src/main/resources/static/1.png b/src/main/resources/static/1.png new file mode 100644 index 0000000..08d0cd2 Binary files /dev/null and b/src/main/resources/static/1.png differ diff --git a/src/main/resources/static/2.png b/src/main/resources/static/2.png new file mode 100644 index 0000000..64eace8 Binary files /dev/null and b/src/main/resources/static/2.png differ diff --git a/src/main/resources/static/3.png b/src/main/resources/static/3.png new file mode 100644 index 0000000..03d4905 Binary files /dev/null and b/src/main/resources/static/3.png differ diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico new file mode 100644 index 0000000..cd20d77 Binary files /dev/null and b/src/main/resources/static/favicon.ico differ diff --git a/src/test/java/com/iflytop/handacid/ColorTitrationApplicationTests.java b/src/test/java/com/iflytop/handacid/ColorTitrationApplicationTests.java new file mode 100644 index 0000000..3df3bd0 --- /dev/null +++ b/src/test/java/com/iflytop/handacid/ColorTitrationApplicationTests.java @@ -0,0 +1,13 @@ +package com.iflytop.handacid; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class HandAcidApplicationTests { + + @Test + void contextLoads() { + // 这个测试验证Spring上下文能正常加载 + } +}