d4e219b7bd76a5ab3ece1446b7f88fe3fd0304d8..3b1b756f1a619e60b2243d28817c39e0a7a0cb0c
2026-01-21 senzhi
fix:修改项目为组件
3b1b75 diff | tree
2026-01-21 senzhi
fix:修改项目为组件
1abc7e diff | tree
2026-01-21 senzhi
fix:修改项目名称
93d053 diff | tree
2026-01-21 senzhi
Initial commit
9e1ac6 diff | tree
1 files modified
13 files added
1071 ■■■■■ changed files
.gitattributes 3 ●●●●● patch | view | raw | blame | history
.gitignore 43 ●●●● patch | view | raw | blame | history
build.gradle 88 ●●●●● patch | view | raw | blame | history
doc/开发文档.doc patch | view | raw | blame | history
gradle/wrapper/gradle-wrapper.jar patch | view | raw | blame | history
gradle/wrapper/gradle-wrapper.properties 7 ●●●●● patch | view | raw | blame | history
gradlew 248 ●●●●● patch | view | raw | blame | history
gradlew.bat 93 ●●●●● patch | view | raw | blame | history
lib/CReaderV2.1.jar patch | view | raw | blame | history
settings.gradle 1 ●●●● patch | view | raw | blame | history
src/main/java/com/eternal/rfid/RfidReaderServer.java 251 ●●●●● patch | view | raw | blame | history
src/main/java/com/eternal/rfid/params/UF3ReaderConfig.java 42 ●●●●● patch | view | raw | blame | history
src/main/java/com/eternal/rfid/service/UF3RFIDReader.java 282 ●●●●● patch | view | raw | blame | history
src/test/java/com/reader/uf3readerdemo/Uf3ReaderDemoApplicationTests.java 13 ●●●●● patch | view | raw | blame | history
.gitattributes
New file
@@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
.gitignore
@@ -1,12 +1,37 @@
*.class
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
# Mobile Tools for Java (J2ME)
.mtj.tmp/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
# Package Files #
*.jar
*.war
*.ear
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
build.gradle
New file
@@ -0,0 +1,88 @@
plugins {
    id 'java'
    id 'maven-publish' // or 'maven'
    id 'org.springframework.boot' version '2.7.18'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
repositories {
    maven {
        url 'http://1.95.69.137:9081/repository/maven-public/'
        allowInsecureProtocol = true
    }
    mavenLocal()
    maven { url 'https://maven.aliyun.com/repository/public' }
    maven { url 'https://maven.aliyun.com/repository/center' }
    mavenCentral()
}
bootJar.enabled = true
jar.enabled = true
group = 'com.eternal'
version = '1.0.0'
java {
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}
dependencies {
    implementation fileTree(dir: '/lib', include: ['*.jar'])
    implementation 'com.eternal:com.eternal.rfid.Common:2.1.20'
    implementation 'com.eternal:app-params-config:1.1.16'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'com.alibaba.fastjson2:fastjson2:2.0.46'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testCompileOnly('org.projectlombok:lombok')
    testAnnotationProcessor('org.projectlombok:lombok')
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
// 编译相关参数
compileJava {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
    options.encoding = 'UTF-8'
    options.compilerArgs = [
            '-Xlint:all', '-Xlint:-processing'
    ]
}
// 定义 sourcesJar 任务
task sourcesJar(type: Jar, dependsOn: classes) {
    archiveClassifier.set('sources')
    from sourceSets.main.allSource
}
publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            // 如果需要发布源码 JAR 文件
            artifact sourcesJar
        }
    }
    repositories {
        maven {
            name = 'localRepo'
            url = "file://${buildDir}/repo"
        }
        maven {
            name = 'releases'
            allowInsecureProtocol = true  // 显式允许不安全的 HTTP 协议
            url = 'http://1.95.69.137:9081/repository/maven-releases/'
            credentials {
                username = "admin"
                password = "admin123"
            }
        }
    }
}
tasks.named('test') {
    useJUnitPlatform()
}
doc/开发文档.doc
Binary files differ
gradle/wrapper/gradle-wrapper.jar
Binary files differ
gradle/wrapper/gradle-wrapper.properties
New file
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
gradlew
New file
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 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
# 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" )
    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" \
        -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" "$@"
gradlew.bat
New file
@@ -0,0 +1,93 @@
@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
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -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
lib/CReaderV2.1.jar
Binary files differ
settings.gradle
New file
@@ -0,0 +1 @@
rootProject.name = 'com.eternal.rfid.equip.UF3Reader'
src/main/java/com/eternal/rfid/RfidReaderServer.java
New file
@@ -0,0 +1,251 @@
package com.eternal.rfid;
import com.eternal.event.DeviceWorkEvent;
import com.eternal.monitor.AbstractEquipmentManager;
import com.eternal.monitor.constant.ErrorTypeEnums;
import com.eternal.monitor.constant.StatusTypeEnums;
import com.eternal.monitor.entity.Device;
import com.eternal.monitor.entity.DeviceErrorType;
import com.eternal.monitor.entity.DeviceStatus;
import com.eternal.monitor.entity.DeviceType;
import com.eternal.rfid.params.UF3ReaderConfig;
import com.eternal.rfid.service.UF3RFIDReader;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * description: 略
 *
 * @author senzhi
 * @since 2026/1/19 17:14
 */
@Slf4j
@Service("UF3RFIDReader")
public class RfidReaderServer extends AbstractEquipmentManager {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    private UF3ReaderConfig readerConfig;
    @Getter
    private final List<UF3RFIDReader> readers = new ArrayList<>();
    /**
     * 是否允许读写器工作
     */
    private boolean enableWork = false;
    public void setEnableWork(boolean enableWork) {
        log.info("RfidReaderServer work mode changed. from {} --> {}", this.enableWork, enableWork);
        this.enableWork = enableWork;
    }
    @EventListener(DeviceWorkEvent.class)
    public void onDeviceWorkEvent(DeviceWorkEvent event) {
        setEnableWork(event.isEnableWork());
    }
    private final static int maxTryTime = 3;
    private final AtomicInteger currentRunIndex = new AtomicInteger(-1);
    private final AtomicInteger tryTime = new AtomicInteger(0);
    @PostConstruct
    public void init() {
        String readerKey = readerConfig.getReaderKey();
        String[] antennasKey = new String[4];
        for (int j = 0; j < 4; j++) {
            antennasKey[j] = "reader" + 1 + "-antenna" + (j + 1);
        }
        UF3RFIDReader uf3RFIDReader = new UF3RFIDReader(readerKey, "uf3-rfid-reader", antennasKey, "reader-antenna");
        readers.add(uf3RFIDReader);
    }
    @Override
    public void start() {
        for (int i = 0; i < readers.size(); i++) {
            UF3RFIDReader uf3RFIDReader = readers.get(i);
            uf3RFIDReader.setUpdateDeviceStatusConsumer(this::updateDeviceStatus);
            uf3RFIDReader.setEventConsumer(event -> eventPublisher.publishEvent(event));
            uf3RFIDReader.init(i, readerConfig);
        }
        log.info("RfidReaderServer create finished,reader number:{}", readers.size());
    }
    @Scheduled(initialDelay = 100, fixedDelay = 3000)
    public void checkingTimer() {
        try {
            if (enableWork) {
                if (currentRunIndex.get() < 0) {
                    currentRunIndex.set(0);
                }
                UF3RFIDReader uf3RFIDReader = readers.get(currentRunIndex.get());
                if (uf3RFIDReader.isOnline()) {
                    log.debug("ZebraReader<ip:{}> is online", uf3RFIDReader.getIp());
                    return;
                }
                log.info("Try to connect to ZebraReader<ip:{}>,attempts:{}", uf3RFIDReader.getIp(), tryTime.get());
                uf3RFIDReader.start();
                if (uf3RFIDReader.isOnline()) {
                    tryTime.set(0);
                    return;
                }
                // 超过最大尝试次数,切换下一个读写器
                if (tryTime.incrementAndGet() > maxTryTime) {
                    log.warn("The ZebraReader<{}> exceeds the maximum attempts number<{}>, switch to the next reader", uf3RFIDReader.getIp(), maxTryTime);
                    // 重置尝试次数
                    tryTime.set(0);
                    // 切换下一个读写器
                    currentRunIndex.incrementAndGet();
                    if (currentRunIndex.get() >= readers.size()) {
                        currentRunIndex.set(0);
                    }
                }
            } else {
                if (currentRunIndex.get() < 0) {
                    log.debug("The node is not ACTIVE, do not connect to RFIDReader");
                    return;
                }
                UF3RFIDReader zebraRFIDReader = readers.get(currentRunIndex.get());
                zebraRFIDReader.disconnectReader();
                currentRunIndex.set(-1);
                log.info("The node is not ACTIVE, disconnect from RFIDReader");
            }
        } catch (Exception e) {
            log.error("RfidReaderServer checkingTimer error", e);
        }
    }
    /**
     * 启动读
     */
    public void startRead() {
        if (enableWork) {
            if (currentRunIndex.get() < 0) {
                log.warn("It is not working and cannot be started.");
                return;
            }
            UF3RFIDReader zebraRFIDReader = readers.get(currentRunIndex.get());
            try {
                zebraRFIDReader.startRead();
            } catch (Exception e) {
                log.error("Failed to start read.", e);
            }
        }
    }
    /**
     * 停止读
     */
    public void stopRead() {
        if (enableWork) {
            if (currentRunIndex.get() < 0) {
                log.warn("It is not working and cannot be stopped.");
                return;
            }
            UF3RFIDReader zebraRFIDReader = readers.get(currentRunIndex.get());
            zebraRFIDReader.stopRead();
        }
    }
    @PreDestroy
    public void destroy() {
        if (enableWork) {
            if (currentRunIndex.get() < 0) {
                return;
            }
            UF3RFIDReader zebraRFIDReader = readers.get(currentRunIndex.get());
            zebraRFIDReader.disconnectReader();
            currentRunIndex.set(-1);
            log.info("Close the rfid-reader before closing the application");
        }
    }
    @Override
    public void initDevices() {
        for (UF3RFIDReader reader : readers) {
            int index = reader.getIndex();
            // 添加设备类型
            DeviceType readerType = new DeviceType();
            readerType.setDeviceTypeKey("zebra-rfid-reader");
            readerType.setName("东集读写器");
            readerType.setTypeDescription("东集读写器");
            addDeviceType(readerType);
            DeviceType antennaType = new DeviceType();
            antennaType.setDeviceTypeKey("reader-antenna");
            antennaType.setName("读写器天线");
            antennaType.setTypeDescription("RFID读写器天线");
            addDeviceType(antennaType);
            // 添加状态
            for (StatusTypeEnums.Common value : StatusTypeEnums.Common.values()) {
                // 读写器状态
                DeviceStatus readerStatus = new DeviceStatus();
                readerStatus.setDeviceTypeKey(readerType.getDeviceTypeKey());
                readerStatus.setDeviceStatusKey(value.getKey());
                readerStatus.setName(value.getName());
                readerStatus.setStatusDescription(value.getDescription());
                addDeviceStatusType(readerStatus);
                // 天线状态
                DeviceStatus antennaStatus = new DeviceStatus();
                antennaStatus.setDeviceTypeKey(antennaType.getDeviceTypeKey());
                antennaStatus.setDeviceStatusKey(value.getKey());
                antennaStatus.setName(value.getName());
                antennaStatus.setStatusDescription(value.getDescription());
                addDeviceStatusType(antennaStatus);
            }
            // 添加错误码
            for (ErrorTypeEnums.Common value : ErrorTypeEnums.Common.values()) {
                DeviceErrorType readerErrorType = new DeviceErrorType(readerType.getDeviceTypeKey(), value.getKey(), value.getName(), value.getDescription());
                addDeviceErrorType(readerErrorType);
                DeviceErrorType antennaErrorType = new DeviceErrorType(readerType.getDeviceTypeKey(), value.getKey(), value.getName(), value.getDescription());
                addDeviceErrorType(antennaErrorType);
            }
            // 添加设备
            Device readerDevice = new Device();
            readerDevice.setDeviceKey(reader.getReaderKey());
            readerDevice.setName(readerConfig.getEquipType() + (index + 1));
            readerDevice.setDeviceTypeKey(readerType.getDeviceTypeKey());
            readerDevice.setDeviceStatusKey(StatusTypeEnums.Common.UNKNOWN.getKey());
            addDevice(readerDevice);
            for (int i = 0; i < 4; i++) {
                Device antennaDevice = new Device();
                antennaDevice.setParentDeviceKey(readerDevice.getDeviceKey());
                antennaDevice.setDeviceKey(reader.getAntennasKey()[i]);
                antennaDevice.setName("reader" + (index + 1) + "-antenna" + (i + 1));
                antennaDevice.setDeviceTypeKey(antennaType.getDeviceTypeKey());
                antennaDevice.setDeviceStatusKey(StatusTypeEnums.Common.UNKNOWN.getKey());
                antennaDevice.setParentDeviceKey(readerDevice.getDeviceKey());
                addDevice(antennaDevice);
            }
        }
    }
}
src/main/java/com/eternal/rfid/params/UF3ReaderConfig.java
New file
@@ -0,0 +1,42 @@
package com.eternal.rfid.params;
import com.eternal.config.param.annotation.Module;
import com.eternal.config.param.annotation.Param;
import lombok.Data;
import org.springframework.stereotype.Component;
@Component
@Data
@Module(type = "东集读写器",name = "东集写器配置参数")
public class UF3ReaderConfig {
    @Param(key = "component.reader.ip", defaultValue = "192.168.0.250", name = "读写器IP", fixed = true, description = "读写器IP")
    private String ip;
    @Param(key = "component.reader.readerKey", defaultValue = "uf3-reader", name = "读写器KEY", fixed = true, description = "读写器KEY")
    private String readerKey;
    @Param(key = "component.reader.readerName", defaultValue = "东集读写器", name = "读写器名称", fixed = true, description = "读写器名称")
    private String readerName;
    @Param(key = "component.reader.port", defaultValue = "27011", name = "端口", fixed = true, description = "读写器端口")
    private int port;
    @Param(key = "component.reader.readType", defaultValue = "4", name = "读写器类型", fixed = true, description = "读写器类型,当前连接的是几口设备,例如4,连的是4天线口设备;16表示连接的16天线口设备")
    private int readType;
    @Param(key = "component.reader.readLogOn_Off", defaultValue = "0", name = "日志开关", fixed = true, description = "日志开关。0-关;1-开")
    private int readLogOn_Off;
    @Param(key = "component.reader.antennaPower", defaultValue = "22,22,22,26", name = "读写器功率", fixed = true, description = "读写器功率")
    private String antennasPower;
    @Param(key = "component.rfid.reader.mode", defaultValue = "epc", name = "读模式", description = "epc,user,tid")
    private String mode;
    @Param(key = "component.rfid.reader.read.log", defaultValue = "true", name = "是否打印读写日志", description = "是否打印读写日志")
    private boolean rfidReaderReadLog;
    @Param(key = "component.rfid.reader.type", defaultValue = "uf3Reader", name = "设备类型", description = "RFID设备类型")
    private String equipType;
}
src/main/java/com/eternal/rfid/service/UF3RFIDReader.java
New file
@@ -0,0 +1,282 @@
package com.eternal.rfid.service;
import com.eternal.event.RfidReadEvent;
import com.eternal.monitor.vo.DeviceStatusVo;
import com.rfid.CReader;
import com.rfid.ReadTag;
import com.rfid.ReaderParameter;
import com.rfid.TagCallback;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import com.eternal.rfid.params.UF3ReaderConfig;
import org.springframework.context.ApplicationEvent;
import java.util.function.Consumer;
@Slf4j
public class UF3RFIDReader implements TagCallback {
    @Getter
    private final String readerKey;
    @Getter
    private final String readerTypeKey;
    @Getter
    private final String[] antennasKey;
    @Getter
    private final String antennaTypeKey;
    @Getter
    private int index;
    private UF3ReaderConfig props;
    private CReader reader;
    @Getter
    private String ip;
    private int port;
    private int readerType;
    private int readerLogOn_Off;
    private String mode;
    @Setter
    private Consumer<ApplicationEvent> eventConsumer;
    @Setter
    private Consumer<DeviceStatusVo> updateDeviceStatusConsumer;
    public UF3RFIDReader(String readerKey, String readerTypeKey, String[] antennasKey, String antennaTypeKey) {
        this.readerKey = readerKey;
        this.readerTypeKey = readerTypeKey;
        this.antennasKey = antennasKey;
        this.antennaTypeKey = antennaTypeKey;
    }
    public void init(int index, UF3ReaderConfig params) {
        this.index = index;
        this.ip = params.getIp();
        this.port = params.getPort();
        this.readerType = params.getReadType();
        this.readerLogOn_Off = params.getReadLogOn_Off();
        this.mode = params.getMode();
        this.props = params;
    }
    public void start() {
        log.info("ip:{},port:{},readerType:{},log:{}", ip, port, readerType, readerLogOn_Off);
        reader = new CReader(ip, port, readerType, readerLogOn_Off);
        int conn = reader.Connect();
        if (conn != 0) {
            log.error("连接UF3设备失败,返回码={}", conn);
            return;
        }
        log.info("UF3 Reader设备连接成功:{}:{}", ip, props.getPort());
        //0x00关闭蜂鸣器,0x01打开蜂鸣器
        reader.SetBeepNotification(0x00);
        ReaderParameter param = new ReaderParameter();
        try {
            param.SetAntenna(0x09);
            param.SetReadType(0);
            reader.SetInventoryParameter(param);
        } catch (Exception e) {
            log.warn("设置读参数时发生异常:{}", e.getMessage());
        }
        applyPowerConfig();
        reader.SetCallBack(this);
        startRead();
    }
    public void startRead() {
        int start = reader.StartRead();
        if (start != 0) {
            log.error("开启读失败,返回码={}", start);
        } else {
            getAntennaPowers();
            log.info("读流程已开启");
        }
    }
    public void stopRead() {
        if (reader != null) {
            try {
                reader.StopRead();
            } catch (Exception e) {
                log.warn("停止读过程异常:{}", e.getMessage());
            }
        }
    }
    public void disconnectReader() {
        if (reader != null) {
            try {
                reader.DisConnect();
            } catch (Exception e) {
                log.warn("断开连接异常:{}", e.getMessage());
            }
            log.info("已停止并断开UF3设备连接");
        }
    }
    public boolean isOnline() {
        return reader != null;
    }
    @Override
    public void tagCallback(ReadTag tag) {
        if (tag == null) {
            return;
        }
        String memoryData = "";
        if (mode.equals("user")) {
            memoryData = readDataByEpcMask(tag.epcId, (byte) 3, (byte) 0000, (byte) 32, "00000000");
        } else if (mode.equals("tid")) {
            memoryData = readDataByEpcMask(tag.epcId, (byte) 2, (byte) 0000, (byte) 32, "00000000");
        }
        long timeInReader = System.currentTimeMillis();
        long timeInPc = System.currentTimeMillis();
        int ant = tag.antId;
        String epc = tag.epcId;
        float rssi = tag.rssi;
        double phase = 0;
        int doppler = 0;
        RfidReadEvent rfidReadEvent = null;
        if (mode.equals("tid")) {
            rfidReadEvent = new RfidReadEvent(this, timeInReader, timeInPc, ant, epc, rssi, phase, doppler, 0L, 0, "", memoryData);
        } else if (mode.equals("user")) {
            rfidReadEvent = new RfidReadEvent(this, timeInReader, timeInPc, ant, epc, rssi, phase, doppler, 0L, 0, memoryData, "");
        } else {
            rfidReadEvent = new RfidReadEvent(this, timeInReader, timeInPc, ant, epc, rssi, phase, doppler, 0L, 0, "", "");
        }
        publishEvent(rfidReadEvent);
        if (props.isRfidReaderReadLog()) {
            log.info("标签: ip={} epc={} mem={} rssi={} ant={}",
                    tag.ipAddr, tag.epcId, tag.memId, tag.rssi, tag.antId);
            if (!memoryData.isEmpty()) {
                log.info("readDataByEpcMask 数据:{}", memoryData);
            }
        }
    }
    @Override
    public void StopReadCallback() {
        log.info("设备已停止读回调");
    }
    private void applyPowerConfig() {
        String[] powers = props.getAntennasPower().split(",");
        if (powers != null && powers.length >= 4) {
            byte[] byAnt = new byte[4];
            for (int i = 0; i < 4; i++) {
                int p = Integer.parseInt(powers[i]);
                if (p < 0) p = 0;
                if (p > 33) p = 33;
                byAnt[i] = (byte) p;
            }
            int ret = reader.SetRfPowerByAnt(byAnt);
            if (ret == 0) {
                log.info("已设置四天线功率: A1={} A2={} A3={} A4={}", byAnt[0] & 0xFF, byAnt[1] & 0xFF, byAnt[2] & 0xFF, byAnt[3] & 0xFF);
            } else {
                log.warn("按天线设置功率失败,返回码={}", ret);
            }
        }
    }
    /**
     * EPC掩码读取
     *
     * @param epc       EPC数据 (Mask)
     * @param memBank   存储区 (0:Reserved, 1:EPC, 2:TID, 3:User)
     * @param startAddr 起始地址 (Word)  起始地址都是从 0000 开始
     * @param length    读取长度 (Word)
     * @param password  访问密码 (8位16进制字符串)
     * @return 读取到的数据
     */
    public String readDataByEpcMask(String epc, byte memBank, byte startAddr, byte length, String password) {
        if (reader == null) {
            log.error("Reader未初始化");
            return null;
        }
        try {
            // 注意:调用此方法可能需要先停止自动读(StartRead),取决于SDK实现。
            // 如果是在Inventory过程中调用,可能会失败或阻塞。
            String data = reader.ReadDataByEPC(epc, memBank, startAddr, length, password);
//            log.info("EPC掩码读取结果: epc={} mem={} addr={} len={} data={}", epc, memBank, startAddr, length, data);
            return data;
        } catch (Exception e) {
            log.error("EPC掩码读取异常", e);
            return null;
        }
    }
    /**
     * TID掩码读取
     *
     * @param tid       TID数据 (Mask)
     * @param memBank   存储区 (0:Reserved, 1:EPC, 2:TID, 3:User)
     * @param startAddr 起始地址 (Word)
     * @param length    读取长度 (Word)
     * @param password  访问密码 (8位16进制字符串)
     * @return 读取到的数据
     */
    public String readDataByTidMask(String tid, byte memBank, byte startAddr, byte length, String password) {
        if (reader == null) {
            log.error("Reader未初始化");
            return null;
        }
        try {
            String data = reader.ReadDataByTID(tid, memBank, startAddr, length, password);
            log.info("TID掩码读取结果: tid={} mem={} addr={} len={} data={}", tid, memBank, startAddr, length, data);
            return data;
        } catch (Exception e) {
            log.error("TID掩码读取异常", e);
            return null;
        }
    }
    public int[] getAntennaPowers() {
        if (reader == null) {
            log.error("Reader未初始化");
            return null;
        }
        byte[] byAnt = new byte[4];
        int ret = reader.GetRfPowerByAnt(byAnt);
        if (ret != 0) {
            log.warn("查询天线功率失败,返回码={}", ret);
            return null;
        }
        int[] powers = new int[4];
        for (int i = 0; i < 4; i++) {
            powers[i] = byAnt[i] & 0xFF;
        }
        log.info("当前四天线功率: A1={} A2={} A3={} A4={}", powers[0], powers[1], powers[2], powers[3]);
        return powers;
    }
    private void publishEvent(ApplicationEvent event) {
        if (eventConsumer != null) {
            eventConsumer.accept(event);
        } else {
            log.error("{}", "No eventConsumer");
        }
    }
    protected void updateDeviceStatus(DeviceStatusVo deviceStatusVo) {
        if (updateDeviceStatusConsumer != null) {
            updateDeviceStatusConsumer.accept(deviceStatusVo);
        } else {
            log.warn("updateDeviceStatusConsumer is null");
        }
    }
}
src/test/java/com/reader/uf3readerdemo/Uf3ReaderDemoApplicationTests.java
New file
@@ -0,0 +1,13 @@
package com.reader.uf3readerdemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Uf3ReaderDemoApplicationTests {
    @Test
    void contextLoads() {
    }
}