commit a1adfd9cbe50a59d1973de7f3889ac01b9ae96a9 Author: zzp <472876707@qq.com> Date: Wed Sep 4 14:38:10 2024 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea4af49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,170 @@ +################# +## Eclipse +################# + +_site +*.iml +.idea/ +target/ +*.log +*.gz +out/ + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.vspscc +.builds +*.dotCover + +#packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +# sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +# Mac crap +.DS_Store + +.gradle diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 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..a441313 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-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..b740cf1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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. +# + +############################################################################## +# +# 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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# 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, 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" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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 + +@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=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +: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/pom.xml b/pom.xml new file mode 100644 index 0000000..828b5da --- /dev/null +++ b/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + com.cisdi.data + dacoo-data-sdk-yongfeng + 1.0-SNAPSHOT + + + 8 + 8 + + + + + junit + junit + 4.13.2 + test + + + + + + + org.apache.tomcat.embed + tomcat-embed-core + 8.5.51 + + + org.apache.tomcat.embed + tomcat-embed-el + 8.5.51 + + + org.apache.tomcat.embed + tomcat-embed-websocket + 8.5.51 + + + org.apache.tomcat + tomcat-annotations-api + 8.5.51 + + + com.cisdi.data + dacoo-data-common + 1.0-SNAPSHOT + + + com.cisdi.data + dacoo-data-sdk + 1.0-SNAPSHOT + + + netty-all + io.netty + + + + + io.netty + netty-all + 4.1.97.Final + + + org.apache.commons + commons-dbcp2 + 2.8.0 + + + mysql + mysql-connector-java + 5.1.46 + + + + com.google.guava + guava + 27.0-jre + + + com.alibaba + easyexcel + 1.1.2-beta5 + + + org.java-websocket + Java-WebSocket + 1.3.8 + + + + + + org.projectlombok + lombok + 1.18.28 + provided + + + + + \ No newline at end of file diff --git a/rfid测试说明.txt b/rfid测试说明.txt new file mode 100644 index 0000000..f7c73e7 --- /dev/null +++ b/rfid测试说明.txt @@ -0,0 +1,28 @@ +心跳报文 +02486561727442656103 + +一个标签 +02524630313030303103 + +--2条电文 +0252463031303030310302524830333030303103 + +-- 4条电文 +02524630313030303103025248303330303031030252463031303030310302524830333030303103 + +-- 4条电文心跳2: +025246303130303031030252483033303030310302524630313030303103025248303330303031030248656172744265610302486561727442656103 + +一个标签+心跳+ 一个标签 +02524630313030303103024865617274426561030252463031303030310302524630313030303103025246303130303031030252463031303030310302524630313030303103025246303130303031030252483033303030310302524630313030303103025248303330303031030248656172744265610302486561727442656103 + + + +-- 启用心跳 +{"enableHeartbeat":true,"heartbeatMaxLostCount":3,"heartbeatTimeInMs":5000} + +--多个serverId +{"serverList":["10.233.8.22:2112","10.233.8.23:2112","10.233.8.24:2112","10.233.8.25:2112"]} + +--启用心跳+ 多个server +{"enableHeartbeat":true,"heartbeatMaxLostCount":3,"heartbeatTimeInMs":5000,"serverList":["10.233.8.22:2112","10.233.8.23:2112","10.233.8.24:2112","10.233.8.25:2112"]} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..492c1ff --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'dacoo-data-sdk-DI-client' + diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/AMETEKSurfaceVisionGateway.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/AMETEKSurfaceVisionGateway.java new file mode 100644 index 0000000..8d4543f --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/AMETEKSurfaceVisionGateway.java @@ -0,0 +1,209 @@ +package com.cisdi.data.AMETEKSurfaceVision; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionAliveCheckTask; +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionChannelInitializer; +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionFrameDecoder; +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionIoSession; +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionSessionFactory; +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionSocketReturnMessage; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.google.common.collect.Sets; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; + +/** + * 表检仪tcp协议 平台:server rfid:客户端 + * + * @author tzz + */ +public class AMETEKSurfaceVisionGateway extends SocketGatewayBase { + private static Logger logger = LoggerFactory.getLogger(AMETEKSurfaceVisionGateway.class); + private TcpIoService ioService = null; + private SessionFactory sessionFactory = null; + private AtomicBoolean shouldRun = new AtomicBoolean(false); + private String deviceId; + private BlockingQueue sendQueue = new LinkedBlockingQueue<>(1); + + /** + * 下发命令超时时间,单位毫秒 + */ + private static int maxSendTime = 5000; + + private static final int sleepInternal = 100; + //10毫秒 + + @SneakyThrows + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + logger.info("send msg is :{}", returnMsg); + //阻塞队列,容量为1,若上一个命令未发送则阻塞 + sendQueue.put(returnMsg); + + ExeResultVo sendResult = new ExeResultVo(); + sendResult.setSuccess(false); + + try { + SurfaceVisionSocketReturnMessage sendMsg = new SurfaceVisionSocketReturnMessage(); + BeanUtils.copyProperties(returnMsg, sendMsg); + sendMsg.setSendReturnFlag(false); + + //发送下发命令 + List sessions = sessionFactory.getSessions(); + SurfaceVisionIoSession ioSession = (SurfaceVisionIoSession) sessions.get(0); + logger.info("send msg SurfaceVisionIoSession is :{}", ioSession); + ioSession.send(sendMsg); + + //等待下发命令返回,若等待时间超过最大等待时长,直接返回下发失败,每隔sleepInternal时间判断一次 + long sendTime = System.currentTimeMillis(); + while (!sendMsg.getSendReturnFlag()) { + long now = System.currentTimeMillis(); + if ((now - sendTime) > (maxSendTime)) { + sendResult.setMessage("下发命令响应超时,超时时间为:" + maxSendTime + "ms"); + ioSession.setCurrentSend(null); + return sendResult; + } + Thread.sleep(sleepInternal); + } + ioSession.setCurrentSend(null); + logger.info("send result is :{}", sendMsg.getRealData()); + + sendResult = sendMsg.getRealData(); + }catch (Exception e){ + logger.error("下发中断,异常为: ", e); + sendResult.setMessage("下发异常中断"); + }finally { + //空出阻塞队列 + sendQueue.take(); + } + return sendResult; + } + + + @Override + public void start() { + if(state == GatewayState.RUNNING) { + return; + } + + //配置信息校验 + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + maxSendTime = socketParam.getTimeout(); + + if (StringUtils.isEmpty(socketParam.getListenIp()) || socketParam.getListenPort() == null) { + throw new BusinessException("未正确配置监听ip地址和监听端口。"); + } + + logger.info("AMETEKSurfaceVision网关:{}读取启动配置参数:{}",instanceVo.getRunId(), getInstanceVo().getParameter()); + //读取网关连接配置参数 + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + //一个网关连接只能配置一个设备 + if(deviceVos.size() > 1) { + throw new BusinessException("网关Id:" +instanceVo.getRunId() + "为AMETEKSurfaceVision网关,只允许关联一个设备Id"); + } + + deviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if(StringUtils.isEmpty(deviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + + //创建session工厂并初始化 + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new SurfaceVisionSessionFactory(deviceId); + factory.init(serviceProvider, this); + + //初始化channel + SurfaceVisionChannelInitializer channelInitializer = + new SurfaceVisionChannelInitializer<>(factory); + + //初始化ioService + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + //启动监听 + boolean open = ioService.open(); + + if(open == true) { + state = GatewayState.RUNNING; + logger.info("AMETEKSurfaceVision网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + instanceVo.setLogOpen(true); + //心跳机制 + shouldRun.set(true); +// +// Integer keepAlive = socketParam.getKeepAlive(); +// +// if(keepAlive == null || keepAlive < 10) { +// keepAlive = 10; +// }else if (keepAlive > 60) { +// keepAlive = 60; +// } +// +// SurfaceVisionAliveCheckTask task = new SurfaceVisionAliveCheckTask(keepAlive, shouldRun, factory); +// +// Thread thread = new Thread(task, "ametek-surface-alive-check-thread-" + getInstanceVo().getRunId()); +// thread.start(); + }else { + logger.info("AMETEKSurfaceVision网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + + } + + @Override + public void shutdown() { + if(state == GatewayState.CLOSED) { + return; + } + + //关闭channel + if(sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + SurfaceVisionIoSession imsIoSession = (SurfaceVisionIoSession)ioSession; + imsIoSession.close(); + } + } + + //关闭监听和会话 + boolean close = ioService.close(); + if(close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + shouldRun.set(false); + logger.info("AMETEKSurfaceVision网关:{}关闭成功", instanceVo.getRunId()); + }else { + logger.info("AMETEKSurfaceVision网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds() { + Set set = Sets.newHashSet(); + set.add(deviceId); + return set; + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/AMETEKSurfaceVisionSocketProtocol.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/AMETEKSurfaceVisionSocketProtocol.java new file mode 100644 index 0000000..0c2a1bc --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/AMETEKSurfaceVisionSocketProtocol.java @@ -0,0 +1,41 @@ +package com.cisdi.data.AMETEKSurfaceVision; + +import com.cisdi.data.AMETEKSurfaceVision.gateway.SurfaceVisionConstants; +import java.util.Map; + +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.procotol.SocketProtocol; +import com.cisdi.data.sdk.procotol.base.ProtocolBase; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.vo.DecodeVo; +import com.cisdi.data.sdk.vo.ReturnVo; + +/** + * 表检仪协议发送给平台的数据编解码 + * + * @author tzz + */ +public class AMETEKSurfaceVisionSocketProtocol extends ProtocolBase implements SocketProtocol { + @SuppressWarnings("unchecked") + @Override + public DecodeVo deCode(SocketMessage message) { + DecodeVo vo = new DecodeVo(); + + Object object = message.getPropsMap().get(SurfaceVisionConstants.S_V_SEND_PLATFORM_DATA); + + Map map = (Map)object; + + vo.setData(map); + return vo; + } + + @Override + public SocketReturnMessage enCode(ReturnVo returnVo) { + SocketReturnMessage message = new SocketReturnMessage(); + message.setDeviceId(returnVo.getDeviceId()); + message.setMsgId(returnVo.getMsgId()); + message.setMsgKey(returnVo.getMsgKey()); + message.getPropsMap().put(SurfaceVisionConstants.S_V_SEND_DATA, returnVo.getValue()); + return message; + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/DynamicCodeActionParamDTO.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/DynamicCodeActionParamDTO.java new file mode 100644 index 0000000..584f02b --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/DynamicCodeActionParamDTO.java @@ -0,0 +1,98 @@ +package com.cisdi.data.AMETEKSurfaceVision; + +import java.util.ArrayList; +import java.util.List; + +/** + * 实例点位信息 + * + * @author tzz + */ +public class DynamicCodeActionParamDTO { + public enum PropertyType { + BYTE, + BOOLEAN, + SHORT, + INT, + FLOAT, + DOUBLE, + STRING, + LONG, + PLACEHOLDER; + } + + private List propertyList; + + public void appendProperty(PropertyConfig propertyConfig) { + + if (propertyConfig == null) { + return; + } + + if (propertyList == null) { + propertyList = new ArrayList<>(); + } + propertyList.add(propertyConfig); + } + + public static class PropertyConfig{ + + /** 属性Id*/ + private String propertyId; + + /** 属性类型*/ + private PropertyType propertyType; + + /**长度*/ + private Integer length; + + public String getPropertyId() { + return propertyId; + } + + public void setPropertyId(String propertyId) { + this.propertyId = propertyId; + } + + public PropertyType getPropertyType() { + return propertyType; + } + + public void setPropertyType(PropertyType propertyType) { + this.propertyType = propertyType; + } + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + } + + @Override + public String toString() { + return "PropertyConfig{" + + "propertyId='" + propertyId + '\'' + + ", propertyType=" + propertyType + + ", length=" + length + + '}'; + } + } + + public List getPropertyList() { + return propertyList; + } + + public void setPropertyList(List propertyList) { + this.propertyList = propertyList; + } + + @Override + public String toString() { + return "DynamicCodeActionParamDTO{" + + "propertyList=" + propertyList + + '}'; + } + +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionAliveCheckTask.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionAliveCheckTask.java new file mode 100644 index 0000000..889e6bf --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionAliveCheckTask.java @@ -0,0 +1,70 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 保活时间检测,如果超过指定时间,则关闭连接 + * @author tzz + * + */ +@Slf4j +public class SurfaceVisionAliveCheckTask implements Runnable { + + private SessionFactory sessionFactory; + private AtomicBoolean shouldRun = null; + + /** + * 会话允许的保活时间,单位秒 + */ + private int keepAlive; + + private static final double multiply = 2; + + private static final int sleepInternal = 1000; // 1秒 + + public SurfaceVisionAliveCheckTask(int keepAlive, AtomicBoolean shouldRun, SessionFactory sessionFactory) { + super(); + this.keepAlive = keepAlive; + this.shouldRun = shouldRun; + this.sessionFactory = sessionFactory; + } + + @Override + public void run() { + log.info("启动SurfaceVision心跳及检测心跳线程"); + + while (shouldRun != null && shouldRun.get() == true) { + try { + long now = System.currentTimeMillis(); + + List sessions = sessionFactory.getSessions(); + + for (IoSession ioSession : sessions) { + SurfaceVisionIoSession session = (SurfaceVisionIoSession)ioSession; + log.info("{} 定时心跳发送检测", session.gwPrefix()); + session.sendBeat(); + + // 超出指定倍数心跳时间,关闭通道 + if((now - session.getLastAliveTime()) > (multiply * keepAlive * 1000)) { + session.close(); + log.warn("{} 超出指定倍数{}心跳时间{},单位秒,关闭通道", + session.gwPrefix(), multiply, keepAlive); + } + } + + Thread.sleep(sleepInternal); + } catch (Exception e) { + log.warn(e.getLocalizedMessage(), e); + } + } + + log.info("结束SurfaceVision保活超时检测线程"); + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionChannelInitializer.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionChannelInitializer.java new file mode 100644 index 0000000..42910a1 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionChannelInitializer.java @@ -0,0 +1,51 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.handler.timeout.IdleStateHandler; +import java.util.ArrayList; +import java.util.List; + +/** + * 表检仪协议通道初始化 + * + * @author tzz + */ +public class SurfaceVisionChannelInitializer extends AbstractChannelInitializer { + private SessionFactory factory; + + /** + * 构造通道初始化器对象 + * @param factory 会话工厂 + */ + public SurfaceVisionChannelInitializer(SessionFactory factory) { + this.factory = factory; + } + + @SuppressWarnings("unchecked") + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + handlers.add(new SurfaceVisionFrameDecoder()); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + return null; + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionConstants.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionConstants.java new file mode 100644 index 0000000..001b4f4 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionConstants.java @@ -0,0 +1,73 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +/** + * 存储表检仪协议常量信息 + * + * @author tzz + */ +public final class SurfaceVisionConstants { + + //下发命令返回消息头类型 + + /** + * 非Query类型命令下发返回消息头,使用starWith判断 + */ + public static final String RETURN_HEADER1 = "REPLY_"; + /** + * 默认下发返回消息头(应为下发命令报错时消息返回头),使用equals判断 + */ + public static final String RETURN_HEADER2 = "REPLY"; + /** + * Query类型命令下发返回消息头,使用equals判断 + */ + public static final String RETURN_HEADER3 = "RESULT"; + + + /** + * 命令下发返回成功状态值,ok代表成功 + */ + public static final String RETURN_OK = "OK"; + + /** + * 消息分隔符 + */ + public static final String SEPARATE1 = "\\|"; + + /** + * 消息分隔符 + */ + public static final char SEPARATE = '|'; + + /** + * 消息结束符(除|以外) + */ + public static final char[] END = {'$','\r','\n'}; + + /** + * Query类型命令下发消息头,使用starWith判断 + */ + public static final String QUERY = "QUERY"; + + /** + * 下发命令计数器最大值 + */ + public static final int MAX_COUNT = 100; + + /** + * 下发命令正常返回消息最小字段数 + */ + public static final int MIN_NORMAL_RETURN = 3; + + /** + * 下发命令数据KEY + */ + public static final String S_V_SEND_DATA = "surfaceVisionSendData"; + + /** + * 返回给平台数据KEY + */ + public static final String S_V_SEND_PLATFORM_DATA = "surfaceVisionSendPlatformData"; + + /** 心跳符,$的16进制 */ + public static final byte BEAT_BYTE = 0x07; +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionEncoder.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionEncoder.java new file mode 100644 index 0000000..a13d2e4 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionEncoder.java @@ -0,0 +1,49 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.cisdi.data.AMETEKSurfaceVision.DynamicCodeActionParamDTO.PropertyConfig; +import com.cisdi.data.common.exception.BusinessException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +/** + * 表检仪编码 + * + * @author zhongda + */ +public class SurfaceVisionEncoder { + + /** + * 下发命令编码 + * @param msgKey 消息头,也是平台电文ID + * @param propsMap 下发消息 + * @param propertyConfigList 下发消息点位配置表 + * @param count 下发消息replayId + */ + public static ByteBuf Encode(String msgKey, Map propsMap, List propertyConfigList, int count) { + StringBuffer stringBuffer = new StringBuffer(); + //组装消息头 + stringBuffer.append(msgKey).append(SurfaceVisionConstants.SEPARATE); + //非Query命令添加replayId + if (!msgKey.startsWith(SurfaceVisionConstants.QUERY)){ + stringBuffer.append(count).append(SurfaceVisionConstants.SEPARATE); + } + //组装消息体 + for (PropertyConfig propertyConfig : propertyConfigList){ + String key = propertyConfig.getPropertyId(); + stringBuffer.append(propsMap.get(key)) + .append(SurfaceVisionConstants.SEPARATE); + } + //组装消息结尾 + stringBuffer.append(String.valueOf(SurfaceVisionConstants.END)); + + //UTF_8编码下发消息 + byte[] bytes = stringBuffer.toString().getBytes(StandardCharsets.UTF_8); + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(bytes); + + return wrappedBuffer; + } + +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionFrameDecoder.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionFrameDecoder.java new file mode 100644 index 0000000..c8df639 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionFrameDecoder.java @@ -0,0 +1,59 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import java.util.List; + +/** + * 表检仪协议帧解码 + * + * @author tzz + */ +public class SurfaceVisionFrameDecoder extends ByteToMessageDecoder { + /** 结束符除|第一位,$的16进制 */ + private byte end = 0x24; + /** 结束符除|第二位,\r的16进制 */ + private byte end1 = 0x0D; + /** 结束符除|第三位,\n的16进制 */ + private byte end2 = 0x0A; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bufferIn, List out) throws Exception { + if (bufferIn.readableBytes() < 1){ + return; + } + final int beginIndex = bufferIn.readerIndex(); + + int length = 0; + for (int i = 0; i + 2 < bufferIn.readableBytes(); i += 1) { + byte curByte = bufferIn.getByte(beginIndex + i); + + //判断是否为结束符,若为结束符读取从beginIndex开始的所有数据 + if (curByte == end){ + if (end1 == bufferIn.getByte(beginIndex + i + 1) && + end2 == bufferIn.getByte(beginIndex + i + 2)){ + length = i + 3; + byte[] bytes = new byte[i+3]; + bufferIn.readBytes(bytes); + + ByteBuf copiedBuff = Unpooled.copiedBuffer(bytes); + out.add(copiedBuff); + break; + } + } + + if (i + 3 == bufferIn.readableBytes()){ + return; + } + } + //若未结束,重置读索引 + bufferIn.readerIndex(beginIndex + length); + + //解决可能得粘包和拆包问题 + decode(ctx, bufferIn, out); + return; + } + +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionFrameInputVo.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionFrameInputVo.java new file mode 100644 index 0000000..001c3ed --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionFrameInputVo.java @@ -0,0 +1,82 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import java.util.List; +import java.util.Map; +import lombok.ToString; + +/** + * @author tzz + */ +@ToString +public class SurfaceVisionFrameInputVo { + /** 电文ID */ + private String msgKey; + /** 下发命令返回ID */ + private Integer ReplayId; + /** 下发命令返回状态值,true:成功,false:失败 */ + private Boolean status; + /** 下发命令返回描述 */ + private String description; + /** 是否为心跳信息 */ + private Boolean beatType = false; + /** 通过命令上传的返回的数据值,按照顺序填入 */ + private List data; + /** 通过命令上传的返回的数据值与配置点位的点位id对应,按照顺序对应 */ + private Map returnData; + + public String getMsgKey() { + return msgKey; + } + + public Integer getReplayId() { + return ReplayId; + } + + public Boolean getStatus() { + return status; + } + + public String getDescription() { + return description; + } + + public List getData() { + return data; + } + + public Boolean getBeatType() { + return beatType; + } + + public Map getReturnData() { + return returnData; + } + + public void setReturnData(Map returnData) { + this.returnData = returnData; + } + + public void setBeatType(Boolean beatType) { + this.beatType = beatType; + } + + public void setMsgKey(String msgKey) { + this.msgKey = msgKey; + } + + public void setReplayId(Integer replayId) { + ReplayId = replayId; + } + + public void setStatus(Boolean status) { + this.status = status; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setData(List data) { + this.data = data; + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionIoSession.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionIoSession.java new file mode 100644 index 0000000..e14e3b5 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionIoSession.java @@ -0,0 +1,291 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.alibaba.fastjson.JSON; + +import com.cisdi.data.AMETEKSurfaceVision.DynamicCodeActionParamDTO; +import com.cisdi.data.AMETEKSurfaceVision.DynamicCodeActionParamDTO.PropertyConfig; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoSession; +import com.cisdi.data.sdk.service.DynamicCodecService; +import com.cisdi.data.sdk.service.SendService; +import com.cisdi.data.sdk.service.Service; +import com.cisdi.data.sdk.vo.ExeResultVo; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 表检仪协议会话 + * + * @author tzz + */ +public class SurfaceVisionIoSession extends AbstractIoSession implements IoSession { + private long lastAliveTime = System.currentTimeMillis(); + private static Logger logger = LoggerFactory.getLogger(SurfaceVisionIoSession.class); + private String deviceId = null; + private String gwPrefixCache = null; + private final static byte[] beat = {0x07,0x7C,0x24,0x0D,0x0A}; + + private int count = 1; + // 初始化为0的原子整型变量作为计数器,用作发送命令的replayId,初始值为1 + + /** 当前下发命令replayId */ + private Integer currentCount = -1; + + public void setCurrentSend(SurfaceVisionSocketReturnMessage currentSend) { + this.currentSend = currentSend; + } + + /** 当前下发命令信息 */ + private SurfaceVisionSocketReturnMessage currentSend = null; + + public void increment() { + if (count < SurfaceVisionConstants.MAX_COUNT) { + count++; + // 自增1并返回新值 + } + else { + count = 1; + } + } + + public int getCount() { + return count; + // 获取当前计数器的值 + } + + + public SurfaceVisionIoSession(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public String[] getDeviceIds() { + return new String[0]; + } + + /** 获取当前下发电文点位配置信息 */ + private List getPropertyConfig(String msgKeyName){ + //获取DynamicService + Service service = serviceProvider.getByName(ServiceName.DynamicCodec); + if (service == null || !(service instanceof DynamicCodecService)) { + throw new BusinessException("获取DynamicService失败"); + } + + //获取网关ID + String runId = socketGateway.getInstanceVo().getRunId(); + + //获取点位配置信息 + DynamicCodecService dynamicCodecService = (DynamicCodecService)service; + String actionParam = dynamicCodecService.findByRunIdAndMsgKey(runId, msgKeyName); + DynamicCodeActionParamDTO dto = null; + try { + dto = JSON.parseObject(actionParam, DynamicCodeActionParamDTO.class); + } catch (Exception e) { + throw new BusinessException("连接id:" + runId + "电文号:" + msgKeyName + "json转换错误", e); + } + + if (dto == null || dto.getPropertyList() == null || dto.getPropertyList().isEmpty()) { + throw new BusinessException("连接id:" + runId + "电文号:" + msgKeyName + "属性列表为空"); + } + + //返回点位信息 + return dto.getPropertyList(); + } + + @Override + public void onRead(Object message) { + //获取表检仪上送或返回消息 + ByteBuf inBuf = (ByteBuf) message; + + int length = inBuf.readableBytes(); + + //将消息头和消息体拆包,解析出消息头信息 + SurfaceVisionFrameInputVo vo = SurfaceVisionVoDecoder.Decode(inBuf); + logger.info("AMETEKSurfaceVision收到 msg: {}", vo); + + //消息头,也是电文信息 + String msgKey = vo.getMsgKey(); + + try { + //通过消息头判断返回消息类型 + if(vo.getBeatType()){ + //判断是否心跳信息 + lastAliveTime = System.currentTimeMillis(); + logger.info("接收到心跳消息,当前时间为:{}", lastAliveTime); + } else if (msgKey.startsWith(SurfaceVisionConstants.RETURN_HEADER1)) { + //非Query命令正常返回消息 + //解码 + SurfaceVisionVoDecoder.DecodeReturn(vo); + + //组装返回信息,判断replayId是否一致 + ExeResultVo sendResult = new ExeResultVo(); + if (!currentCount.equals(vo.getReplayId())) { + logger.info("AMETEKSurfaceVision收到 msgkey: {} 消息:{}, ReplayId:{} 与实际发送下去的:{},不一致" + , msgKey, vo, vo.getReplayId(), currentCount); + sendResult.setSuccess(false); + sendResult.setMessage("返回消息replayId与发送消息replayId不符"); + } else { + sendResult.setSuccess(vo.getStatus()); + sendResult.setMessage(vo.getDescription()); + } + + //组装返回结果 + returnData(sendResult); + } else if (SurfaceVisionConstants.RETURN_HEADER2.equals(msgKey)) { + //下发命令异常返回消息 + //解码 + SurfaceVisionVoDecoder.DecodeReturnError(vo); + + ExeResultVo sendResult = new ExeResultVo(); + sendResult.setSuccess(vo.getStatus()); + sendResult.setMessage(vo.getDescription()); + + //组装返回结果 + returnData(sendResult); + } else if (SurfaceVisionConstants.RETURN_HEADER3.equals(msgKey)) { + //Query命令返回 + //解码 + SurfaceVisionVoDecoder.DecodeReturnQuery(vo); + + ExeResultVo sendResult = new ExeResultVo(); + sendResult.setSuccess(vo.getStatus()); + sendResult.setMessage(vo.getDescription()); + + Map queryData = new HashMap<>(); + queryData.putAll(vo.getReturnData()); + sendResult.setData(queryData); + + //组装返回结果 + returnData(sendResult); + } else { + //主动上报消息 + //解码 + SurfaceVisionVoDecoder.DecodeData(vo, getPropertyConfig(msgKey)); + Map realDataMap = vo.getReturnData(); + + //数据发送到平台 + if (vo != null && serviceProvider != null && socketGateway != null) { + SendService service = (SendService) serviceProvider.getByName(ServiceName.Send); + + //组装数据包 + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(deviceId); + + socketMessage.getPropsMap().put(SurfaceVisionConstants.S_V_SEND_PLATFORM_DATA, realDataMap); + + socketMessage.setMsgKey(msgKey); + + //发送数据 + service.sendMessage(socketMessage); + } else { + logger.info("AMETEKSurfaceVision收到 msgkey: {} 消息:{}, 但是不能发到平台", msgKey, JSON.toJSONString(realDataMap)); + } + } + }catch (Exception e){ + logger.error("AMETEKSurfaceVision接收消息匹配错误, msgKey: {}, msg:{}, error", msgKey, vo, e); + } + + if(socketGateway!= null && Boolean.TRUE.equals(socketGateway.getInstanceVo().getLogOpen())) { + logger.info("AMETEKSurfaceVision {} 报文长度:{} vo:{} ", getChannel(), length, JSON.toJSONString(vo)); + } + + } + + /** 设置当前下发命令返回信息 */ + private void returnData(ExeResultVo sendResult) { + if(currentSend == null){ + throw new BusinessException("下发返回消息没有对应的下发命令"); + } + currentSend.setRealData(sendResult); + currentSend.setSendReturnFlag(true); + currentSend = null; + } + + @Override + public void onOpen() { + super.onOpen(); + logger.info("建立连接,channel:{}", getChannel()); + } + + @Override + public void onClose() { + super.onClose(); + } + + public void close() { + if(getChannel() != null) { + try { + getChannel().close().sync(); + } catch (InterruptedException e) { + logger.warn(e.getLocalizedMessage(), e); + } + } + } + + public String gwPrefix() { + if(gwPrefixCache != null) { + return gwPrefixCache; + } + + gwPrefixCache = ""; + if(socketGateway != null && socketGateway.getInstanceVo() != null) { + gwPrefixCache = "网关Id:" + socketGateway.getInstanceVo().getRunId() + "连接:" + getChannel() + " "; + }else { + gwPrefixCache = "连接:" + getChannel() + " "; + } + + return gwPrefixCache; + } + + public long getLastAliveTime() { + return lastAliveTime; + } + + @Override + public void send(Object message) { + //下发命令 + SurfaceVisionSocketReturnMessage returnMsg = (SurfaceVisionSocketReturnMessage) message; + String msgKey = returnMsg.getMsgKey(); + + //与AMETEKSurfaceVisionSocketProtocol类对应 + Map propsMap = (Map) + returnMsg.getPropsMap().get(SurfaceVisionConstants.S_V_SEND_DATA); + + //获取下发点位配置 + List propertyConfigList = getPropertyConfig(msgKey); + + //获取计数器值作为下发命令replayId + int c = getCount(); + logger.info("下发数据, 数据:{}, 点位配置:{}, 返回ID:{}", propsMap, propertyConfigList, c); + + ByteBuf byteBuf = SurfaceVisionEncoder.Encode(msgKey, propsMap, propertyConfigList, c); + logger.info("下发数据的byteBuf:{}", byteBuf); + + //下发命令编码后发送 + getChannel().writeAndFlush(byteBuf); + + logger.info("下发数据成功"); + + //缓存当前下发命令信息和replayId + currentSend = returnMsg; + currentCount = c; + + //计数器自增 + increment(); + } + + public void sendBeat() { + logger.info("发送心跳"); + ByteBuf byteBuf = Unpooled.wrappedBuffer(beat); + getChannel().writeAndFlush(byteBuf); + logger.info("心跳发送成功"); + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionSessionFactory.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionSessionFactory.java new file mode 100644 index 0000000..3fc6f98 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionSessionFactory.java @@ -0,0 +1,26 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; +import java.util.UUID; + +/** + * 表检仪协议会话工厂 + * + * @author tzz + */ +public class SurfaceVisionSessionFactory extends AbstractSessionFactory { + private String deviceId; + + public SurfaceVisionSessionFactory(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new SurfaceVisionIoSession(deviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionSocketReturnMessage.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionSocketReturnMessage.java new file mode 100644 index 0000000..b0d8698 --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionSocketReturnMessage.java @@ -0,0 +1,41 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.vo.ExeResultVo; + +/** + * 表检仪协议下发消息及返回信息 + * + * @author tzz + */ +public class SurfaceVisionSocketReturnMessage extends SocketReturnMessage { + /** 下发消息返回消息 */ + ExeResultVo realData; + /** 下发消息返回标志 */ + boolean sendReturnFlag; + + public SurfaceVisionSocketReturnMessage() { + } + + public ExeResultVo getRealData() { + return this.realData; + } + + public void setRealData(ExeResultVo realData) { + this.realData = realData; + } + + public boolean getSendReturnFlag() { + return this.sendReturnFlag; + } + + public void setSendReturnFlag(boolean sendReturnFlag) { + this.sendReturnFlag = sendReturnFlag; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionVoDecoder.java b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionVoDecoder.java new file mode 100644 index 0000000..89769eb --- /dev/null +++ b/src/main/java/com/cisdi/data/AMETEKSurfaceVision/gateway/SurfaceVisionVoDecoder.java @@ -0,0 +1,226 @@ +package com.cisdi.data.AMETEKSurfaceVision.gateway; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.AMETEKSurfaceVision.DynamicCodeActionParamDTO.PropertyConfig; +import com.cisdi.data.AMETEKSurfaceVision.DynamicCodeActionParamDTO.PropertyType; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.beans.BeanUtils; + +/** + * 表检仪解码 + * + * @author zhongda + */ +public class SurfaceVisionVoDecoder { + /** 将消息头和消息体拆包,解析出消息头信息 */ + public static SurfaceVisionFrameInputVo Decode(ByteBuf in) { + SurfaceVisionFrameInputVo vo = new SurfaceVisionFrameInputVo(); + //消息体部分 + int length; + if (in == null) { + throw new BusinessException("ByteBuf 入参为null"); + } else if (in.readableBytes() > 4) { + length = in.readableBytes(); + } + else { + throw new BusinessException("报文长度不匹配 ,期望:大于4,实际:" + in.readableBytes()); + } + //去掉结束符 + byte[] headerBytes = new byte[length - 4]; + in.readBytes(headerBytes); + + //将数据流转为字符串,UTF_8编码 + String strData = new String(headerBytes, StandardCharsets.UTF_8); + + //以分隔符分割数据 + List data = Arrays.asList(strData.split(SurfaceVisionConstants.SEPARATE1, -1)); + + //获取报文头 + vo.setMsgKey(data.get(0)); + + //去掉报文头 + vo.setData(data.subList(1, data.size())); + + //判断是否为心跳帧 + byte[] header = data.get(0).getBytes(StandardCharsets.UTF_8); + if (header.length == 1 && header[0] == SurfaceVisionConstants.BEAT_BYTE){ + vo.setBeatType(true); + } + return vo; + } + + /** 非Query命令正常返回消息解码 */ + public static void DecodeReturn(SurfaceVisionFrameInputVo vo) { + List data = vo.getData(); + if (data.size() < SurfaceVisionConstants.MIN_NORMAL_RETURN){ + throw new BusinessException("返回消息不标准,msg:" + JSON.toJSONString(data)); + } + + vo.setReplayId(Integer.valueOf(data.get(0))); + vo.setStatus(SurfaceVisionConstants.RETURN_OK.equalsIgnoreCase(data.get(1))); + vo.setDescription(data.get(2)); + } + + /** 命令异常返回消息解码 */ + public static void DecodeReturnError(SurfaceVisionFrameInputVo vo) { + List data = vo.getData(); + int size = data.size(); + if (size < SurfaceVisionConstants.MIN_NORMAL_RETURN){ + throw new BusinessException("返回消息不标准,msg:" + JSON.toJSONString(data)); + } + + StringBuffer message = new StringBuffer(); + message.append(data.get(size - 1)) + .append(":") + .append(data.get(size - 2)); + + vo.setReplayId(0); + vo.setStatus(false); + vo.setDescription(message.toString()); + } + + /** Query命令返回消息解码 */ + public static void DecodeReturnQuery(SurfaceVisionFrameInputVo vo) { + List data = vo.getData(); + int size = data.size(); + if (size < 2){ + throw new BusinessException("返回查询消息不标准,msg:" + JSON.toJSONString(data)); + } + + Map queryData = new HashMap<>(); + queryData.put("result", data.get(size - 1)); + + vo.setReplayId(0); + vo.setStatus(true); + vo.setReturnData(queryData); + } + + /** 上送消息解码 */ + public static void DecodeData(SurfaceVisionFrameInputVo vo, List propertyConfigs) { + List data = vo.getData(); + + //判断上送消息字段数是否与点位配置信息字段数一致 + if (data.size() != propertyConfigs.size()){ + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("报文数据字段数与点位配置不一致 ,电文ID:") + .append(vo.getMsgKey()) + .append(",电文字段数:") + .append(data.size()) + .append(", 点位字段数:") + .append(propertyConfigs.size()); + throw new BusinessException(stringBuffer.toString()); + } + + Map valueMap = new HashMap<>(); + + //按照点位配置字段顺序获取上送信息 + for (int i = 0; i < data.size(); i++){ + String value = data.get(i); + PropertyConfig propertyConfig = propertyConfigs.get(i); + + PropertyType propertyType = propertyConfig.getPropertyType(); + + String propertyId = propertyConfig.getPropertyId(); + + formatValue(valueMap, value, propertyType, propertyId); + } + + vo.setReturnData(valueMap); + } + + /** 更具字段类型格式化数据值 */ + private static void formatValue(Map valueMap, String value, PropertyType propertyType, String propertyId) { + switch (propertyType){ + case INT: { + valueMap.put(propertyId, Integer.valueOf(value)); + break; + } + case BYTE: { + valueMap.put(propertyId, Byte.valueOf(value)); + break; + } + case BOOLEAN: { + valueMap.put(propertyId, Boolean.valueOf(value)); + break; + } + case SHORT: { + valueMap.put(propertyId, Short.valueOf(value)); + break; + } + case FLOAT: { + valueMap.put(propertyId, Float.valueOf(value)); + break; + } + case DOUBLE: { + valueMap.put(propertyId, Double.valueOf(value)); + break; + } + case LONG: { + valueMap.put(propertyId, Long.valueOf(value)); + break; + } + default:{ + valueMap.put(propertyId, value); + break; + } + } + } + + public static void main(String[] args) throws Exception { + //标签读取 + SurfaceVisionFrameDecoder surfaceVisionFrameDecoder = new SurfaceVisionFrameDecoder(); +// String str = "DEFECT|1|3|2011-03-19 15:21:45|2.54|1.73|252.76|$\r\n"; + //粘包 + String str = "DEFECT|1|3|2011-03-19 15:21:45||1.23||$\r\nDEFECT|1|3|2011-03-19 15:21:45||1.23||"; +// String str = "DEFECT|1|3|2011-03-19 15:21:45|2.54|1.73|252.76|$\r\n"; + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + ByteBuf copiedBuff = Unpooled.copiedBuffer(bytes); + List out = new ArrayList(); + surfaceVisionFrameDecoder.decode(null, copiedBuff, out); + + ByteBuf bufferIn = (ByteBuf) out.get(0); + + SurfaceVisionFrameInputVo vo = Decode(bufferIn); + String msgKey = vo.getMsgKey(); + + SocketReturnMessage returnMsg = new SocketReturnMessage(); + returnMsg.setMsgKey("1"); + returnMsg.setMsgId("2"); + returnMsg.setData(new byte[3]); + + SurfaceVisionSocketReturnMessage sendMsg = new SurfaceVisionSocketReturnMessage(); + BeanUtils.copyProperties(returnMsg, sendMsg); + + sendMsg.setSendReturnFlag(true); + + String msgKey1 = "test"; + Map propsMap = new HashMap<>(); + propsMap.put("id1", 1); + propsMap.put("id2", 2.33); + propsMap.put("id3", "value"); + List propertyConfigList = new ArrayList<>(); + PropertyConfig propertyConfig1 = new PropertyConfig(); + propertyConfig1.setPropertyId("id1"); + propertyConfigList.add(propertyConfig1); + PropertyConfig propertyConfig2 = new PropertyConfig(); + propertyConfig2.setPropertyId("id2"); + propertyConfigList.add(propertyConfig2); + PropertyConfig propertyConfig3 = new PropertyConfig(); + propertyConfig3.setPropertyId("id3"); + propertyConfigList.add(propertyConfig3); + int count = 1; + + SurfaceVisionEncoder.Encode(msgKey1, propsMap, propertyConfigList, count); + } + + +} diff --git a/src/main/java/com/cisdi/data/DHV/YFDHealthVibrationGateway.java b/src/main/java/com/cisdi/data/DHV/YFDHealthVibrationGateway.java new file mode 100644 index 0000000..d2020df --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/YFDHealthVibrationGateway.java @@ -0,0 +1,354 @@ +package com.cisdi.data.DHV; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.cisdi.data.DHV.gateway.vo.dhv.OtherParamVO; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.PulledHttpGatewayBase; +import com.cisdi.data.sdk.param.PullHttpParam; +import com.cisdi.data.sdk.procotol.message.HttpReturnMessage; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.cisdi.data.DHV.gateway.impl.dhv.YFDHVPullTask; +import com.cisdi.data.DHV.gateway.vo.PullHttpTaskStartVo; +import com.cisdi.data.DHV.gateway.vo.dhv.MPSVO; +import com.cisdi.data.DHV.gateway.vo.dhv.MPVVO; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author jianghc + * @function: YFDHealthVibrationGateway永锋现场-设备健康振动数据接入网关,入口 + * @date 2022年05月05日 09:29 + */ +public class YFDHealthVibrationGateway extends PulledHttpGatewayBase { + private static Logger logger = LoggerFactory.getLogger(YFDHealthVibrationGateway.class); + + //设备id + private String deviceId; + //设备当前是否运行 + private AtomicBoolean shouldRun = null; + // 允许最小的拉取间隔为1毫秒 + private static final int Min_Pull_Internal = 1; + // 允许最小的超时时间间隔,为1000毫秒 + private static final int Min_Timeout = 1000; + + + @Override + public ExeResultVo sendReturnMessage(HttpReturnMessage httpReturnMessage) { + return null; + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + try { + startInternal(); + state = GatewayState.RUNNING; + logger.info("永峰设备健康振动数据http拉取网关:{}启动成功", instanceVo.getRunId()); + } catch (Exception e) { + logger.error("永峰设备健康振动数据http拉取网关:{}启动失败", instanceVo.getRunId()); + logger.error(e.getLocalizedMessage(), e); + } + } + + private void startInternal() { + httpParam = JSON.parseObject(instanceVo.getParameter(), PullHttpParam.class); + + if (StringUtils.isEmpty(httpParam.getPullLocation())) { + throw new BusinessException("未正确配置拉取地址。"); + } + + if (httpParam.getPullInternal() == null) { + throw new BusinessException("未正确配置拉取间隔"); + } + + if (httpParam.getTimeout() == null) { + throw new BusinessException("未正确配置拉取超时时间"); + } + + httpParam.setPullInternal(httpParam.getPullInternal() < Min_Pull_Internal ? + Min_Pull_Internal : httpParam.getPullInternal()); + + httpParam.setTimeout(httpParam.getTimeout() < Min_Timeout ? Min_Timeout : httpParam.getTimeout()); + + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为永峰设备健康振动数据http拉取网关,只允许关联一个设备Id"); + } + + deviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + logger.info("永峰设备健康振动数据http拉取网关:{}读取启动配置参数:{}", getInstanceVo().getRunId(), httpParam); + + closePrevState(); + shouldRun = new AtomicBoolean(true); + +// DynamicCodecService dynamicCodecService = (DynamicCodecService) serviceProvider.getByName(ServiceName.DynamicCodec); +// if (dynamicCodecService == null || !(dynamicCodecService instanceof DynamicCodecService)) { +// throw new BusinessException("获取JDynamicCodecService失败"); +// } +// String tagConfig = dynamicCodecService.findByRunIdAndMsgKeyNoCache(getInstanceVo().getRunId(),"mps.MPS"); +// if (Strings.isEmpty(tagConfig)) { +// throw new BusinessException("未正确配置当前DHV所需配置"); +// }else{ +// logger.info("点位配置信息:"+tagConfig); +// } + + + // 启动线程 + PullHttpTaskStartVo startVo = new PullHttpTaskStartVo(); + startVo.setDeviceId(deviceId); + startVo.setGatewayBase(this); + startVo.setServiceProvider(serviceProvider); + startVo.setHttpParam(httpParam); + startVo.setShouldRun(shouldRun); + + //其他参数 + OtherParamVO otherParamVO = null; + if (httpParam.getOtherParameter() != null && httpParam.getOtherParameter().trim().length() > 0) { + String otherParameter = httpParam.getOtherParameter(); + otherParamVO = parseOtherParameter(otherParameter); + } else { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + + // 启动拉取ids的线程 + YFDHVPullTask idsTask = new YFDHVPullTask(startVo, otherParamVO); + Thread idsThread = new Thread(idsTask, "永峰-设备健康振动数据网关"); + idsThread.start(); + + state = GatewayState.RUNNING; + } + + /** + * @param otherParameter + * @return com.cisdi.data.yongfeng.gateway.vo.dhv.OtherParamVO + * @function: 解析其他参数 + * @author jianghc + * @date 2022/5/6 16:41 + */ + private OtherParamVO parseOtherParameter(String otherParameter) { + MPSVO mpsvo = new MPSVO(); + List mpvvoList = new ArrayList<>(); + JSONObject jsonObject = null; + try { + jsonObject = JSONObject.parseObject(otherParameter); + } catch (Exception ex) { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + Object obj = jsonObject.get("mps"); + if (obj != null && obj instanceof JSONObject) { + JSONObject databaseName = (JSONObject) obj; + Object dbNames = databaseName.get("databaseName"); + if (dbNames instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) dbNames; + List dbNameList = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + dbNameList.add((String) jsonArray.get(i)); + } + mpsvo.setDatabaseName(dbNameList); + } else if (dbNames instanceof String) { + List dbNameList = new ArrayList<>(); + dbNameList.add((String) dbNames); + mpsvo.setDatabaseName(dbNameList); + } + } + Object obj1 = jsonObject.get("mpv"); + if (obj1 != null && obj1 instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) obj1; + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject obj2 = (JSONObject) jsonArray.get(i); + MPVVO mpvvo = new MPVVO(); + String databaseName = obj2.getString("databaseName"); + if (databaseName != null && databaseName.trim().length() > 0) { + mpvvo.setDatabaseName(databaseName); + } else { + mpvvo.setDatabaseName(null); + } + String measuringPointNumberOrTechID = obj2.getString("measuringPointNumberOrTechID"); + if (measuringPointNumberOrTechID != null && measuringPointNumberOrTechID.trim().length() > 0) { + mpvvo.setMeasuringPointNumberOrTechID(measuringPointNumberOrTechID); + } else { + if (databaseName != null && databaseName.trim().length() > 0) { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + mpvvo.setMeasuringPointNumberOrTechID(null); + } + String assignmentTechIDorName = obj2.getString("assignmentTechIDorName"); + if (assignmentTechIDorName != null && assignmentTechIDorName.trim().length() > 0) { + mpvvo.setAssignmentTechIDorName(assignmentTechIDorName); + } else { + if (databaseName != null && databaseName.trim().length() > 0) { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + mpvvo.setAssignmentTechIDorName(null); + } + Boolean ShortMem = obj2.getBoolean("ShortMem"); + if (ShortMem != null && ShortMem.equals(Boolean.TRUE)) { + mpvvo.setShortMem(Boolean.TRUE); + } else if (ShortMem != null && ShortMem.equals(Boolean.FALSE)) { + mpvvo.setShortMem(Boolean.FALSE); + } else { + mpvvo.setShortMem(null); + } + mpvvo.setMaxCount(obj2.getInteger("MaxCount")); + Boolean LongMem = obj2.getBoolean("LongMem"); + if (LongMem != null && LongMem.equals(Boolean.TRUE)) { + mpvvo.setLongMem(Boolean.TRUE); + } else if (LongMem != null && LongMem.equals(Boolean.FALSE)) { + mpvvo.setLongMem(Boolean.FALSE); + } else { + mpvvo.setLongMem(null); + } + String StartDateTime = obj2.getString("StartDateTime"); + if (StartDateTime != null && StartDateTime.trim().length() > 0) { + mpvvo.setStartDateTime(StartDateTime); + } else { + mpvvo.setStartDateTime(null); + } + String StopDateTime = obj2.getString("StopDateTime"); + if (StopDateTime != null && StopDateTime.trim().length() > 0) { + mpvvo.setStopDateTime(StopDateTime); + } else { + mpvvo.setStopDateTime(null); + } + mpvvo.setMpktTech(obj2.getInteger("MpktTech")); + String Expand = obj2.getString("Expand"); + if (Expand != null && Expand.trim().length() > 0) { + mpvvo.setExpand(Expand); + } else { + mpvvo.setExpand(null); + } + mpvvoList.add(mpvvo); + } + } else if (obj1 != null && obj1 instanceof JSONObject) { + JSONObject obj2 = (JSONObject) obj1; + MPVVO mpvvo = new MPVVO(); + String databaseName = obj2.getString("databaseName"); + if (databaseName != null && databaseName.trim().length() > 0) { + mpvvo.setDatabaseName(databaseName); + } else { + mpvvo.setDatabaseName(null); + } + String measuringPointNumberOrTechID = obj2.getString("measuringPointNumberOrTechID"); + if (measuringPointNumberOrTechID != null && measuringPointNumberOrTechID.trim().length() > 0) { + mpvvo.setMeasuringPointNumberOrTechID(measuringPointNumberOrTechID); + } else { + if (databaseName != null && databaseName.trim().length() > 0) { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + mpvvo.setMeasuringPointNumberOrTechID(null); + } + String assignmentTechIDorName = obj2.getString("assignmentTechIDorName"); + if (assignmentTechIDorName != null && assignmentTechIDorName.trim().length() > 0) { + mpvvo.setAssignmentTechIDorName(assignmentTechIDorName); + } else { + if (databaseName != null && databaseName.trim().length() > 0) { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + mpvvo.setAssignmentTechIDorName(null); + } + Boolean ShortMem = obj2.getBoolean("ShortMem"); + if (ShortMem != null && ShortMem.equals(Boolean.TRUE)) { + mpvvo.setShortMem(Boolean.TRUE); + } else if (ShortMem != null && ShortMem.equals(Boolean.FALSE)) { + mpvvo.setShortMem(Boolean.FALSE); + } else { + mpvvo.setShortMem(null); + } + mpvvo.setMaxCount(obj2.getInteger("MaxCount")); + Boolean LongMem = obj2.getBoolean("LongMem"); + if (LongMem != null && LongMem.equals(Boolean.TRUE)) { + mpvvo.setLongMem(Boolean.TRUE); + } else if (LongMem != null && LongMem.equals(Boolean.FALSE)) { + mpvvo.setLongMem(Boolean.FALSE); + } else { + mpvvo.setLongMem(null); + } + String StartDateTime = obj2.getString("StartDateTime"); + if (StartDateTime != null && StartDateTime.trim().length() > 0) { + mpvvo.setStartDateTime(StartDateTime); + } else { + mpvvo.setStartDateTime(null); + } + String StopDateTime = obj2.getString("StopDateTime"); + if (StopDateTime != null && StopDateTime.trim().length() > 0) { + mpvvo.setStopDateTime(StopDateTime); + } else { + mpvvo.setStopDateTime(null); + } + mpvvo.setMpktTech(obj2.getInteger("MpktTech")); + String Expand = obj2.getString("Expand"); + if (Expand != null && Expand.trim().length() > 0) { + mpvvo.setExpand(Expand); + } else { + mpvvo.setExpand(null); + } + mpvvoList.add(mpvvo); + } + if ((mpsvo.getDatabaseName() == null || mpsvo.getDatabaseName().size() < 1) && (mpvvoList.size() < 1)) { + throw new BusinessException("永峰设备健康振动数据Http网关其他参数配置异常!"); + } + String user = jsonObject.getString("user"); + String pass = jsonObject.getString("pass"); + String Authorization = null; + if (user != null) { + Authorization = "Basic " + new String(Base64.getEncoder().encode((user + ":" + pass).getBytes(StandardCharsets.UTF_8))); + } else { + Authorization = "Basic "; + } + String emptyanddefault = jsonObject.getString("emptyanddefault"); + OtherParamVO otherParamVO = new OtherParamVO(); + otherParamVO.setMpsvo(mpsvo); + otherParamVO.setMpvvoList(mpvvoList); + otherParamVO.setAuthorization(Authorization); + otherParamVO.setEmptyanddefault(emptyanddefault); + return otherParamVO; + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + + closePrevState(); + + logger.info("永峰设备健康振动数据http拉取网关:{}关闭中", getInstanceVo().getRunId()); + + state = GatewayState.CLOSED; + + logger.info("永峰设备健康振动数据http拉取网关:{}关闭成功", getInstanceVo().getRunId()); + } + + private void closePrevState() { + if (shouldRun != null) { + shouldRun.set(false); + shouldRun = null; + } + } + + @Override + public Set getActiveDeviceIds() { + Set set = new HashSet(); + if (deviceId != null) { + set.add(deviceId); + } + return set; + } + +} diff --git a/src/main/java/com/cisdi/data/DHV/doc/readme.md b/src/main/java/com/cisdi/data/DHV/doc/readme.md new file mode 100644 index 0000000..4fbbb98 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/doc/readme.md @@ -0,0 +1,1523 @@ +### 设备健康振动数据接入 + +#### 测点状态接口及返回数据结构 + +/databases/{databaseName}/measuringpoints + +######databaseName数据库名称 + +```json +{ + "values": [ + { + "Number": "01-A-DX", + "IntNo": 121, + "Name": "导向轴承", + "ComponentName": "1号炉", + "ComponentNumber": "01", + "ComponentIntNo": 1, + "MachineIntNo": 0, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "TechColorEval": { + "Loaded": true, + "Alarms": 0, + "Unknown": 0, + "Green": 4, + "Yellow": 0, + "Red": 0 + } + }, + { + "Number": "01-A-ZC", + "IntNo": 122, + "Name": "支撑轴承", + "ComponentName": "1号炉", + "ComponentNumber": "01", + "ComponentIntNo": 1, + "MachineIntNo": 0, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "TechColorEval": { + "Loaded": true, + "Alarms": 0, + "Unknown": 0, + "Green": 4, + "Yellow": 0, + "Red": 0 + } + }, + { + "Number": "01-B-DX", + "IntNo": 174, + "Name": "导向轴承", + "ComponentName": "1号炉", + "ComponentNumber": "01", + "ComponentIntNo": 1, + "MachineIntNo": 0, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "TechColorEval": { + "Loaded": true, + "Alarms": 0, + "Unknown": 0, + "Green": 4, + "Yellow": 0, + "Red": 0 + } + }, + { + "Number": "01-B-ZC", + "IntNo": 175, + "Name": "支撑轴承", + "ComponentName": "1号炉", + "ComponentNumber": "01", + "ComponentIntNo": 1, + "MachineIntNo": 0, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "TechColorEval": { + "Loaded": true, + "Alarms": 0, + "Unknown": 0, + "Green": 4, + "Yellow": 0, + "Red": 0 + } + }, + { + "Number": "02-A-DX", + "IntNo": 176, + "Name": "导向轴承", + "ComponentName": "2号炉", + "ComponentNumber": "02", + "ComponentIntNo": 3, + "MachineIntNo": 0, + "ColorEval": 2, + "ColorEvalExt": 3, + "ColorEvalValue": 0.5, + "Alarmed": true, + "TechColorEval": { + "Loaded": true, + "Alarms": 1, + "Unknown": 0, + "Green": 3, + "Yellow": 1, + "Red": 0 + } + }, + { + "Number": "02-A-ZC", + "IntNo": 188, + "Name": "支撑轴承", + "ComponentName": "2号炉", + "ComponentNumber": "02", + "ComponentIntNo": 3, + "MachineIntNo": 0, + "ColorEval": 3, + "ColorEvalExt": 5, + "ColorEvalValue": 0.810000002384186, + "Alarmed": true, + "TechColorEval": { + "Loaded": true, + "Alarms": 2, + "Unknown": 0, + "Green": 2, + "Yellow": 0, + "Red": 2 + } + }, + { + "Number": "02-B-DX", + "IntNo": 189, + "Name": "导向轴承", + "ComponentName": "2号炉", + "ComponentNumber": "02", + "ComponentIntNo": 3, + "MachineIntNo": 0, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "TechColorEval": { + "Loaded": true, + "Alarms": 0, + "Unknown": 0, + "Green": 4, + "Yellow": 0, + "Red": 0 + } + }, + { + "Number": "02-B-ZC", + "IntNo": 190, + "Name": "支撑轴承", + "ComponentName": "2号炉", + "ComponentNumber": "02", + "ComponentIntNo": 3, + "MachineIntNo": 0, + "ColorEval": 2, + "ColorEvalExt": 3, + "ColorEvalValue": 0.5, + "Alarmed": true, + "TechColorEval": { + "Loaded": true, + "Alarms": 1, + "Unknown": 0, + "Green": 3, + "Yellow": 1, + "Red": 0 + } + } + ], + "startAt": 0, + "maxResults": 8, + "total": 8, + "isLast": true +} +```` + +#### 平台采集点位 + +```` +测点编码:Number +采样时间:目前(2022.5.19)平台生成采集时间 DateTime +测点状态:ColorEval +```` + +#### 获取方法的接口及返回的数据结构 +/databases/{databaseName}/measuringpoints/{measuringPointNumberOrTechID}/assignments +######databaseName数据库名称 +######measuringPointNumberOrTechID测点编码 +````json +{ + "values": [ + { + "Bearing": { + "Number": "23192", + "Manufacturer": "SKF" + }, + "ShaftDiameterMM": 460, + "NoRev": 50, + "HDi": -32768, + "FFT_Measurement": true, + "Upper_Frequency_Orders": 100, + "NoLinesSpectrum": 800, + "EnhanceFactor": 3, + "Symptoms": [ + { + "IntNo": 15, + "GroupSymNo": 117, + "Name": "HDm", + "SymType": 0, + "IsGroup": false, + "UnitStr": "HDn", + "IsCondParam": true, + "CondParam": 17, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 16, + "GroupSymNo": 118, + "Name": "HDc", + "SymType": 0, + "IsGroup": false, + "UnitStr": "HDn", + "IsCondParam": true, + "CondParam": 18, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 2, + "GroupSymNo": 13, + "Name": "Bearing", + "SymType": 4, + "IsGroup": true, + "UnitStr": "", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 3, + "GroupSymNo": 1, + "Name": "BPFO", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDesv", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 10.303, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 5, + "GroupSymNo": 3, + "Name": "BPFI", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDesv", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 12.697, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 7, + "GroupSymNo": 5, + "Name": "BSF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDesv", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 9.328, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 9, + "GroupSymNo": 7, + "Name": "FTF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDesv", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 0.448, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + } + ], + "MeasuringPointNumber": "01-A-DX", + "TechID": "121_6_3", + "Name": "01-A-DX 导向轴承 (SPM HD)", + "TechName": "SPM HD", + "Instruments": { + "Names": [ + "Online" + ], + "Intellinova": { + "CUIntNo": 1, + "Address": "U1 --> 2. VIB --> 1", + "MeasType": 0, + "MeasTypeName": "Time", + "MeasIntervalSeconds": 14400, + "RPMFactor": 1 + } + }, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "LimitsColorEval": { + "Loaded": false, + "Alarms": 409647016, + "Unknown": 9656561, + "Green": 0, + "Yellow": 43753680, + "Red": 0 + }, + "Limits": [ + { + "IntNo": 1, + "TechID": "121_6_3", + "SymInt": 15, + "GroupNo": -2147483647, + "LimitType": 111, + "CondParam": 17, + "Trend_nRes": 0, + "LimitName": "Level alarm: HDm", + "FullLimitName": "SPM HD: Level alarm: HDm", + "ValueName": "HDm", + "UnitStr": "HDn", + "GrafUnitStr": "HDn", + "ColorLevels": { + "UpperRed": 50, + "UpperYellow": 35, + "LowerYellow": 4.94065645842191E-308, + "LowerRed": 4.94065645842191E-308 + }, + "AlarmLevels": { + "UpperAlarm": 50, + "UpperAlert": 35, + "LowerAlarm": 4.94065645842191E-308, + "LowerAlert": 4.94065645842191E-308 + } + } + ], + "AssType": "MeasuringPoint.TSubAssignment_Vibration_HDm_HDc" + }, + { + "Bearing": { + "Number": "23192", + "Manufacturer": "SKF" + }, + "Upper_Frequency_Orders": 100, + "NoLinesSpectrum": 800, + "EnhanceFactor": -32768, + "Envelope": -100, + "EnvelopeName": "Filter 1 (5-100 Hz)", + "Advanced": false, + "Averaging": { + "Type": 0, + "TypeName": "(None)", + "Count": 4, + "OverlapPercent": 0 + }, + "HDi": 0, + "ShaftdiameterMM": 460, + "Symptoms": [ + { + "IntNo": 1, + "GroupSymNo": 122, + "Name": "HDrp", + "SymType": 0, + "IsGroup": false, + "UnitStr": "dB", + "IsCondParam": true, + "CondParam": 22, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 2, + "GroupSymNo": 13, + "Name": "Bearing", + "SymType": 4, + "IsGroup": true, + "UnitStr": "", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 3, + "GroupSymNo": 1, + "Name": "BPFO", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDeu", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 10.303, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 5, + "GroupSymNo": 3, + "Name": "BPFI", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDeu", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 12.697, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 6, + "GroupSymNo": 4, + "Name": "BPFIM", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDeu", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 12.697, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 1, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 0 + } + }, + { + "IntNo": 7, + "GroupSymNo": 5, + "Name": "BSF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDeu", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 9.328, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 8, + "GroupSymNo": 6, + "Name": "BSFM", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDeu", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 9.328, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 1, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 0 + } + }, + { + "IntNo": 9, + "GroupSymNo": 7, + "Name": "FTF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "HDeu", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 0.448, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + } + ], + "MeasuringPointNumber": "01-A-DX", + "TechID": "121_6_1", + "Name": "01-A-DX 导向轴承 (HD ENV)", + "TechName": "HD ENV", + "Instruments": { + "Names": [ + "Online" + ], + "Intellinova": { + "CUIntNo": 1, + "Address": "U1 --> 2. VIB --> 1", + "MeasType": 0, + "MeasTypeName": "Time", + "MeasIntervalSeconds": 57600, + "RPMFactor": 1 + } + }, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "LimitsColorEval": { + "Loaded": false, + "Alarms": 6, + "Unknown": 9644948, + "Green": 0, + "Yellow": 5, + "Red": 0 + }, + "Limits": [ + { + "IntNo": 4, + "TechID": "121_6_1", + "SymInt": 1, + "GroupNo": -2147483647, + "LimitType": 111, + "CondParam": 22, + "Trend_nRes": 0, + "LimitName": "Level alarm: HDrp", + "FullLimitName": "HD ENV: Level alarm: HDrp", + "ValueName": "HDrp", + "UnitStr": "dB", + "GrafUnitStr": "dB", + "ColorLevels": { + "UpperRed": 50, + "UpperYellow": 40, + "LowerYellow": 4.94065645842191E-308, + "LowerRed": 4.94065645842191E-308 + }, + "AlarmLevels": { + "UpperAlarm": 50, + "UpperAlert": 40, + "LowerAlarm": 4.94065645842191E-308, + "LowerAlert": 4.94065645842191E-308 + } + } + ], + "AssType": "MeasuringPoint.TSubAssignment_Vibration_HD_ENV" + }, + { + "OrderTracking": false, + "FrequenceRange": { + "UpperValue": 50, + "UpperUnit": 0, + "UpperUnitName": "Hz", + "LowerHz": 0.5 + }, + "Window": 1, + "WindowName": "Hanning", + "NoLinesSpectrum": 1600, + "Envelope": -32768, + "EnvelopeName": "(None)", + "SignalUnit": 2, + "SignalUnitName": "mm/s", + "SpectrumUnit": 2, + "SpectrumUnitName": "mm/s", + "FFT": 0, + "FFTName": "Linear", + "Averaging": { + "Type": 2, + "TypeName": "FFT linear", + "Count": 4, + "OverlapPercent": 0 + }, + "Zoom": { + "Enabled": false, + "Center": 4.94065645842191E-308, + "Factor": -32768 + }, + "Symptoms": [ + { + "IntNo": 10, + "GroupSymNo": 100, + "Name": "位移", + "SymType": 0, + "IsGroup": false, + "UnitStr": "µm", + "IsCondParam": true, + "CondParam": 0, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 11, + "GroupSymNo": 101, + "Name": "速度", + "SymType": 0, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": true, + "CondParam": 1, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 12, + "GroupSymNo": 102, + "Name": "加速度", + "SymType": 0, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": true, + "CondParam": 2, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 13, + "GroupSymNo": 110, + "Name": "峰值", + "SymType": 0, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": true, + "CondParam": 10, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 14, + "GroupSymNo": 111, + "Name": "峰峰值", + "SymType": 0, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": true, + "CondParam": 11, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 2, + "GroupSymNo": 13, + "Name": "Bearing", + "SymType": 4, + "IsGroup": true, + "UnitStr": "", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 3, + "GroupSymNo": 1, + "Name": "BPFO", + "SymType": 3, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 10.303, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 5, + "GroupSymNo": 3, + "Name": "BPFI", + "SymType": 3, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 12.697, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 7, + "GroupSymNo": 5, + "Name": "BSF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 9.328, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 9, + "GroupSymNo": 7, + "Name": "FTF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 0.448, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + } + ], + "MeasuringPointNumber": "01-A-DX", + "TechID": "121_6_2", + "Name": "01-A-DX 导向轴承 (振动低频)", + "TechName": "振动低频", + "Instruments": { + "Names": [ + "Online" + ], + "Intellinova": { + "CUIntNo": 1, + "Address": "U1 --> 2. VIB --> 1", + "MeasType": 0, + "MeasTypeName": "Time", + "MeasIntervalSeconds": 14400, + "RPMFactor": 1 + } + }, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "LimitsColorEval": { + "Loaded": false, + "Alarms": 0, + "Unknown": 207740048, + "Green": 0, + "Yellow": 98774272, + "Red": 0 + }, + "Limits": [ + { + "IntNo": 2, + "TechID": "121_6_2", + "SymInt": 11, + "GroupNo": -2147483647, + "LimitType": 111, + "CondParam": 1, + "Trend_nRes": 0, + "LimitName": "Level alarm: Vel, Rms", + "FullLimitName": "振动低频: Level alarm: Vel, Rms", + "ValueName": "Vel, Rms", + "UnitStr": "mm/s", + "GrafUnitStr": "mm/s", + "ColorLevels": { + "UpperRed": 7.1, + "UpperYellow": 4.5, + "LowerYellow": 4.94065645842191E-308, + "LowerRed": 4.94065645842191E-308 + }, + "AlarmLevels": { + "UpperAlarm": 7.1, + "UpperAlert": 4.5, + "LowerAlarm": 4.94065645842191E-308, + "LowerAlert": 4.94065645842191E-308 + } + } + ], + "AssType": "MeasuringPoint.TSubAssignment_Vibration_EVAM" + }, + { + "OrderTracking": false, + "FrequenceRange": { + "UpperValue": 2000, + "UpperUnit": 0, + "UpperUnitName": "Hz", + "LowerHz": 10 + }, + "Window": 1, + "WindowName": "Hanning", + "NoLinesSpectrum": 1600, + "Envelope": -32768, + "EnvelopeName": "(None)", + "SignalUnit": 3, + "SignalUnitName": "m/s²", + "SpectrumUnit": 3, + "SpectrumUnitName": "m/s²", + "FFT": 0, + "FFTName": "Linear", + "Averaging": { + "Type": 2, + "TypeName": "FFT linear", + "Count": 4, + "OverlapPercent": 0 + }, + "Zoom": { + "Enabled": false, + "Center": 4.94065645842191E-308, + "Factor": -32768 + }, + "Symptoms": [ + { + "IntNo": 10, + "GroupSymNo": 100, + "Name": "位移", + "SymType": 0, + "IsGroup": false, + "UnitStr": "µm", + "IsCondParam": true, + "CondParam": 0, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 11, + "GroupSymNo": 101, + "Name": "速度", + "SymType": 0, + "IsGroup": false, + "UnitStr": "mm/s", + "IsCondParam": true, + "CondParam": 1, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 12, + "GroupSymNo": 102, + "Name": "加速度", + "SymType": 0, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": true, + "CondParam": 2, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 13, + "GroupSymNo": 110, + "Name": "峰值", + "SymType": 0, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": true, + "CondParam": 10, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 14, + "GroupSymNo": 111, + "Name": "峰峰值", + "SymType": 0, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": true, + "CondParam": 11, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 2, + "GroupSymNo": 13, + "Name": "Bearing", + "SymType": 4, + "IsGroup": true, + "UnitStr": "", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "", + "Manufacturer": "", + "RPM_Formula": "", + "DeltaRPM": 0, + "z": 0, + "dz": 0, + "MinHarm": 0, + "MaxHarm": 0, + "ModRPM": 0, + "ModFIX": "", + "NoSidebands": 0, + "IncludeCF": 0 + } + }, + { + "IntNo": 3, + "GroupSymNo": 1, + "Name": "BPFO", + "SymType": 3, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 10.303, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 5, + "GroupSymNo": 3, + "Name": "BPFI", + "SymType": 3, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 12.697, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 7, + "GroupSymNo": 5, + "Name": "BSF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 9.328, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + }, + { + "IntNo": 9, + "GroupSymNo": 7, + "Name": "FTF", + "SymType": 3, + "IsGroup": false, + "UnitStr": "m/s²", + "IsCondParam": false, + "CondParam": 128, + "BearingParams": { + "BrngNo": "23192", + "Manufacturer": "SKF", + "RPM_Formula": "RPM1", + "DeltaRPM": 0.01, + "z": 0.448, + "dz": 0.01, + "MinHarm": 1, + "MaxHarm": 4, + "ModRPM": 0, + "ModFIX": "0", + "NoSidebands": 2, + "IncludeCF": 1 + } + } + ], + "MeasuringPointNumber": "01-A-DX", + "TechID": "121_6_4", + "Name": "01-A-DX 导向轴承 (振动高频)", + "TechName": "振动高频", + "Instruments": { + "Names": [ + "Online" + ], + "Intellinova": { + "CUIntNo": 1, + "Address": "U1 --> 2. VIB --> 1", + "MeasType": 0, + "MeasTypeName": "Time", + "MeasIntervalSeconds": 600, + "RPMFactor": 1 + } + }, + "ColorEval": 1, + "ColorEvalExt": 2, + "ColorEvalValue": 0.25, + "Alarmed": false, + "LimitsColorEval": { + "Loaded": false, + "Alarms": 0, + "Unknown": 207746096, + "Green": 0, + "Yellow": 98774272, + "Red": 0 + }, + "Limits": [ + { + "IntNo": 3, + "TechID": "121_6_4", + "SymInt": 11, + "GroupNo": -2147483647, + "LimitType": 111, + "CondParam": 1, + "Trend_nRes": 0, + "LimitName": "Level alarm: Vel, Rms", + "FullLimitName": "振动高频: Level alarm: Vel, Rms", + "ValueName": "Vel, Rms", + "UnitStr": "mm/s", + "GrafUnitStr": "mm/s", + "ColorLevels": { + "UpperRed": 7.1, + "UpperYellow": 4.5, + "LowerYellow": 4.94065645842191E-308, + "LowerRed": 4.94065645842191E-308 + }, + "AlarmLevels": { + "UpperAlarm": 7.1, + "UpperAlert": 4.5, + "LowerAlarm": 4.94065645842191E-308, + "LowerAlert": 4.94065645842191E-308 + } + } + ], + "AssType": "MeasuringPoint.TSubAssignment_Vibration_EVAM" + } + ], + "startAt": 0, + "maxResults": 4, + "total": 4, + "isLast": true +} +```` + + + +#### 测点总值接口及返回数据结构 + +/databases/{databaseName}/measuringpoints/{measuringPointNumberOrTechID}/assignments/{assignmentTechIDorName}/results + +######databaseName是数据库名称 +######measuringPointNumberOrTechID是测点编码 +######assignmentTechIDorName是第二个接口中返回的TechName + + + +```json +{ + "values": [ + { + "DateTime": "2022-02-18T15:33:34.000Z", + "Result": { + "RPM": { + "RPM1": 942.8578, + "RPM2": 4.94065645842191e-308, + "Avg": 942.8578, + "Min": 942.736145, + "Max": 942.945923, + "Delta": 0.209778000000028 + }, + "FFT": { + "Upper_Orders": 0, + "Lower_Orders": 0, + "Upper_Frequence_Hz": 0, + "Lower_Frequence_Hz": 0, + "Unit": -1, + "UnitName": "", + "DisplayUnitName": "", + "DisplayUnitConvType": 0, + "FFT": [] + }, + "Phase": { + "Phases": [] + }, + "TimeSignal": { + "Unit": 0, + "UnitName": "", + "DisplayUnitName": "", + "DisplayUnitConvType": 0, + "Samples": [], + "TotalTimeMS": 0 + }, + "Symptoms": [], + "Conditions": { + "DISP": 4.94065645842191e-308, + "VEL": 4.94065645842191e-308, + "ACC": 4.94065645842191e-308, + "CREST": 4.94065645842191e-308, + "KURT": 4.94065645842191e-308, + "SKEW": 4.94065645842191e-308, + "NL1": 4.94065645842191e-308, + "NL2": 4.94065645842191e-308, + "NL3": 4.94065645842191e-308, + "NL4": 4.94065645842191e-308, + "Peak": 4.94065645842191e-308, + "PkToPk": 4.94065645842191e-308, + "dBm": 4.94065645842191e-308, + "dBc": 4.94065645842191e-308, + "LR": 4.94065645842191e-308, + "HR": 4.94065645842191e-308, + "HDm": -18.3999996185303, + "HDc": -26.1000003814697, + "HDi": 19.6000003814697, + "HDrp": 4.94065645842191e-308, + "Temperature": 4.94065645842191e-308, + "VibBias": 13.1055326461792, + "TLT": -128, + "kOhm": 4.94065645842191e-308 + }, + "DateTime": "2022-02-18T15:33:34.000Z", + "TechID": "177_6_2" + } + } + ] +} +```` + +#### 平台采集点位 +Max:Max +```` +测点编码:measuringPointNumberOrTechID +采样时间:DateTime +====(RPM:RPM)====开始 +RPM1:RPM1 +RPM2:RPM2 +Avg:Avg +Min:Min +Max:Max +Delta:Delta +====(RPM:RPM)====结束 +HDm:HDm +HDc:HDc +速度值:VEL +位移值:DISP +加速度值:ACC +峰值:Peak +峰峰值:PkToPk +```` + +#### 平台内其他参数配置及说明 + +```json +{ + "mps": { + "databaseName": [ + "mps" + ] + }, + "user": "YFDHV", + "pass": "YFDHV", + "emptyanddefault": "1" +} +``` + +``` +mps:测点状态 + databaseName:数据库名称 //如果要采集测点状态,必填 +mpv:测点总值 + databaseName:数据库名称 //如果要采集测点总值,必填 + measuringPointNumberOrTechID:测点编码 //如果要采集测点总值,必填 + assignmentTechIDorName: //如果要采集测点总值,必填 + ShortMem: + LongMem: + MaxCount: + StartDateTime: + StopDateTime: + MpktTech: + Expand: +user:认证用户名 +pass:认证用户密码 +emptyanddefault: 默认是1 +``` \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/DHV/doc/现场点位数据信息.xlsx b/src/main/java/com/cisdi/data/DHV/doc/现场点位数据信息.xlsx new file mode 100644 index 0000000..1852384 Binary files /dev/null and b/src/main/java/com/cisdi/data/DHV/doc/现场点位数据信息.xlsx differ diff --git a/src/main/java/com/cisdi/data/DHV/doc/设备健康振动协议使用说明.docx b/src/main/java/com/cisdi/data/DHV/doc/设备健康振动协议使用说明.docx new file mode 100644 index 0000000..33bcb1a Binary files /dev/null and b/src/main/java/com/cisdi/data/DHV/doc/设备健康振动协议使用说明.docx differ diff --git a/src/main/java/com/cisdi/data/DHV/gateway/config/YFHttpClient.java b/src/main/java/com/cisdi/data/DHV/gateway/config/YFHttpClient.java new file mode 100644 index 0000000..8d9a339 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/config/YFHttpClient.java @@ -0,0 +1,42 @@ +package com.cisdi.data.DHV.gateway.config; + +import com.google.common.collect.Maps; +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * @author jianghc + * @function: + * @date 2022年05月07日 10:26 + */ +public class YFHttpClient { + + private static Map map = Maps.newConcurrentMap(); + + + public static OkHttpClient get(Integer timeout) { + + OkHttpClient okHttpClient = map.get(timeout); + + if(okHttpClient != null) { + return okHttpClient; + } + + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.connectTimeout(timeout, TimeUnit.MILLISECONDS) + .readTimeout(timeout, TimeUnit.MILLISECONDS) + .writeTimeout(timeout,TimeUnit.MILLISECONDS) + .connectionPool(new ConnectionPool(15, 1, TimeUnit.MINUTES)) + .retryOnConnectionFailure(true); + + okHttpClient = builder.build(); + + map.put(timeout, okHttpClient); + + return okHttpClient; + } + +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/impl/HttpPullTaskBase.java b/src/main/java/com/cisdi/data/DHV/gateway/impl/HttpPullTaskBase.java new file mode 100644 index 0000000..128b222 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/impl/HttpPullTaskBase.java @@ -0,0 +1,26 @@ +package com.cisdi.data.DHV.gateway.impl; + +import com.cisdi.data.DHV.gateway.vo.dhv.OtherParamVO; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.DHV.gateway.vo.PullHttpTaskStartVo; + +/** + * @author jianghc + * @function: + * @date 2022年05月05日 09:37 + */ +public class HttpPullTaskBase { + /** + * 线程启动参数 + */ + protected PullHttpTaskStartVo startVo = null; + + protected void checkInitParam(PullHttpTaskStartVo taskStartVo, OtherParamVO otherParamVO) { + if(taskStartVo.getShouldRun() == null + || taskStartVo.getShouldRun() == null + || taskStartVo.getHttpParam() == null) { + throw new BusinessException("永峰设备健康振动数据Http拉取线程启动参数缺失。"); + } + this.startVo = taskStartVo; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/impl/dhv/YFDHVPullTask.java b/src/main/java/com/cisdi/data/DHV/gateway/impl/dhv/YFDHVPullTask.java new file mode 100644 index 0000000..acc455b --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/impl/dhv/YFDHVPullTask.java @@ -0,0 +1,575 @@ +package com.cisdi.data.DHV.gateway.impl.dhv; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.cisdi.data.DHV.gateway.config.YFHttpClient; +import com.cisdi.data.DHV.gateway.impl.HttpPullTaskBase; +import com.cisdi.data.DHV.gateway.vo.PullHttpTaskStartVo; +import com.cisdi.data.DHV.gateway.vo.dhv.*; +import com.cisdi.data.common.exception.BusinessException; + +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.base.PulledHttpGatewayBase; +import com.cisdi.data.sdk.gateway.message.HttpMessage; +import com.cisdi.data.sdk.gateway.message.Message; +import com.cisdi.data.sdk.gateway.message.MessageBatch; +import com.cisdi.data.sdk.service.SendService; +import com.cisdi.data.sdk.service.ServiceProvider; +import okhttp3.OkHttpClient; +import okhttp3.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * @author jianghc + * @function: + * @date 2022年05月05日 10:32 + */ +public class YFDHVPullTask extends HttpPullTaskBase implements Runnable { + + private Logger logger = LoggerFactory.getLogger(YFDHVPullTask.class); + + //请求测点状态的URL地址 + private String[] DHV_MPS_URL = null; + + //获取teachName的URL地址 + private String[] DHV_MET_URL = null; + + //请求测点总值的URL地址 + private String[] DHV_MPV_URL = null; + + //测点的ID + private String[] measuringPointNumberOrTechIDs = null; + + //base64编码的user:password + private String Authorization; + + //DHV请求必须携带的头部参数 + private String emptyanddefault; + + //超时时间 + private Integer timeOut; + + //其他参数中配置的数据库 + private List dbNames; + + //判断当前是否获取了db-测点id-测点方法的一个标志,在网关启动的时候执行一次后变成true,并装入下面三个容器中 + private boolean metPara = false; + private List dbNameList; + private List itemIdList; + private List methodNameList; + + //连接管理中的拉取地址,以上所有的URL地址都是用这个地址为前缀拼接成的 + private String url; + + public YFDHVPullTask(PullHttpTaskStartVo pullHttpTaskStartVo, OtherParamVO otherParamVO) { + checkInitParam(pullHttpTaskStartVo, otherParamVO); + + url = startVo.getHttpParam().getPullLocation(); + if (url == null) { + logger.error("拉取地址不能为空!"); + throw new BusinessException("拉取地址不能为空!"); + } + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + + dbNames = otherParamVO.getMpsvo().getDatabaseName(); + int dbNamesLen = dbNames.size(); + if (dbNamesLen > 0) { + DHV_MPS_URL = new String[dbNamesLen]; + for (int i = 0; i < dbNamesLen; i++) { + DHV_MPS_URL[i] = url + String.format(YFDHV.YF_DHV_MPS, dbNames.get(i)); + } + } + + Authorization = otherParamVO.getAuthorization(); + + emptyanddefault = otherParamVO.getEmptyanddefault() == null ? "1" : otherParamVO.getEmptyanddefault(); + + timeOut = startVo.getHttpParam().getTimeout(); + + } + + + @Override + public void run() { + OkHttpClient httpClient = YFHttpClient.get(startVo.getHttpParam().getPullInternal()); + if (DHV_MPS_URL != null) { + for (int i = 0; i < DHV_MPS_URL.length; i++) { + logger.info("MPS采集地址:" + DHV_MPS_URL[i]); + } + } else { + logger.info("您尚未配置采集地址"); + } + + //在这个驱动中,我们只请求如下时间范围的数据,从当前时间往前数半个小时(第一次请求这样) + //以后每个半个小时请求一次,每次的请求时间范围是,上一次的请求时间到当前时间点,通常会比半个小时多一点点, + //多出来的时间是上一次请求的耗时时间 + LocalDateTime endTime = null; + LocalDateTime startTime = null; + while (startVo.getShouldRun().get() == true) { + logger.info("即将采集设备数据......"); + LocalDateTime end_time_temp = null; + if (endTime != null) { + end_time_temp = endTime; + } + endTime = LocalDateTime.now(); + if (startTime == null) { + startTime = endTime.minusMinutes(30); + } else { + startTime = end_time_temp; + } + Map mpsJsonStringMap = MPSRequest(httpClient); + if (DHV_MET_URL == null) { + Construct_DHV_MET_URL(); + } + if (DHV_MPV_URL == null) { + METRequest(httpClient); + } + Map mpvJsonArrayMap = MPVRequest(httpClient, startTime, endTime); + if (mpsJsonStringMap != null && mpsJsonStringMap.size() > 0) { + for (int i = 0; i < mpsJsonStringMap.size(); i++) { + String msgKey = dbNames.get(i) + YFDHV.YF_Separator + YFDHV.YF_MPS; + sendToNextMQ(mpsJsonStringMap.get(dbNames.get(i)), msgKey); + } + } + if (mpvJsonArrayMap != null && mpvJsonArrayMap.size() > 0) { + List dbNameList = new ArrayList<>(mpvJsonArrayMap.keySet()); + for (int i = 0; i < dbNameList.size(); i++) { + String dbName = dbNameList.get(i); + sendToNextMQ(mpvJsonArrayMap.get(dbName).toJSONString(), dbName); + } + } + try { + Thread.sleep(startVo.getHttpParam().getPullInternal()); + } catch (InterruptedException e) { + logger.error("休眠异常"); + throw new BusinessException("休眠异常"); + } + } + } + + /** + * @param httpClient + * @return java.lang.String + * @function: 终极奥义之通过DHV_MPS_URL地址获取测点状态信息 + * @author jianghc + * @date 2022/5/24 16:16 + */ + private Map MPSRequest(OkHttpClient httpClient) { + if (!metPara) { + dbNameList = new ArrayList<>(); + itemIdList = new ArrayList<>(); + } + if (DHV_MPS_URL == null) { + return null; + } + Map mpsJsonMap = new HashMap<>(); + int DHV_MPS_URL_Len = DHV_MPS_URL.length; + + for (int i = 0; i < DHV_MPS_URL_Len; i++) { + okhttp3.Request request = new okhttp3.Request.Builder() + .header("Authorization", Authorization) + .header("emptyanddefault", emptyanddefault) + .url(DHV_MPS_URL[i]) + .get() + .build(); + Response response = null; + try { + Long currentTime_start = System.currentTimeMillis(); + Long currentTime_end = 0l; + response = httpClient.newCall(request).execute(); + currentTime_end = System.currentTimeMillis(); + if (currentTime_end - currentTime_start > timeOut) { + logger.error("地址:" + DHV_MPS_URL[i] + "请求超时!"); + throw new BusinessException("地址:" + DHV_MPS_URL[i] + "请求超时!"); + } + } catch (Exception e) { + logger.error("被请求的接口:" + DHV_MPS_URL[i] + "请求超时," + e.getMessage()); + throw new BusinessException("被请求的接口:" + DHV_MPS_URL[i] + "请求超时," + e.getMessage()); + } + if (response.isSuccessful()) { + String jsonString = null; + try { + jsonString = new String(response.body().bytes(), StandardCharsets.UTF_8); + } catch (Exception ex) { + logger.error("MPS响应数据解析异常"); + throw new BusinessException("MPS响应数据解析异常"); + } + Object jsonArrayObject = ((JSONObject) JSONObject.parse(jsonString)).get("values"); + JSONObject resJson = new JSONObject(); + if (jsonArrayObject instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) jsonArrayObject; + for (int j = 0; j < jsonArray.size(); j++) { + JSONObject jsonObject = (JSONObject) jsonArray.get(j); + MPSItem[] mpsItems = MPSItem.values(); + MPSItem primary = MPSItem.getPrimary(); + Object primaryValue = jsonObject.get(primary.toString()); + + String mp = jsonObject.getString(MPSItem.getPrimary().getMpsItem()); + if (!AllowMeasuringPoint.allowMeasuringPoint(mp)) { + continue; + } + + for (MPSItem mpsItem : mpsItems) { + String key = dbNames.get(i) + YFDHV.YF_Separator + primaryValue + YFDHV.YF_Separator + mpsItem.getMpsItem(); + Object value = jsonObject.get(mpsItem.toString()); + resJson.fluentPut(key, value); + } + resJson.fluentPut(dbNames.get(i) + YFDHV.YF_Separator + primaryValue + YFDHV.YF_Separator + YFDHV.DateTime + , LocalDateTime.now());//注意测点状态时间是系统生成的,没有从接口中获取到 + if (!metPara) { + String mpx = jsonObject.getString(MPSItem.getPrimary().getMpsItem()); + if (!AllowMeasuringPoint.allowMeasuringPoint(mpx)) { + continue; + } + dbNameList.add(dbNames.get(i)); + itemIdList.add(mpx); + } + } + } else { + logger.warn("地址:" + DHV_MPS_URL[i] + " 未采集到数据!"); + } + if (resJson.size() > 0) { + mpsJsonMap.put(dbNames.get(i), resJson.toJSONString()); + } + } + } + if (!metPara) { + StringBuilder stringBuffer = new StringBuilder("\n"); + stringBuffer.append("测点状态采集点信息:\n"); + stringBuffer.append("=======================================================================================\n"); + for (int i = 0; i < dbNameList.size(); i++) { + stringBuffer.append("数据库:" + dbNameList.get(i) + " 测点状态id:" + itemIdList.get(i) + "\n"); + } + stringBuffer.append("=======================================================================================\n"); + logger.info(stringBuffer.toString()); + metPara = true; + } + if (mpsJsonMap.size() > 0) { + return mpsJsonMap; + } else { + return null; + } + } + + /** + * @function: 构造DHV_MET_URL的请求地址 + * @author jianghc + * @date 2022/5/24 16:12 + */ + private void Construct_DHV_MET_URL() { + if (itemIdList == null || itemIdList.size() == 0) { + return; + } + int itemIdList_len = itemIdList.size(); + DHV_MET_URL = new String[itemIdList_len]; + for (int i = 0; i < itemIdList.size(); i++) { + String measuringPointNumberOrTechID = itemIdList.get(i); + if (measuringPointNumberOrTechID.contains("#")) { + measuringPointNumberOrTechID = measuringPointNumberOrTechID.replace("#", "%23"); + } + DHV_MET_URL[i] = url + String.format(YFDHV.YF_DHV_METH, dbNameList.get(i), measuringPointNumberOrTechID); + } + StringBuilder stringBuffer = new StringBuilder("\n"); + stringBuffer.append("获取测点总值需要的方法请求路径包含:\n"); + stringBuffer.append("=======================================================================================\n"); + for (int i = 0; i < DHV_MET_URL.length; i++) { + stringBuffer.append(DHV_MET_URL[i] + "\n"); + } + stringBuffer.append("=======================================================================================\n"); + logger.info(stringBuffer.toString()); + } + + /** + * @param httpClient + * @function: 主要要来获取dbNameList,itemIdList,methodNameList + * @author jianghc + * @date 2022/5/24 16:12 + */ + private void METRequest(OkHttpClient httpClient) { + List dbNameList_temp = new ArrayList<>(); + List itemIdList_temp = new ArrayList<>(); + if (DHV_MET_URL == null) { + return; + } + methodNameList = new ArrayList<>(); + int DHV_MET_URL_len = DHV_MET_URL.length; + for (int i = 0; i < DHV_MET_URL_len; i++) { + okhttp3.Request request = new okhttp3.Request.Builder() + .header("Authorization", Authorization) + .header("emptyanddefault", emptyanddefault) + .url(DHV_MET_URL[i]) + .get() + .build(); + Response response = null; + try { + response = httpClient.newCall(request).execute(); + } catch (IOException ex) { + throw new BusinessException("接口:" + DHV_MET_URL[i] + "请求失败!"); + } + if (response != null && response.isSuccessful()) { + String jsonString = null; + try { + jsonString = new String(response.body().bytes(), StandardCharsets.UTF_8); + } catch (Exception ex) { + logger.error("MPS响应数据解析异常"); + throw new BusinessException("MPS响应数据解析异常"); + } + JSONObject jsonObject = (JSONObject) JSONObject.parse(jsonString); + JSONArray jsonArray = (JSONArray) jsonObject.get("values"); + if (jsonArray != null && jsonArray.size() > 0) { + for (int j = 0; j < jsonArray.size(); j++) { + JSONObject jsonObject_val = (JSONObject) jsonArray.get(j); + String TechName = jsonObject_val.getString("TechName"); + if (!AllowMethod.allowMethod(TechName)) { + logger.info(TechName + "方法不匹配"); + continue; + } + dbNameList_temp.add(dbNameList.get(i)); + itemIdList_temp.add(itemIdList.get(i)); + methodNameList.add(TechName); + } + } + } + } + dbNameList = null; + dbNameList = dbNameList_temp; + itemIdList = null; + itemIdList = itemIdList_temp; + StringBuilder stringBuilder = new StringBuilder("\n"); + measuringPointNumberOrTechIDs = new String[dbNameList.size()]; + stringBuilder.append("测点总值需要的参数:\n"); + stringBuilder.append("=======================================================================================\n"); + for (int i = 0; i < dbNameList.size(); i++) { + measuringPointNumberOrTechIDs[i] = dbNameList.get(i) + YFDHV.YF_Separator + itemIdList.get(i) + YFDHV.YF_Separator + methodNameList.get(i); + stringBuilder.append("数据库:" + dbNameList.get(i) + + " 测点总值id:" + itemIdList.get(i) + + " 方法:" + methodNameList.get(i) + "\n"); + } + stringBuilder.append("=======================================================================================\n"); + logger.info(stringBuilder.toString()); + Construct_DHV_MPV_URL(); + } + + /** + * @function: 构造DHV_MPV_URL的请求地址 + * @author jianghc + * @date 2022/5/24 16:14 + */ + private void Construct_DHV_MPV_URL() { + if (methodNameList == null) { + return; + } + int methodNameList_len = methodNameList.size(); + DHV_MPV_URL = new String[methodNameList_len]; + for (int i = 0; i < methodNameList_len; i++) { + String measuringPointNumberOrTechID = itemIdList.get(i); + if (measuringPointNumberOrTechID.contains("#")) { + measuringPointNumberOrTechID = measuringPointNumberOrTechID.replace("#", "%23"); + } + DHV_MPV_URL[i] = url + String.format(YFDHV.YF_DHV_MPV, dbNameList.get(i), measuringPointNumberOrTechID, methodNameList.get(i)); + DHV_MPV_URL[i] += "?ShortMem=true&LongMem=true&Expand=result"; + } + StringBuilder stringBuilder = new StringBuilder("\n"); + stringBuilder.append("测点总值请求路径(该请求路径不含时间参数 &StartDateTime &StopDateTime ):\n"); + stringBuilder.append("=======================================================================================\n"); + for (int i = 0; i < DHV_MPV_URL.length; i++) { + stringBuilder.append(DHV_MPV_URL[i] + "\n"); + } + stringBuilder.append("=======================================================================================\n"); + logger.info(stringBuilder.toString()); + } + + /** + * @param httpClient + * @param start + * @param end + * @return Map + * @function: 终极奥义之通过DHV_MPV_URL信息获取测点总值信息 + * @author jianghc + * @date 2022/5/24 16:14 + */ + private Map MPVRequest(OkHttpClient httpClient, LocalDateTime start, LocalDateTime end) { + String startTime = start.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + String endTime = end.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + if (DHV_MPV_URL == null) { + return null; + } + int DHV_MPV_URL_Len = DHV_MPV_URL.length; + + Map jsonArrayResObj = new HashMap<>(); + JSONObject resJson = null; + for (int i = 0; i < DHV_MPV_URL_Len; i++) { + String msgKey = dbNameList.get(i) + YFDHV.YF_Separator + itemIdList.get(i) + YFDHV.YF_Separator + YFDHV.YF_MPV; + resJson = jsonArrayResObj.get(msgKey); + if (resJson == null) { + resJson = new JSONObject(); + } + okhttp3.Request request = new okhttp3.Request.Builder() + .header("Authorization", Authorization) + .header("emptyanddefault", emptyanddefault) + //.url(DHV_MPV_URL[i] + "&StartDateTime=2020-01-01 00:00:01" + "&StopDateTime=" + endTime) + .url(DHV_MPV_URL[i] + "&StartDateTime=" + startTime + "&StopDateTime=" + endTime) + .get() + .build(); + Response response = null; + try { + Long currentTime_start = System.currentTimeMillis(); + Long currentTime_end = 0l; + response = httpClient.newCall(request).execute(); + currentTime_end = System.currentTimeMillis(); + if (currentTime_end - currentTime_start > timeOut) { + logger.error("地址:" + DHV_MPV_URL[i] + "请求超时!"); + throw new BusinessException("地址:" + DHV_MPV_URL[i] + "请求超时!"); + } + } catch (Exception e) { + logger.error("地址:" + DHV_MPV_URL[i] + "请求超时!" + e.getMessage()); + throw new BusinessException("地址:" + DHV_MPV_URL[i] + "请求超时!"); + } + MPVItem[] mpvItems = MPVItem.values(); + if (response.isSuccessful()) { + MPVItem primary = MPVItem.getPrimary(); + String primary_value = measuringPointNumberOrTechIDs[i]; + String jsonString = null; + try { + jsonString = new String(response.body().bytes(), StandardCharsets.UTF_8); + } catch (Exception ex) { + logger.error("MPV响应数据解析异常"); + throw new BusinessException("MPV响应数据解析异常"); + } + Object jsonObject_temp = JSONObject.parse(jsonString); + if (jsonObject_temp instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) jsonObject_temp; + Object jsonArrayObject = jsonObject.get("values"); + if (jsonArrayObject != null && jsonArrayObject instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) jsonArrayObject; + + for (int j = 0; j < jsonArray.size(); j++) { + Object obj = jsonArray.get(j); + if (j >= 2) { + logger.error("拉取数据过多,请缩短采集时间间隔!"); + logger.error("多余数据将不被采集:" + ((JSONObject) obj).toJSONString()); + continue; + } + if (obj != null && obj instanceof JSONObject) { + JSONObject jsonObject_value = (JSONObject) obj; + Object obj_Result = jsonObject_value.get("Result"); + if (obj_Result != null && obj_Result instanceof JSONObject) { + JSONObject jsonObject_Result = (JSONObject) obj_Result; + + //primary_value=数据库.测点.方法 + //key-value + //key:数据库.测点.方法.属性 + //value:测点 + resJson.fluentPut(primary_value + YFDHV.YF_Separator + primary.getMpvItem(), + primary_value.split(YFDHV.YF_EscapeCharacter + YFDHV.YF_Separator)[1]); + + + Object RPM = jsonObject_Result.get("RPM"); + if (RPM != null && RPM instanceof JSONObject) { + JSONObject rpmObj = (JSONObject) RPM; + MPVRPMItem[] mpvrpmItems = MPVRPMItem.values(); + for (MPVRPMItem mpvrpmItem : mpvrpmItems) { + String key = primary_value + YFDHV.YF_Separator + mpvrpmItem.getMpvRPMItem(); + String value = rpmObj.getString(mpvrpmItem.getMpvRPMItem()); + //key-value + //key:数据库.测点.方法.属性 + //value:属性值 + resJson.fluentPut(key, value); + } + } + + //String DateTime_key = primary_value + YFDHV.YF_Separator + MPVItem.DateTime.getMpvItem(); + //String DateTime_value = jsonObject_Result.get(MPVItem.DateTime.toString()).toString(); + //key-value + //key:数据库.测点.方法.属性 + //value:属性值 + //resJson.fluentPut(DateTime_key, DateTime_value); + + Object obj_Conditions = jsonObject_Result.get("Conditions"); + if (obj_Conditions != null && obj_Conditions instanceof JSONObject) { + JSONObject jsonObject_Conditions = (JSONObject) obj_Conditions; + + for (MPVItem mpvItem : mpvItems) { + String key = primary_value + YFDHV.YF_Separator + mpvItem.getMpvItem(); + Object value = null; + try { + value = jsonObject_Conditions.get(mpvItem.toString()); + } catch (Exception ex) { + logger.error("永峰设备健康振动数据Http网关response解析异常"); + throw new BusinessException("永峰设备健康振动数据Http网关response解析异常"); + } + if (value != null) { + //key-value + //key:数据库.测点.方法.属性 + //value:属性值 + resJson.fluentPut(key, value); + } + } + } + } + } + } + } + } + } + if (resJson != null && resJson.size() > 0) { + jsonArrayResObj.put(msgKey, resJson); + } + } + if (jsonArrayResObj.size() > 0) { + return jsonArrayResObj; + } else { + return null; + } + } + + /** + * @param body + * @param msgKey + * @function: 将数据发送到平台 + * @author jianghc + * @date 2022/5/24 16:15 + */ + private void sendToNextMQ(Object body, String msgKey) { + ServiceProvider serviceProvider = startVo.getServiceProvider(); + PulledHttpGatewayBase gatewayBase = startVo.getGatewayBase(); + if (serviceProvider != null && gatewayBase != null && body instanceof String) { + SendService service = (SendService) serviceProvider.getByName(ServiceName.Send); + Message message = null; + HttpMessage httpMessage = gatewayBase.buildHttpMessage(); + httpMessage.setDeviceId(startVo.getDeviceId()); + httpMessage.setProviderCode(null); + httpMessage.setBody((String) body); + httpMessage.setMsgKey(msgKey); + message = httpMessage; + service.sendMessage(message); + } else if (serviceProvider != null && gatewayBase != null && body instanceof JSONArray) { + SendService service = (SendService) serviceProvider.getByName(ServiceName.Send); + Message message; + JSONArray sendBody = (JSONArray) body; + List httpMessages = new ArrayList(sendBody.size()); + for (Object obj : sendBody) { + HttpMessage httpMessage = gatewayBase.buildHttpMessage(); + httpMessage.setDeviceId(startVo.getDeviceId()); + httpMessage.setProviderCode(null); + httpMessage.setBody(obj.toString()); + httpMessage.setMsgKey(msgKey); + httpMessages.add(httpMessage); + } + MessageBatch batch = new MessageBatch<>(httpMessages); + message = batch; + service.sendMessage(message); + } else { + logger.info("连接id-{} 拉取采集到值-{}", gatewayBase.getInstanceVo().getRunId(), body); + } + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/PullHttpTaskStartVo.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/PullHttpTaskStartVo.java new file mode 100644 index 0000000..4d7452b --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/PullHttpTaskStartVo.java @@ -0,0 +1,55 @@ +package com.cisdi.data.DHV.gateway.vo; + +import com.cisdi.data.sdk.gateway.base.PulledHttpGatewayBase; +import com.cisdi.data.sdk.param.PullHttpParam; +import com.cisdi.data.sdk.service.ServiceProvider; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class PullHttpTaskStartVo { + private AtomicBoolean shouldRun; + private ServiceProvider serviceProvider; + private String deviceId; + private PulledHttpGatewayBase gatewayBase; + private PullHttpParam httpParam; + + public AtomicBoolean getShouldRun() { + return shouldRun; + } + + public void setShouldRun(AtomicBoolean shouldRun) { + this.shouldRun = shouldRun; + } + + public ServiceProvider getServiceProvider() { + return serviceProvider; + } + + public void setServiceProvider(ServiceProvider serviceProvider) { + this.serviceProvider = serviceProvider; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public PulledHttpGatewayBase getGatewayBase() { + return gatewayBase; + } + + public void setGatewayBase(PulledHttpGatewayBase gatewayBase) { + this.gatewayBase = gatewayBase; + } + + public PullHttpParam getHttpParam() { + return httpParam; + } + + public void setHttpParam(PullHttpParam httpParam) { + this.httpParam = httpParam; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/AllowMeasuringPoint.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/AllowMeasuringPoint.java new file mode 100644 index 0000000..f409c78 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/AllowMeasuringPoint.java @@ -0,0 +1,33 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + +public enum AllowMeasuringPoint { + //1#XB-10V -02,符合规则的测点 + RegularExpression1("^([\\s]*)([0-9]+)[#]([A-Z]+)([\\s]*)[-]([\\s]*)([0-9]+)([A-Z]+)([\\s]*)([\\s]*)[-]([\\s]*)([0-9]+)([\\s]*)$"), + //1#XB-ZG1 -05,符合规则的测点 + RegularExpression2("^([\\s]*)([0-9]+)[#]([A-Z]+)([\\s]*)[-]([\\s]*)([A-Z]+)([0-9]+)([\\s]*)([\\s]*)[-]([\\s]*)([0-9]+)([\\s]*)$"), + //2#XB-1#GF-05,符合规则的测点 + RegularExpression3("^([\\s]*)([0-9]+)[#]([A-Z]+)([\\s]*)[-]([\\s]*)([0-9]+)[#]([A-Z]+)([\\s]*)([\\s]*)[-]([\\s]*)([0-9]+)([\\s]*)$"); + + private String reg; + + AllowMeasuringPoint(String reg) { + this.reg = reg; + } + + private String getReg() { + return this.reg; + } + + public static boolean allowMeasuringPoint(String mp) { + if (mp == null) { + return true; + } + AllowMeasuringPoint[] allowMeasuringPoints = AllowMeasuringPoint.values(); + for (int i = 0; i < allowMeasuringPoints.length; i++) { + if (mp.matches(allowMeasuringPoints[i].getReg())) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/AllowMethod.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/AllowMethod.java new file mode 100644 index 0000000..b1751b7 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/AllowMethod.java @@ -0,0 +1,40 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + + +public enum AllowMethod { +// tam1("SPM HD"), +// tam2("HD ENV"), +// tam3("振动低频"), +// tam4("振动高频"), + + am1("冲击脉冲频谱"), + am2("振动速度谱"), + am3("冲击脉冲"), + am4("振动速度"), + am5("冲击脉冲谱"), + am6("冲激脉冲频谱"), + am7("振动速度普"); + + private String method; + + AllowMethod(String method) { + this.method = method; + } + + private String getMethod(){ + return this.method; + } + + public static boolean allowMethod(String am) { + if (am == null) { + return false; + } + AllowMethod[] allowMethods=AllowMethod.values(); + for (int i=0;i databaseName; + + public MPSVO(){ + + } + + public List getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(List databaseName) { + this.databaseName = databaseName; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVItem.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVItem.java new file mode 100644 index 0000000..5958cb9 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVItem.java @@ -0,0 +1,27 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + +public enum MPVItem { + measuringPointNumberOrTechID("measuringPointNumberOrTechID"), +// DateTime("DateTime"), + HDm("HDm"), + HDc("HDc"); +// VEL("VEL"), +// DISP("DISP"), +// ACC("ACC"), +// Peak("Peak"), +// PkToPk("PkToPk"); + + private String mpvItem; + + MPVItem(String mpvItem) { + this.mpvItem = mpvItem; + } + + public String getMpvItem() { + return this.mpvItem; + } + + public static MPVItem getPrimary() { + return measuringPointNumberOrTechID; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVRPMItem.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVRPMItem.java new file mode 100644 index 0000000..37b9d40 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVRPMItem.java @@ -0,0 +1,20 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + +public enum MPVRPMItem { +// RPM1("RPM1"), +// RPM2("RPM2"), +// Avg("Avg"), +// Min("Min"), + Max("Max"); +// Delta("Delta"); + + MPVRPMItem(String mpvRPMItem) { + this.mpvRPMItem = mpvRPMItem; + } + + private String mpvRPMItem; + + public String getMpvRPMItem() { + return this.mpvRPMItem; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVVO.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVVO.java new file mode 100644 index 0000000..24bb0b4 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/MPVVO.java @@ -0,0 +1,147 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + +import java.io.Serializable; +import java.util.Date; + +/** + * @author jianghc + * @function: + * @date 2022年05月06日 16:25 + */ +public class MPVVO implements Serializable { + + private String databaseName; + + private String measuringPointNumberOrTechID; + + private String assignmentTechIDorName; + + private Boolean shortMem; + + private Boolean longMem; + + private Integer maxCount; + + private String startDateTime; + + private String stopDateTime; + + private Integer mpktTech; + + private String expand; + + public String getParameter() { + String para = "?placeholder=placeholder";//该参数无意义,用作占位符,方便后面拼接url,统一 &xxx=yyy + if (shortMem != null) { + para = para + ("&ShortMem=" + shortMem); + } else { + para = para + ("&ShortMem=" + true); + } + if (longMem != null) { + para = para + ("&LongMem=" + longMem); + } else { + para = para + ("&LongMem=" + true); + } + if (maxCount != null) { + para = para + ("&MaxCount=" + maxCount); + } +// if (startDateTime != null) { +// para = para + ("&StartDateTime=" + startDateTime); +// +// } +// if (stopDateTime != null) { +// para = para + ("&StartDateTime=" + startDateTime); +// } + if (mpktTech != null) { + para = para + ("&MpktTech=" + mpktTech); + } + if (expand != null) { + para = para + ("&Expand=" + expand); + } + return para; + } + + public MPVVO() { + + } + + public String getDatabaseName() { + return databaseName; + } + + public void setDatabaseName(String databaseName) { + this.databaseName = databaseName; + } + + public String getMeasuringPointNumberOrTechID() { + return measuringPointNumberOrTechID; + } + + public void setMeasuringPointNumberOrTechID(String measuringPointNumberOrTechID) { + this.measuringPointNumberOrTechID = measuringPointNumberOrTechID; + } + + public String getAssignmentTechIDorName() { + return assignmentTechIDorName; + } + + public void setAssignmentTechIDorName(String assignmentTechIDorName) { + this.assignmentTechIDorName = assignmentTechIDorName; + } + + public boolean isShortMem() { + return shortMem; + } + + public void setShortMem(Boolean shortMem) { + this.shortMem = shortMem; + } + + public boolean isLongMem() { + return longMem; + } + + public void setLongMem(Boolean longMem) { + this.longMem = longMem; + } + + public int getMaxCount() { + return maxCount; + } + + public void setMaxCount(Integer maxCount) { + this.maxCount = maxCount; + } + + public String getStartDateTime() { + return startDateTime; + } + + public void setStartDateTime(String startDateTime) { + this.startDateTime = startDateTime; + } + + public String getStopDateTime() { + return stopDateTime; + } + + public void setStopDateTime(String stopDateTime) { + this.stopDateTime = stopDateTime; + } + + public int getMpktTech() { + return mpktTech; + } + + public void setMpktTech(Integer mpktTech) { + this.mpktTech = mpktTech; + } + + public String getExpand() { + return expand; + } + + public void setExpand(String expand) { + this.expand = expand; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/OtherParamVO.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/OtherParamVO.java new file mode 100644 index 0000000..3c45a24 --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/OtherParamVO.java @@ -0,0 +1,56 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + +import java.io.Serializable; +import java.util.List; + +/** + * @author jianghc + * @function: 连接管理中,自定义的其他参数 + * @date 2022年05月05日 09:48 + */ +public class OtherParamVO implements Serializable { + + private MPSVO mpsvo; + + private List mpvvoList; + + String Authorization; + + String emptyanddefault; + + public OtherParamVO(){ + + } + + public MPSVO getMpsvo() { + return mpsvo; + } + + public void setMpsvo(MPSVO mpsvo) { + this.mpsvo = mpsvo; + } + + public List getMpvvoList() { + return mpvvoList; + } + + public void setMpvvoList(List mpvvoList) { + this.mpvvoList = mpvvoList; + } + + public String getAuthorization() { + return Authorization; + } + + public void setAuthorization(String authorization) { + Authorization = authorization; + } + + public String getEmptyanddefault() { + return emptyanddefault; + } + + public void setEmptyanddefault(String emptyanddefault) { + this.emptyanddefault = emptyanddefault; + } +} diff --git a/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/YFDHV.java b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/YFDHV.java new file mode 100644 index 0000000..aa28f6a --- /dev/null +++ b/src/main/java/com/cisdi/data/DHV/gateway/vo/dhv/YFDHV.java @@ -0,0 +1,45 @@ +package com.cisdi.data.DHV.gateway.vo.dhv; + +/** + * @author jianghc + * @function: 设备健康振动数据接入请求接口路径 + * @date 2022/5/6 15:46 + */ +public interface YFDHV { + + + //测点状态接口 + //{databaseName} + String YF_DHV_MPS = "/databases/%s/measuringpoints"; + + + //测点方法接口 + //{dataBaseNamePlaceholder}数据库 + //{measuringPointNumberOrTechID}测点编码 + String YF_DHV_METH = "/databases/%s/measuringpoints/%s/assignments"; + + + //测点总值接口 + //{databaseName} + //{measuringPointNumberOrTechID} + //{assignmentTechIDorName} + String YF_DHV_MPV = "/databases/%s/measuringpoints/%s/assignments/%s/results"; + + + //测点状态电文号=数据库.MPS + String YF_MPS = "MPS"; + + + //测点总值电文号=测点.MPV + String YF_MPV = "MPV"; + + //分隔符,用.作为分隔符 + String YF_Separator = "."; + + //转义字符,split("\\.")时使用 + String YF_EscapeCharacter="\\"; + + //注意测点状态时间是系统生成的,没有从接口中获取到 + String DateTime = "DateTime"; + +} diff --git a/src/main/java/com/cisdi/data/DIClient/DIClientGateway.java b/src/main/java/com/cisdi/data/DIClient/DIClientGateway.java new file mode 100644 index 0000000..df02171 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/DIClientGateway.java @@ -0,0 +1,146 @@ +package com.cisdi.data.DIClient; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.DIClient.bean.DIClientCnfg; +import com.cisdi.data.DIClient.bean.PDIOVo; +import com.cisdi.data.DIClient.gateway.DBReader; +import com.cisdi.data.DIClient.gateway.DBTask; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.google.common.collect.Sets; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class DIClientGateway extends SocketGatewayBase { + private static Logger logger = LoggerFactory.getLogger(DIClientGateway.class); + + private String deviceId; + private DBTask dbTask = null; + + private AtomicLong lastTime=new AtomicLong(0); + private AtomicInteger no=new AtomicInteger(0); + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + ExeResultVo rt = new ExeResultVo(); + + + + PDIOVo pdioVo = new PDIOVo(); + pdioVo.setQueueId(returnMsg.getMsgKey()); + pdioVo.setData(new String(returnMsg.getData())); + Long thisTime=System.currentTimeMillis(); + if(thisTime>lastTime.get()) + { + lastTime.set(thisTime); + no.set(0); + pdioVo.setSerialNo(0); + pdioVo.setTimeStamp(thisTime); + } + else if(thisTime==lastTime.get()) + { + pdioVo.setSerialNo(no.incrementAndGet()); + pdioVo.setTimeStamp(thisTime); + } + else + { + pdioVo.setTimeStamp(lastTime.get()); + pdioVo.setSerialNo(no.incrementAndGet()); + } + + + pdioVo.setHeader("cisdi"); + pdioVo.setStatus("N"); + dbTask.sendMessage(pdioVo); + + rt.setSuccess(true); + rt.setMessage("DIClient下发数据成功,消息体为" + JSON.toJSONString(pdioVo)); + logger.info("DIClient下发数据成功,消息体为" + JSON.toJSONString(pdioVo)); + return rt; + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + String otherParameter = socketParam.getOtherParameter(); + DIClientCnfg diClientCnfg = null; + // 处理分组 + if (StringUtils.isNotBlank(otherParameter)) { + diClientCnfg = JSON.parseObject(otherParameter, DIClientCnfg.class); + } + if (diClientCnfg == null) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为DIClient网关,请配置正确的DIClient配置"); + } + + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为DIClient网关,只允许关联一个设备Id"); + } + + deviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(deviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + logger.info("DIClient网关:{}读取启动配置参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + + boolean open = false; + DBReader dbReader = new DBReader(diClientCnfg.getDriverClassName(), diClientCnfg.getUrl(), diClientCnfg.getUser(), diClientCnfg.getPwd()); + open = dbReader.connectionInternal(0); + if (open == true) { + dbTask = new DBTask(serviceProvider, this, dbReader, diClientCnfg.getCycleTime(), deviceId); + dbTask.start(); + state = GatewayState.RUNNING; + logger.info("DIClient网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } else { + logger.info("DIClient网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + try { + dbTask.close(); + state = GatewayState.CLOSED; + logger.info("网关:" + instanceVo.getRunId() + "关闭成功", instanceVo.getRunId()); + } catch (SQLException e) { + logger.error("网关:" + instanceVo.getRunId() + "关闭失败" + e.getLocalizedMessage()); + } + + } + + @Override + public Set getActiveDeviceIds() { + + Set set = Sets.newConcurrentHashSet(); + + if (StringUtils.isNotEmpty(deviceId)) { + set.add(deviceId); + } + + return set; + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/DIClientTest.java b/src/main/java/com/cisdi/data/DIClient/DIClientTest.java new file mode 100644 index 0000000..35c73b0 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/DIClientTest.java @@ -0,0 +1,52 @@ +package com.cisdi.data.DIClient; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.DIClient.bean.DIClientCnfg; +import com.cisdi.data.DIClient.gateway.DBReader; +import com.cisdi.data.DIClient.gateway.DBTask; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.enums.GatewayState; + +import java.io.UnsupportedEncodingException; + +public class DIClientTest { + public static void main(String[] args) throws UnsupportedEncodingException { + char x=0x03; + String test="20210408195055077MR001 N11001001 yf R一级焦炭 Bt 7.000 2 Y99999.0000 0.0000 0.0000 0.0000 B020 "; + String value="7.000 "; + value=value.trim(); + int splitIndex = value.length() - 3; + + String t="{\"cycleTime\":3000,\"dbName\":\"test\",\"driverClassName\":\"com.mysql.jdbc.Driver\",\"pwd\":\"yLNklB0v6Hu&J2T!\",\"url\":\"jdbc:mysql://10.232.12.58:3306/zgerp?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false\",\"user\":\"root\"} "; + + String prev = value.substring(0, splitIndex-1); + String end = value.substring(splitIndex); + + DIClientCnfg diClientCnfg=new DIClientCnfg(); + diClientCnfg.setCycleTime((long) 3000); + diClientCnfg.setDbName("test"); + diClientCnfg.setDriverClassName("com.mysql.jdbc.Driver"); + diClientCnfg.setUrl("jdbc:mysql://10.232.12.58:3306/zgerp?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false"); + diClientCnfg.setUser("root"); + diClientCnfg.setPwd("yLNklB0v6Hu&J2T!"); + + System.out.println(JSON.toJSONString(diClientCnfg)); + boolean open = false; + DBReader dbReader=new DBReader(diClientCnfg.getDriverClassName(),diClientCnfg.getUrl(),diClientCnfg.getUser(),diClientCnfg.getPwd()); + open=dbReader.connectionInternal(0); + if(open == true) { + DBTask dbTask=new DBTask(null,null,dbReader,diClientCnfg.getCycleTime(),""); + dbTask.start(); + + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("DIClient网关:{}启动成功 参数:{}"); + }else { + System.out.println("DIClient网关:{}启动失败 参数:{}"); + } + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/bean/DBFieldVo.java b/src/main/java/com/cisdi/data/DIClient/bean/DBFieldVo.java new file mode 100644 index 0000000..d42466e --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/bean/DBFieldVo.java @@ -0,0 +1,31 @@ +package com.cisdi.data.DIClient.bean; + +public class DBFieldVo { + private String filedName; + private String as; + private boolean orderBy; + + public String getFiledName() { + return filedName; + } + + public void setFiledName(String filedName) { + this.filedName = filedName; + } + + public String getAs() { + return as; + } + + public void setAs(String as) { + this.as = as; + } + + public boolean isOrderBy() { + return orderBy; + } + + public void setOrderBy(boolean orderBy) { + this.orderBy = orderBy; + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/bean/DBTableTaskCnfg.java b/src/main/java/com/cisdi/data/DIClient/bean/DBTableTaskCnfg.java new file mode 100644 index 0000000..856c521 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/bean/DBTableTaskCnfg.java @@ -0,0 +1,79 @@ +package com.cisdi.data.DIClient.bean; + +import java.util.List; +import java.util.Map; + +public class DBTableTaskCnfg { + private DBTableVo tableName; + private List fieldNames; + private List condition; + private Long cycleTime; + private String taskType; + private String sortType; + private String groupBy; + private String msgKey; + + public String getMsgKey() { + return msgKey; + } + + public void setMsgKey(String msgKey) { + this.msgKey = msgKey; + } + + public DBTableVo getTableName() { + return tableName; + } + + public void setTableName(DBTableVo tableName) { + this.tableName = tableName; + } + + public List getFieldNames() { + return fieldNames; + } + + public void setFieldNames(List fieldNames) { + this.fieldNames = fieldNames; + } + + public List getCondition() { + return condition; + } + + public void setCondition(List condition) { + this.condition = condition; + } + + public Long getCycleTime() { + return cycleTime; + } + + public void setCycleTime(Long cycleTime) { + this.cycleTime = cycleTime; + } + + public String getTaskType() { + return taskType; + } + + public void setTaskType(String taskType) { + this.taskType = taskType; + } + + public String getSortType() { + return sortType; + } + + public void setSortType(String sortType) { + this.sortType = sortType; + } + + public String getGroupBy() { + return groupBy; + } + + public void setGroupBy(String groupBy) { + this.groupBy = groupBy; + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/bean/DBTableVo.java b/src/main/java/com/cisdi/data/DIClient/bean/DBTableVo.java new file mode 100644 index 0000000..78e710b --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/bean/DBTableVo.java @@ -0,0 +1,40 @@ +package com.cisdi.data.DIClient.bean; + +public class DBTableVo { + private DBTableVo leftTable; + private String rightTable; + private String joinType; + private String on; + + public DBTableVo getLeftTable() { + return leftTable; + } + + public void setLeftTable(DBTableVo leftTable) { + this.leftTable = leftTable; + } + + public String getRightTable() { + return rightTable; + } + + public void setRightTable(String rightTable) { + this.rightTable = rightTable; + } + + public String getJoinType() { + return joinType; + } + + public void setJoinType(String joinType) { + this.joinType = joinType; + } + + public String getOn() { + return on; + } + + public void setOn(String on) { + this.on = on; + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/bean/DIClientCnfg.java b/src/main/java/com/cisdi/data/DIClient/bean/DIClientCnfg.java new file mode 100644 index 0000000..c1eae11 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/bean/DIClientCnfg.java @@ -0,0 +1,72 @@ +package com.cisdi.data.DIClient.bean; + +import java.util.List; +import java.util.Map; + +public class DIClientCnfg { + private String dbName; + private String url; + private String user; + private String pwd; + private String driverClassName; + private Long cycleTime; + //TASK名字,TASK筛选条件 +// Map taskMap; + + + public Long getCycleTime() { + return cycleTime; + } + + public void setCycleTime(Long cycleTime) { + this.cycleTime = cycleTime; + } + + public String getDbName() { + return dbName; + } + + public void setDbName(String dbName) { + this.dbName = dbName; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPwd() { + return pwd; + } + + public void setPwd(String pwd) { + this.pwd = pwd; + } + + public String getDriverClassName() { + return driverClassName; + } + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + +// public Map getTaskMap() { +// return taskMap; +// } +// +// public void setTaskMap(Map taskMap) { +// this.taskMap = taskMap; +// } +} diff --git a/src/main/java/com/cisdi/data/DIClient/bean/PDIOVo.java b/src/main/java/com/cisdi/data/DIClient/bean/PDIOVo.java new file mode 100644 index 0000000..9e2780b --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/bean/PDIOVo.java @@ -0,0 +1,76 @@ +package com.cisdi.data.DIClient.bean; + +public class PDIOVo { + private Long timeStamp; + private Integer serialNo; + private String queueId; + private String header; + private String data; + private String status; + private String processTime; + private String description; + + public Long getTimeStamp() { + return timeStamp; + } + + public void setTimeStamp(Long timeStamp) { + this.timeStamp = timeStamp; + } + + public Integer getSerialNo() { + return serialNo; + } + + public void setSerialNo(Integer serialNo) { + this.serialNo = serialNo; + } + + public String getQueueId() { + return queueId; + } + + public void setQueueId(String queueId) { + this.queueId = queueId; + } + + public String getHeader() { + return header; + } + + public void setHeader(String header) { + this.header = header; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getProcessTime() { + return processTime; + } + + public void setProcessTime(String processTime) { + this.processTime = processTime; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/gateway/DBReader.java b/src/main/java/com/cisdi/data/DIClient/gateway/DBReader.java new file mode 100644 index 0000000..84e4bfb --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/gateway/DBReader.java @@ -0,0 +1,215 @@ +package com.cisdi.data.DIClient.gateway; + +import com.cisdi.data.DIClient.bean.PDIOVo; +import com.cisdi.data.common.exception.BusinessException; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.*; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Date; + +public class DBReader +{ + Logger logger= LoggerFactory.getLogger(DBReader.class); + + /** + * JDBC的连接对象实例 + */ + private Connection conn = null; + private String driver; + private String url; + private String username; + private String password; + + public DBReader(String driver, String url, String username, String password) { + this.driver = driver; + this.url = url; + this.username = username; + this.password = password; + } + + public void reconnect() + { + logger.info("由于发生了异常,正在尝试重连"); + try { + if(conn!=null&&!conn.isClosed()) + { + conn.close(); + } + connectionInternal(0); + } catch (SQLException e1) { + e1.printStackTrace(); + } + } + + + public boolean connectionInternal(int reconnectNumber) { + try { + Class.forName(driver); + conn = DriverManager.getConnection(url, username, password); + logger.info("【DI Client】建立与mysql数据库(URL:" + url + ")的连接"); + return true; + } catch (Exception e) { + logger.warn(e.getMessage(), e); + + logger.error("【DI Client】SQL:连接失败,等待重连,剩余重连次数" + reconnectNumber + " 异常信息:" + e.getLocalizedMessage()); + reconnectNumber=reconnectNumber-1; + if (reconnectNumber <= 0) { + logger.error("【DI Client】SQL:重连失败,DIClient连接启动失败"); + return false; + } + try { + Thread.sleep(10000); + } catch (Exception e1) { + logger.info("【DI Client】SQL:等待失败,进程退出"); + } + return this.connectionInternal(reconnectNumber); + } + } + public void close() throws SQLException { + if(conn!=null&&!conn.isClosed()) + { + conn.close(); + } + } + + public int getTotal() + { + String sql = "select count(*) from TBDIPDI where status='N' "; + ResultSet myResult = null; + PreparedStatement pre = null; + try { + pre = conn.prepareStatement(sql); + myResult = pre.executeQuery(); + while (myResult.next()) { + return myResult.getInt("count(*)"); + } + } catch (Exception e) { + logger.error("【DB】SQL语句" + sql + "执行失败,请检查配置文件是否正确,以及数据表是否更改:" + e.getLocalizedMessage(), e); + reconnect(); + } finally { + closeObj(myResult); + closeObj(pre); + } + return 0; + } + + public List getPDIOs(int limit) + { + List rt=new ArrayList<>(); + String sql = "select timestamp,serialno,queueid,header,data,status,processtime,description from TBDIPDI where status='N' order by TIMESTAMP,SerialNo asc limit "+limit; + ResultSet myResult = null; + PreparedStatement pre = null; + try { + pre = conn.prepareStatement(sql); + myResult = pre.executeQuery(); + while (myResult.next()) { + PDIOVo pdioVo=new PDIOVo(); + pdioVo.setTimeStamp(myResult.getLong("timestamp")); + pdioVo.setSerialNo(myResult.getInt("serialno")); + pdioVo.setQueueId(myResult.getString("queueid")); + pdioVo.setHeader(myResult.getString("header")); + pdioVo.setData(myResult.getString("data")); + pdioVo.setStatus(myResult.getString("status")); + pdioVo.setProcessTime(myResult.getString("processtime")); + pdioVo.setDescription(myResult.getString("description")); + rt.add(pdioVo); + } + } catch (Exception e) { + logger.error("【DB】SQL语句" + sql + "执行失败,请检查配置文件是否正确,以及数据表是否更改:" + e.getLocalizedMessage(), e); + reconnect(); + + } finally { + closeObj(myResult); + closeObj(pre); + } + return rt; + } + + private void closeObj(Object obj) { + if (obj == null) { + return; + } + if (obj instanceof PreparedStatement) { + PreparedStatement prepare = (PreparedStatement) obj; + try { + prepare.close(); + return; + } catch (Exception e) { + } + } + if (obj instanceof ResultSet) { + ResultSet resultSet = (ResultSet) obj; + try { + resultSet.close(); + return; + } catch (Exception e) { + } + } + + throw new BusinessException("not support type" + obj.getClass()); + } + + public void updateStatus(List pdioVos,char status,String desc) + { + if(pdioVos==null||pdioVos.size()==0) + { + return; + } + String inStr="("; + for(PDIOVo pdioVo:pdioVos) + { + inStr+="("+pdioVo.getTimeStamp()+","+pdioVo.getSerialNo()+"),"; + } + if(inStr.contains("),")) + { + inStr=inStr.substring(0,inStr.length()-1); + } + inStr+=")"; + List rt=new ArrayList<>(); + + Date date=new Date(); + DateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); + String sql = "update TBDIPDI set status='"+status+"', processtime='"+format.format(date)+"'"+ (StringUtils.isBlank(desc)?"":",description='"+desc+"'") +" where (timestamp,serialno) in "+ inStr; + int myResultCount = 0; + PreparedStatement pre = null; + try { + pre = conn.prepareStatement(sql); + myResultCount = pre.executeUpdate(); + if(myResultCount!=pdioVos.size()) + { + logger.error("【DB】部分数据更新失败,入参"+pdioVos.size()+"条,更新了"+myResultCount+"条"); + } + } catch (Exception e) { + logger.error("【DB】SQL语句" + sql + "执行失败,请检查配置文件是否正确,以及数据表是否更改:" + e.getLocalizedMessage(), e); + reconnect(); + } finally { + closeObj(pre); + } + } + + public void addPDIOs(PDIOVo pdioVos) + { + String sql = "insert into TBDIPDO (TBDIPDO.`TimeStamp`,TBDIPDO.SerialNo,TBDIPDO.QueueId,TBDIPDO.Header,TBDIPDO.`Data`,TBDIPDO.`Status`) value ("+pdioVos.getTimeStamp()+","+pdioVos.getSerialNo()+",'"+pdioVos.getQueueId()+"','"+pdioVos.getHeader()+"','"+pdioVos.getData()+"','"+pdioVos.getStatus()+"')"; + int myResultCount = 0; + PreparedStatement pre = null; + try { + pre = conn.prepareStatement(sql); + myResultCount = pre.executeUpdate(); + if(myResultCount!=1) + { + logger.error("【DB】部分数据插入失败"); + } + } catch (Exception e) { + logger.error("【DB】SQL语句" + sql + "执行失败,请检查配置文件是否正确,以及数据表是否更改:" + e.getLocalizedMessage(), e); + reconnect(); + } finally { + closeObj(pre); + } + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/gateway/DBTask.java b/src/main/java/com/cisdi/data/DIClient/gateway/DBTask.java new file mode 100644 index 0000000..29c8a60 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/gateway/DBTask.java @@ -0,0 +1,137 @@ +package com.cisdi.data.DIClient.gateway; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.DIClient.bean.DBFieldVo; +import com.cisdi.data.DIClient.bean.DBTableTaskCnfg; +import com.cisdi.data.DIClient.bean.PDIOVo; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.service.DynamicCodecService; +import com.cisdi.data.sdk.service.SendService; +import com.cisdi.data.sdk.service.ServiceProvider; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class DBTask extends Thread { + + Logger logger= LoggerFactory.getLogger(DBTask.class); + private ServiceProvider serviceProvider; + private SocketGatewayBase socketGateway; + private DBReader dbReader; + private Long cycleTime; + private boolean shouldRun; + private String deviceId; + private long lastTime=0; + private int lastNo=0; + public DBTask(ServiceProvider serviceProvider, SocketGatewayBase socketGateway, DBReader dbReader,Long cycleTime,String deviceId) { + this.serviceProvider = serviceProvider; + this.socketGateway = socketGateway; + this.dbReader = dbReader; + this.cycleTime=cycleTime; + shouldRun=true; + this.deviceId=deviceId; + } + + public boolean close() throws SQLException { + dbReader.close(); + shouldRun=false; + return true; + } + + public void sendMessage(PDIOVo pdioVo) + { + dbReader.addPDIOs(pdioVo); + } + + @Override + public void run() { + while (shouldRun) + { + long startTime=System.currentTimeMillis(); + while(true) + { + int total=dbReader.getTotal(); + List pdioVoList=dbReader.getPDIOs(2000); + if(pdioVoList!=null&&pdioVoList.size()>0) + { + List successList=new ArrayList<>(); + List errorList=new ArrayList<>(); + String error=""; + if(serviceProvider==null||socketGateway==null) + { + for(PDIOVo pdioVo:pdioVoList) + { + sendMessage(pdioVo); + System.out.println(JSON.toJSONString(pdioVo)); + } + } + else + { + SendService service = (SendService)serviceProvider.getByName(ServiceName.Send); + DynamicCodecService dynamicCodecService=(DynamicCodecService)serviceProvider.getByName(ServiceName.DynamicCodec); + for(PDIOVo pdioVo:pdioVoList) + { + try + { + String paramObj = dynamicCodecService.findByRunIdAndMsgKey(socketGateway.getInstanceVo().getRunId(), pdioVo.getQueueId()); + if(StringUtils.isBlank(paramObj)) + { + continue; + } + } + catch (Exception e) + { + continue; + } + try + { + + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(this.deviceId); + socketMessage.setMsgKey(pdioVo.getQueueId()); + socketMessage.setData(pdioVo.getData().getBytes("GBK")); + socketMessage.getPropsMap().put("charset", Charset.forName("GBK")); + service.sendMessage(socketMessage); + successList.add(pdioVo); + } + catch (Exception e) + { + pdioVo.setDescription(e.getLocalizedMessage()); + errorList.add(pdioVo); + error=e.getLocalizedMessage(); + } + } + if(errorList.size() > 0) + { + dbReader.updateStatus(errorList,'E',error); + } + if(successList.size() > 0) + { + dbReader.updateStatus(successList,'O',""); + } + } + } + if(pdioVoList.size()==0||pdioVoList.size()==total) + { + break; + } + } + long endTime=System.currentTimeMillis(); + try { + if(endTime-startTime<=cycleTime) + { + Thread.sleep(cycleTime-(endTime-startTime)); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/gateway/MysqlPool.txt b/src/main/java/com/cisdi/data/DIClient/gateway/MysqlPool.txt new file mode 100644 index 0000000..6d546d8 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/gateway/MysqlPool.txt @@ -0,0 +1,64 @@ +//package com.cisdi.data.DIClient.gateway; +// +//import org.apache.commons.dbcp2.BasicDataSourceFactory; +// +//import javax.security.auth.login.Configuration; +//import javax.sql.DataSource; +//import java.sql.Connection; +//import java.sql.SQLException; +//import java.util.Properties; +// +//public class MysqlPool { +// private String driver; +// private String url; +// private String username; +// private String password; +// private Long removeTimeOut; +// private int maxTotal; +// +// private DataSource dataSource=null; +// +// public MysqlPool(String driver, String url, String username, String password, Long removeTimeOut, int maxTotal) { +// this.driver = driver; +// this.url = url; +// this.username = username; +// this.password = password; +// this.removeTimeOut = removeTimeOut; +// this.maxTotal = maxTotal; +// } +// +// public boolean init() +// { +// Properties properties=new Properties(); +// properties.setProperty("driverClassName",driver); +// properties.setProperty("url",url); +// properties.setProperty("username",username); +// properties.setProperty("password",password); +// properties.setProperty("initialSize",String.valueOf(maxTotal)); +// properties.setProperty("maxTotal",String.valueOf(maxTotal)); +// properties.setProperty("maxIdle",String.valueOf(maxTotal/2)); +// properties.setProperty("minIdle",String.valueOf(maxTotal/4)); +// properties.setProperty("maxWaitMillis",String.valueOf(1000)); +// properties.setProperty("removeAbandonedTimeout",String.valueOf(removeTimeOut)); +// properties.setProperty("removeAbandonedOnMaintenance",String.valueOf(true)); +// properties.setProperty("removeAbandonedOnBorrow",String.valueOf(true)); +// try { +// dataSource= BasicDataSourceFactory.createDataSource(properties); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return false; +// } +// public Connection getConnection() +// { +// Connection connection=null; +// try +// { +// connection=dataSource.getConnection(); +// connection.setAutoCommit(false); +// } catch (SQLException e) { +// e.printStackTrace(); +// } +// return connection; +// } +//} diff --git a/src/main/java/com/cisdi/data/DIClient/protocol/IXCom29DStringSocketProtocol.java b/src/main/java/com/cisdi/data/DIClient/protocol/IXCom29DStringSocketProtocol.java new file mode 100644 index 0000000..4c74801 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/protocol/IXCom29DStringSocketProtocol.java @@ -0,0 +1,127 @@ +package com.cisdi.data.DIClient.protocol; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.DIClient.protocol.utils.IXComActionParamDTO; +import com.cisdi.data.DIClient.protocol.utils.IXComCodecService; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.procotol.SocketProtocol; +import com.cisdi.data.sdk.procotol.base.ProtocolBase; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.DynamicCodecService; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DecodeVo; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ReturnVo; +import org.apache.commons.lang3.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * @author: lhp + * @date: 2019/10/8 + **/ +public class IXCom29DStringSocketProtocol extends ProtocolBase implements SocketProtocol { + + @Override + public DecodeVo deCode(SocketMessage message) { + if (message == null || message.getRunId() == null || message.getPropsMap() == null || message.getDeviceId() == null) { + throw new BusinessException("解析入参错误1,msg为:" + message.toString()); + } + + DynamicCodecService service = (DynamicCodecService)serviceProvider.getByName(ServiceName.DynamicCodec); + if (service == null || !(service instanceof DynamicCodecService)) { + throw new BusinessException("获取DynamicCodecService失败"); + } + String paramObj = service.findByRunIdAndMsgKey(message.getRunId(), message.getMsgKey()); + Charset charset = Charset.forName(String.valueOf(message.getPropsMap().get("charset"))); + + byte[] data = message.getData(); + + if(data == null || paramObj == null) { + throw new BusinessException("解析入参错误2,msg为:" + message.toString()); + } + + IXComActionParamDTO dto = JSON.parseObject(paramObj, IXComActionParamDTO.class); + dto.setMsgKey(message.getMsgKey()); + if(dto == null || dto.getPropertyList().size() == 0) { + throw new BusinessException("解析入参错误3,msg为:" + message.toString()); + } + + DecodeVo vo = new DecodeVo(); + + IXComCodecService defaultService = new IXComCodecService(); + + Map map = defaultService.decode(message.getRunId(), dto, data, charset); + vo.setData(map); + + return vo; + } + + @Override + public SocketReturnMessage enCode(ReturnVo returnVo) { + + if (returnVo == null || returnVo.getValue() == null || returnVo.getDeviceId() == null) { + throw new BusinessException("解析入参错误1,msg为:" + JSON.toJSONString(returnVo)); + } + + DynamicCodecService service = (DynamicCodecService)serviceProvider.getByName(ServiceName.DynamicCodec); + if (service == null || !(service instanceof DynamicCodecService)) { + throw new BusinessException("获取DynamicCodecService失败"); + } + RouteService routeService = (RouteService)serviceProvider.getByName(ServiceName.Route); + if (service == null || !(routeService instanceof RouteService)) { + throw new BusinessException("获取RouteService失败"); + } + DeviceVo deviceVo=routeService.findDeviceByDeviceId(returnVo.getDeviceId()); + if(deviceVo==null|| StringUtils.isBlank(deviceVo.getRunId())) + { + throw new BusinessException("获取DeviceVo失败"); + } + String paramObj = service.findByRunIdAndMsgKey(deviceVo.getRunId(), returnVo.getMsgKey()); + Charset charset = Charset.forName(String.valueOf(Charset.forName("GBK"))); + IXComActionParamDTO dto = JSON.parseObject(paramObj, IXComActionParamDTO.class); + if(dto == null || dto.getPropertyList().size() == 0) { + throw new BusinessException("解析入参错误3,msg为:" + JSON.toJSONString(returnVo)); + } + + StringBuilder data= new StringBuilder(); + + for(IXComActionParamDTO.PropertyConfig propertyConfig:dto.getPropertyList()) + { + if(!returnVo.getValue().containsKey(propertyConfig.getPropertyId())) + { + throw new BusinessException("解析入参错误4,msg为:" + JSON.toJSONString(returnVo)+"在runId="+deviceVo.getRunId()+";msgKey="+returnVo.getMsgKey()+"的情况下,入参缺失"+propertyConfig.getPropertyId()); + } + StringBuilder tmp= new StringBuilder(String.valueOf(returnVo.getValue().get(propertyConfig.getPropertyId()))); + int tmpLength= 0; + try { + tmpLength = tmp.toString().getBytes("GBK").length; + } catch (UnsupportedEncodingException e) { + throw new BusinessException("解析入参错误5,msg为:" + JSON.toJSONString(returnVo)+"在runId="+deviceVo.getRunId()+";msgKey="+returnVo.getMsgKey()+"的情况下,入参"+propertyConfig.getPropertyId()+"转换GBK失败"+e.getLocalizedMessage()); + } + if(propertyConfig.getFixedLength()!=0&&tmpLengthpropertyConfig.getFixedLength()) + { + throw new BusinessException("解析入参错误6,msg为:" + JSON.toJSONString(returnVo)+"在runId="+deviceVo.getRunId()+";msgKey="+returnVo.getMsgKey()+"的情况下,入参过长"+propertyConfig.getPropertyId()); + } + data.append(tmp); + } + + SocketReturnMessage rt=new SocketReturnMessage(); + rt.setDeviceId(returnVo.getDeviceId()); + rt.setMsgId(returnVo.getMsgId()); + rt.setMsgKey(returnVo.getMsgKey()); + rt.setData(data.toString().getBytes()); + return rt; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComActionParamDTO.java b/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComActionParamDTO.java new file mode 100644 index 0000000..a9bb861 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComActionParamDTO.java @@ -0,0 +1,133 @@ +package com.cisdi.data.DIClient.protocol.utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author: lhp + * @date: 2019/10/8 + **/ + +public class IXComActionParamDTO { + + public enum PropertyType { + BYTE, + BOOLEAN, + SHORT, + INT, + FLOAT, + DOUBLE, + STRING, + LONG, + PLACEHOLDER; + } + + /** + * 属性列表 + */ + private List propertyList; + + /** + * 电文号 + */ + private String msgKey; + + /** + * 是否透传 + */ + private Boolean isPassthrough; + + public Boolean getIsPassthrough() { + return isPassthrough; + } + + public void setIsPassthrough(Boolean isPassthrough) { + this.isPassthrough = isPassthrough; + } + + public String getMsgKey() { + return msgKey; + } + + public void setMsgKey(String msgKey) { + this.msgKey = msgKey; + } + + public void appendProperty(PropertyConfig propertyConfig) { + + if (propertyConfig == null) { + return; + } + + if (propertyList == null) { + propertyList = new ArrayList<>(); + } + propertyList.add(propertyConfig); + } + + public static class PropertyConfig{ + + /** 属性Id*/ + private String propertyId; + + /** 属性类型*/ + private PropertyType propertyType; + + /**固定长度*/ + private Integer fixedLength; + + /**精度*/ + private Integer precision; + + public String getPropertyId() { + return propertyId; + } + + public void setPropertyId(String propertyId) { + this.propertyId = propertyId; + } + + public PropertyType getPropertyType() { + return propertyType; + } + + public void setPropertyType(PropertyType propertyType) { + this.propertyType = propertyType; + } + + public Integer getFixedLength() { + return fixedLength; + } + + public void setFixedLength(Integer fixedLength) { + this.fixedLength = fixedLength; + } + + public Integer getPrecision() { + return precision; + } + + public void setPrecision(Integer precision) { + this.precision = precision; + } + + @Override + public String toString() { + return "PropertyConfig [propertyId=" + propertyId + ", propertyType=" + propertyType + ", fixedLength=" + + fixedLength + ", precision=" + precision + "]"; + } + } + + public List getPropertyList() { + return propertyList; + } + + public void setPropertyList(List propertyList) { + this.propertyList = propertyList; + } + + @Override + public String toString() { + return "IXComActionParamDTO [propertyList=" + propertyList + ", isPassthrough=" + isPassthrough + "]"; + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComCodecService.java b/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComCodecService.java new file mode 100644 index 0000000..943aa67 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComCodecService.java @@ -0,0 +1,141 @@ +package com.cisdi.data.DIClient.protocol.utils; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.service.DynamicCodecService; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +/** + * @author: lhp + * @date: 2019/10/8 + **/ +public class IXComCodecService { + + public Map decode(String runId, IXComActionParamDTO dto, byte[] data, Charset charset) { + int sumLen = 0; + + try{ + sumLen = dto.getPropertyList().stream().mapToInt(IXComActionParamDTO.PropertyConfig::getFixedLength).sum(); + } catch (Exception e) { + throw new BusinessException("连接id:" + String.valueOf(runId) + "电文号:" + String.valueOf(dto.getMsgKey()) + + " ixcom29d解析数据长度失败", e); + } + + if (sumLen != data.length) { + if(dto.getPropertyList().get(dto.getPropertyList().size()-1).getFixedLength()!=0) + { + throw new BusinessException("连接id:" + String.valueOf(runId) + "电文号:" + String.valueOf(dto.getMsgKey()) + + " ixcom29d解析长度与数据长度不一致错误, 解析数据长度为: " + data.length + " 配置长度为: " + sumLen); + } + } + + Map map = new HashMap<>(); + + ByteBuffer byteBuffer = ByteBuffer.wrap(data); + + for(int i=0;i map = new HashMap<>(); + map.put("boolean", true); + map.put("short", Short.valueOf((short) 0xf2)); + map.put("int", Integer.valueOf(0x03)); + map.put("long", Long.valueOf(0xf4L)); + map.put("string",new String("1234567890")); + map.put("double", Double.valueOf(-123123213.3)); + Map map1 = JSON.parseObject(JSON.toJSONString(map)); + // System.out.println(Arrays.toString(service.encode(map, "1","2", "1"))); + // System.out.println(service.encode(map, "1","2","1").length); + //System.out.println(service.decode(service.encode(map, "1","2","1"), "1", "2", "1")); + } +} diff --git a/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComConverterUtils.java b/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComConverterUtils.java new file mode 100644 index 0000000..ca5fda5 --- /dev/null +++ b/src/main/java/com/cisdi/data/DIClient/protocol/utils/IXComConverterUtils.java @@ -0,0 +1,127 @@ +package com.cisdi.data.DIClient.protocol.utils; + +import com.cisdi.data.common.exception.BusinessException; +import org.apache.commons.lang3.StringUtils; + +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + + +/** + * @author: lhp + * @date: 2019/10/8 + **/ +public class IXComConverterUtils { + private static final char blankspace = ' '; + + /** + * 数字数据:则在不满位数时左补0: + 例如:421: “00421”在5位数字值的情况下 + -421: “-0421” + 字符串数据:则在不够长度时右补空格 + 例如:TSB: “TSBDDDD”在7位字符串值的情况 + * @param config + * @param reader + * @return + */ + public static Object convertProperty(IXComActionParamDTO.PropertyConfig config, ByteBuffer reader, Charset charset,String runid,String msgKey) { + + char[] buffer = null; + + try { + byte[] subBody = new byte[config.getFixedLength()]; + reader.get(subBody); + + String subValue = new String(subBody, charset); + buffer = subValue.toCharArray(); + } catch (Exception e) { + throw new BusinessException("ixcom29d读取字符数组失败" + e.getLocalizedMessage(), e); + } + + String value = new String(buffer); + if(config.getPropertyType()!= IXComActionParamDTO.PropertyType.STRING&&StringUtils.isBlank(value)) + { + return null; + } + try + { + switch (config.getPropertyType()) { + case BYTE: + return Byte.valueOf(value.trim()); + case SHORT: + return Short.valueOf(value.trim()); + case INT: + return Integer.valueOf(value.trim()); + case LONG: + return Long.valueOf(value.trim()); + case BOOLEAN: + value = value.trim().replaceFirst("0*", ""); + + if("true".equalsIgnoreCase(value) || "1".equals(value)) { + return true; + }else if("false".equalsIgnoreCase(value) || "0".equals(value)) { + return false; + } + break; + case FLOAT: + case DOUBLE: + if(config.getPrecision() != null) { + value=value.trim(); + BigDecimal decimal=null; + if(config.getPrecision()!=0) + { + int splitIndex = value.length() - config.getPrecision(); + + if(splitIndex < 0) { + throw new BusinessException("config:" + config + ",value:" + value + ",precision error"); + } + + String prev = value.substring(0, splitIndex-1); + String end = value.substring(splitIndex); + + decimal= new BigDecimal(prev + "." + end); + } + else + { + decimal=new BigDecimal(value); + } + + return decimal; + }else { + return new BigDecimal(value); + } + case STRING: + StringBuilder builder = new StringBuilder(); + + int index = buffer.length; + for (int i = buffer.length -1 ; i >= 0; i--) { + + if(buffer[i] == blankspace) { + index = i; + continue; + }else { + break; + } + } + + for (int j = 0; j < index; j++) { + builder.append(buffer[j]); + } + + return builder.toString(); + case PLACEHOLDER: + return null; + default: + return null; + } + } + catch (Exception e) + { + throw new BusinessException("连接id:" + String.valueOf(runid) + "电文号:" + msgKey + + " 解析数据类型错误:属性ID=" + config.getPropertyId()+"属性类型="+config.getPropertyType()+"属性精度="+config.getPrecision()+"属性长度="+config.getFixedLength()+"对应的值="+value+"错误为:"+e.getLocalizedMessage()); + } + + throw new BusinessException(config.toString() + "解析失败,ixcom29d"); + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/YfHj212SocketGateway.java b/src/main/java/com/cisdi/data/HJ212/YfHj212SocketGateway.java new file mode 100644 index 0000000..3fed746 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/YfHj212SocketGateway.java @@ -0,0 +1,138 @@ +package com.cisdi.data.HJ212; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.HJ212.gateway.AliveCheckTask; +import com.cisdi.data.HJ212.gateway.Hj212ChannelInitializer; +import com.cisdi.data.HJ212.gateway.Hj212FrameDecoder; +import com.cisdi.data.HJ212.gateway.Hj212IoSession; +import com.cisdi.data.HJ212.gateway.Hj212SessionFactory; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.google.common.collect.Sets; + +/** + * 扬尘监测网络设备 HJ212协议数据接入 + * @author cup + * + */ +public class YfHj212SocketGateway extends SocketGatewayBase { + private static Logger logger = LoggerFactory.getLogger(YfHj212SocketGateway.class); + private TcpIoService ioService = null; + private SessionFactory sessionFactory = null; + private AtomicBoolean shouldRun = null; + private String deviceId; + + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + throw new BusinessException("hj212网关不支持下发命令"); + } + + @Override + public void start() { + if(state == GatewayState.RUNNING) { + return; + } + + logger.info("hj212网关:{}读取启动配置参数:{}",instanceVo.getRunId(), getInstanceVo().getParameter()); + + RouteService routeService = (RouteService)serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if(deviceVos.size() > 1) { + throw new BusinessException("网关Id:" +instanceVo.getRunId() + "为hj212网关,只允许关联一个设备Id"); + } + + deviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if(StringUtils.isEmpty(deviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new Hj212SessionFactory(deviceId); + factory.init(serviceProvider, this); + + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + Hj212ChannelInitializer channelInitializer = + new Hj212ChannelInitializer(factory); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if(open == true) { + state = GatewayState.RUNNING; + logger.info("hj212网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + shouldRun = new AtomicBoolean(true); + + Integer keepAlive = socketParam.getKeepAlive(); + + if(keepAlive == null || keepAlive < 120) { + keepAlive = 120; + }else if (keepAlive > 3600) { + keepAlive = 3600; + } + + AliveCheckTask task = new AliveCheckTask(keepAlive, shouldRun, factory); + + Thread thread = new Thread(task, "hj212-alive-check-thread-" + getInstanceVo().getRunId()); + thread.start(); + }else { + logger.info("hj212网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + } + + @Override + public void shutdown() { + if(state == GatewayState.CLOSED) { + return; + } + + if(sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + Hj212IoSession imsIoSession = (Hj212IoSession)ioSession; + imsIoSession.close(); + } + } + + boolean close = ioService.close(); + if(close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + logger.info("hj212网关:{}关闭成功", instanceVo.getRunId()); + }else { + logger.info("hj212网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds(){ + Set set = Sets.newHashSet(); + set.add(deviceId); + return set; + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/AliveCheckTask.java b/src/main/java/com/cisdi/data/HJ212/gateway/AliveCheckTask.java new file mode 100644 index 0000000..2266ef4 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/AliveCheckTask.java @@ -0,0 +1,68 @@ +package com.cisdi.data.HJ212.gateway; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; + +/** + * 保活时间检测,如果超过指定时间,则关闭连接 + * @author cup + * + */ +public class AliveCheckTask implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(AliveCheckTask.class); + + private SessionFactory sessionFactory; + private AtomicBoolean shouldRun = null; + + /** + * 会话允许的保活时间,单位秒 + */ + private int keepAlive; + + private static final double multiply = 1.5; + + private static final int sleepInternal = 1000; // 1秒 + + public AliveCheckTask(int keepAlive, AtomicBoolean shouldRun, SessionFactory sessionFactory) { + super(); + this.keepAlive = keepAlive; + this.shouldRun = shouldRun; + this.sessionFactory = sessionFactory; + } + + @Override + public void run() { + logger.info("启动hj212保活超时检测线程"); + + while (shouldRun != null && shouldRun.get() == true) { + try { + long now = System.currentTimeMillis(); + + List sessions = sessionFactory.getSessions(); + + for (IoSession ioSession : sessions) { + Hj212IoSession session = (Hj212IoSession)ioSession; + + // 超出指定倍数保活时间, + if((now - session.getLastAliveTime()) > (multiply * keepAlive * 1000)) { + session.close(); + logger.warn("{} 超出指定倍数{}保活时间{},单位秒,关闭通道", + session.gwPrefix(), multiply, keepAlive); + } + } + + Thread.sleep(sleepInternal); + } catch (Exception e) { + logger.warn(e.getLocalizedMessage(), e); + } + } + + logger.info("结束hj212保活超时检测线程"); + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/CRC16Util.java b/src/main/java/com/cisdi/data/HJ212/gateway/CRC16Util.java new file mode 100644 index 0000000..3f4a358 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/CRC16Util.java @@ -0,0 +1,48 @@ +package com.cisdi.data.HJ212.gateway; + +import java.nio.charset.Charset; + +public class CRC16Util { + public static final Charset UTF8 = Charset.forName("UTF-8"); + + /** + * HJ212污染监测 CRC16校验算法 + * + * @param dataBody + * @return CRC16校验码 + */ + public static String CRC16(String dataBody) { + Integer[] regs = new Integer[dataBody.length()]; + for (int i = 0; i < dataBody.length(); i++) { + regs[i] = (int)dataBody.charAt(i); + } + int por = 0xFFFF; + for (int j = 0; j < regs.length; j++) { + por = por >> 8; + por ^= regs[j]; + for (int i = 0; i < 8; i++) { + if ((por & 0x01) == 1) { + por = por >> 1; + por = por ^ 0xa001; + } else + por = por >> 1; + } + } + String result = Integer.toHexString(por).toUpperCase(); + + while (result.length() < 4) { + result = "0" + result; + + } + return result; + } + + public static void main(String[] args) { + String body1 = + "QN=20160801085857223;ST=32;CN=1062;PW=100000;MN=010000A8900016F000169DC0;Flag=5;CP=&&RtdInterval=30&&"; + System.out.println(CRC16(body1)); // should be 1C80 + + String body2 = "QN=20120416225111069;ST=91;CN=9021;PW=122333;MN=88888880000006;Flag=1;CP=&&&&"; + System.out.println(CRC16(body2)); // should be 4100 + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212ChannelInitializer.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212ChannelInitializer.java new file mode 100644 index 0000000..26a91ff --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212ChannelInitializer.java @@ -0,0 +1,48 @@ +package com.cisdi.data.HJ212.gateway; + +import java.util.ArrayList; +import java.util.List; + +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.handler.timeout.IdleStateHandler; + +public class Hj212ChannelInitializer extends AbstractChannelInitializer { + private SessionFactory factory; + + /** + * 构造通道初始化器对象 + * @param factory 会话工厂 + */ + public Hj212ChannelInitializer(SessionFactory factory) { + this.factory = factory; + } + + @SuppressWarnings("unchecked") + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + handlers.add(new Hj212FrameDecoder()); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212FrameDecoder.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212FrameDecoder.java new file mode 100644 index 0000000..03a35b2 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212FrameDecoder.java @@ -0,0 +1,89 @@ +package com.cisdi.data.HJ212.gateway; + +import com.cisdi.data.common.exception.BusinessException; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + *

Title: 永峰HJ212协议解码

+ *

Description: 得到一个完整报文长度

+ * + * @author lhp + * @version 1.0 + * @date 2021.05.13 + */ +public class Hj212FrameDecoder extends ByteToMessageDecoder { + Logger logger = LoggerFactory.getLogger(Hj212FrameDecoder.class); + // 包头2+数据段长度4+数据段0+CRC校验4+包尾2 + private static final short Min_Length = 12; + private static final short Max_Length = 8192; + private Charset charset = StandardCharsets.UTF_8; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bufferIn, List out) throws Exception { + try { + if (bufferIn.readableBytes() < Min_Length) { + return; + } + + final int beginIndex = bufferIn.readerIndex(); + + byte[] headerBytes = new byte[6]; + bufferIn.readBytes(headerBytes); + + String header = new String(headerBytes, charset); + + // 解析headerString + String lendthString = getString(header, 2, 6); + + // 数据段长度 + int length = Integer.valueOf(lendthString); + + // 数据段长度+其他长度才是总的长度 + length += 12; + + if (length > Max_Length) { + ctx.close(); + throw new BusinessException("超过协议允许最大报文长度,默认为 " + Max_Length + " byte,当前为" + length + "byte"); + } + + if (length < Min_Length) { + ctx.close(); + throw new BusinessException(ctx.channel().toString() + "传输数据体长度小于12, 此长度为:" + length); + } + + if (bufferIn.readableBytes() < length - 6) { // 拆包 + bufferIn.readerIndex(beginIndex); + return; + } + + bufferIn.readerIndex(beginIndex); + + ByteBuf otherByteBufRef = bufferIn.retainedSlice(beginIndex, length); + + bufferIn.readerIndex(beginIndex + length); + + out.add(otherByteBufRef); + + // 处理可能的粘包 + decode(ctx, bufferIn, out); + } catch (Exception e) { + logger.error("拆包过程出现异常,原始消息为:{}", bufferIn.toString(charset)); + } + + } + + private String getString(String header, int start, int end) { + char[] dst = new char[end - start]; + header.getChars(start, end, dst, 0); + + return new String(dst); + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212FrameInputVo.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212FrameInputVo.java new file mode 100644 index 0000000..de755cc --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212FrameInputVo.java @@ -0,0 +1,82 @@ +package com.cisdi.data.HJ212.gateway; + +import java.util.Map; + +public class Hj212FrameInputVo { + /** 请求编码 精确到毫秒的时间戳:QN=YYYYMMDDhhmmsszzz,用来唯一标识一次命令交互 */ + private String qn; + /** 系统编码 */ + private String st; + /** 命令编码,可以作为电文号 */ + private String cn; + /** 访问密码 */ + private String pw; + /** 设备唯一标识MN */ + private String mn; + /** 拆分包及应答标志 */ + private Integer flag; + /** 指令参数CP */ + private String cp; + /** cp 中包含的 DataTime字段 */ + private Long dataTime; + + /** + * 通过命令上传的数据 + */ + private Map dataMap; + + public String getQn() { + return qn; + } + public void setQn(String qn) { + this.qn = qn; + } + public String getSt() { + return st; + } + public void setSt(String st) { + this.st = st; + } + public String getCn() { + return cn; + } + public void setCn(String cn) { + this.cn = cn; + } + public String getPw() { + return pw; + } + public void setPw(String pw) { + this.pw = pw; + } + public String getMn() { + return mn; + } + public void setMn(String mn) { + this.mn = mn; + } + public Integer getFlag() { + return flag; + } + public void setFlag(Integer flag) { + this.flag = flag; + } + public String getCp() { + return cp; + } + public void setCp(String cp) { + this.cp = cp; + } + public Long getDataTime() { + return dataTime; + } + public void setDataTime(Long dataTime) { + this.dataTime = dataTime; + } + public Map getDataMap() { + return dataMap; + } + public void setDataMap(Map dataMap) { + this.dataMap = dataMap; + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212IoSession.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212IoSession.java new file mode 100644 index 0000000..2434cc2 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212IoSession.java @@ -0,0 +1,124 @@ +package com.cisdi.data.HJ212.gateway; + +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoSession; +import com.cisdi.data.sdk.service.SendService; +import com.google.common.collect.Maps; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * +*

Title: Hj212IoSession.java

+* @author lhp +* @date 2021年05月13日 +* @version 1.0 + */ +public class Hj212IoSession extends AbstractIoSession implements IoSession { + private long lastAliveTime = System.currentTimeMillis(); + private static Logger logger = LoggerFactory.getLogger(Hj212IoSession.class); + private String deviceId = null; + + public Hj212IoSession(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public void onRead(Object message) { + int length = ((ByteBuf)message).readableBytes(); + ByteBuf inBuf = (ByteBuf)message; + + Hj212FrameInputVo vo = Hj212VoDecode.Decode(inBuf); + + if(socketGateway!= null && Boolean.TRUE.equals(socketGateway.getInstanceVo().getLogOpen())) { + logger.info("hj212 {} 报文长度:{} vo:{} ", getChannel(), length, JSON.toJSONString(vo)); + } + //发送的真正电文数据 + Map realDataMap = Maps.newHashMap(vo.getDataMap()); + realDataMap.put("dataTime", vo.getDataTime()); + realDataMap.put("mn", vo.getMn()); + realDataMap.put("qn", vo.getQn()); + realDataMap.put("cn", vo.getCn()); + String msgKey = String.format("%s_%s", vo.getMn(), vo.getCn()); + + if(vo != null && serviceProvider != null && socketGateway != null) { + SendService service = (SendService)serviceProvider.getByName(ServiceName.Send); + + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(deviceId); + + socketMessage.setData(JSON.toJSONString(realDataMap).getBytes(CRC16Util.UTF8)); + socketMessage.setMsgKey(msgKey); + + service.sendMessage(socketMessage); + }else { + logger.info("hj212收到 msgkey: {} 消息:{}",msgKey, JSON.toJSONString(realDataMap)); + } + + byte[] backBodys = Hj212VoEncoder.encode(vo.getSt(), vo.getCn(), vo.getPw(), vo.getMn()); + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(backBodys); + + getChannel().writeAndFlush(wrappedBuffer); + lastAliveTime = System.currentTimeMillis(); + } + + @Override + public void onOpen() { + super.onOpen(); + logger.info("建立连接,channel:{}", getChannel()); + } + + @Override + public void onClose() { + super.onClose(); + } + + public void close() { + if(getChannel() != null) { + try { + getChannel().close().sync(); + } catch (InterruptedException e) { + logger.warn(e.getLocalizedMessage(), e); + } + } + } + + private String gwPrefixCache = null; + + public String gwPrefix() { + if(gwPrefixCache != null) { + return gwPrefixCache; + } + + gwPrefixCache = ""; + if(socketGateway != null && socketGateway.getInstanceVo() != null) { + gwPrefixCache = "网关Id:" + socketGateway.getInstanceVo().getRunId() + "连接:" + getChannel() + " "; + }else { + gwPrefixCache = "连接:" + getChannel() + " "; + } + + return gwPrefixCache; + } + + public long getLastAliveTime() { + return lastAliveTime; + } + + @Override + public String[] getDeviceIds() { + String cpDeviceId = deviceId; + String[] result = new String[cpDeviceId == null ? 0 : 1]; + if(cpDeviceId != null) { + result[0] = cpDeviceId; + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212SessionFactory.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212SessionFactory.java new file mode 100644 index 0000000..e11ff79 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212SessionFactory.java @@ -0,0 +1,22 @@ +package com.cisdi.data.HJ212.gateway; + +import java.util.UUID; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; + +public class Hj212SessionFactory extends AbstractSessionFactory { + private String deviceId; + + public Hj212SessionFactory(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new Hj212IoSession(deviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212VoDecode.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212VoDecode.java new file mode 100644 index 0000000..fd40334 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212VoDecode.java @@ -0,0 +1,117 @@ +package com.cisdi.data.HJ212.gateway; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.tomcat.util.buf.HexUtils; +import org.apache.tomcat.util.buf.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.common.utils.Strings; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class Hj212VoDecode { + private static final Logger log = LoggerFactory.getLogger(Hj212VoDecode.class); + + public static Hj212FrameInputVo Decode(ByteBuf in) { + byte[] bytes = new byte[in.readableBytes()]; + in.readBytes(bytes); + String message = new String(bytes, CRC16Util.UTF8); + + String body = message.substring(6, message.length() - 6); + String end = message.substring(message.length() - 6, message.length()); + + String crcParameter = end.substring(0, 4); + String crcResult = CRC16Util.CRC16(body); + + if(Strings.equalsIgnoreCase(crcParameter, crcResult) == false){ + log.error("hj212 crc16 check fail, input:{}, input-crc:{},cal-crc:{}", body, crcParameter, crcResult); + throw new BusinessException("crc16 check fail input-crc=" + crcParameter); + } + // 解析body字段 + // 字段与其值用‘=’连接;在数据区中,同一项目的不同分类值间用‘,’来分隔,不同项目之间用‘;’来分隔。 + Map map = new HashMap(); + + String[] bodyItems = Strings.split(body.substring(0,body.length()-2), ';'); + for (String item : bodyItems) { + if(item.contains(",")) { + String[] itemBodys = Strings.split(item, ','); + for (String subItem : itemBodys) { + if(subItem.contains("=")) { + int indexOp = subItem.indexOf('='); + map.put(subItem.substring(0, indexOp), subItem.substring(indexOp + 1)); + } + } + }else if(item.contains("=")) { + int indexOp = item.indexOf('='); + map.put(item.substring(0, indexOp), item.substring(indexOp + 1)); + } + } + + Hj212FrameInputVo value = new Hj212FrameInputVo(); + + value.setCn(map.get("CN")); + value.setQn(map.get("QN")); + value.setSt(map.get("ST")); + value.setPw(map.get("PW")); + value.setMn(map.get("MN")); + if(map.containsKey("Flag")){ + value.setFlag(Integer.valueOf(map.get("Flag"))); + } + value.setCp(map.get("CP").substring(2)); + + // &&DataTime=20190425110800 + int indexDt = value.getCp().indexOf("DataTime"); + if(indexDt >= 0) { + value.setDataTime(Long.valueOf(value.getCp().substring(indexDt + 9, indexDt + 9 + 14))); + } + + map.remove("CN"); + map.remove("QN"); + map.remove("ST"); + map.remove("PW"); + map.remove("MN"); + map.remove("Flag"); + map.remove("CP"); + + value.setDataMap(map); + + return value; + } + + public static void main(String[] args) { + // 实时数据 +// String input = "##0365QN=20190425110900000;ST=22;CN=2011;PW=123456;MN=01004190001003;Flag=5;CP=&&DataTime=20190425110800;a01001-Rtd=0.0,a01001-Flag=N;a01002-Rtd=0.0,a01002-Flag=N;a01006-Rtd=101.09,a01006-Flag=N;a01007-Rtd=0.0,a01007-Flag=N;a01008-Rtd=0,a01008-Flag=N;a34001-Rtd=0.0,a34001-Flag=N;a34002-Rtd=17.8,a34002-Flag=N;a34004-Rtd=7.7,a34004-Flag=N;a50001-Rtd=30.0,a50001-Flag=N;&&0880\r\n"; +// String input="##0102QN=20211207181139971;ST=31;CN=1011;PW=123456;MN=YFLGZXJCLT0003;Flag=1;CP=&&SystemTime=20211207181139&&4980\r\n"; +// String input="##0453QN=20211207181137957;ST=31;CN=2051;PW=123456;MN=YFLGZXJCLT0003;CP=&&DataTime=20211207180000;01-ZsAvg=1.38,01-ZsMin=1.25,01-ZsMax=1.54,01-Cou=0.18,01-Avg=1.38,01-Min=1.25,01-Max=1.54;B02-Cou=130926.47,B02-Avg=218.21,B02-Min=206.41,B02-Max=230.95;S03-Avg=16.38,S03-Min=16.30,S03-Max=16.44;S05-Avg=2.31,S05-Min=2.31,S05-Max=2.32;S08-Avg=-0.16,S08-Min=-0.17,S08-Max=-0.14;S02-Avg=12.08,S02-Min=11.42,S02-Max=12.79;S01-Avg=21.17,S01-Min=21.16,S01-Max=21.18&&5280\r\n"; +// String input="##0279QN=20211207181240386;ST=31;CN=2011;PW=123456;MN=YFLGZXJCLT0003;CP=&&DataTime=20211207181148;01-ZsRtd=1.32,01-Rtd=1.32,01-Flag=N;B02-Rtd=225.99,B02-Flag=N;S03-Rtd=16.29,S03-Flag=N;S05-Rtd=2.31,S05-Flag=N;S08-Rtd=-0.16,S08-Flag=N;S02-Rtd=12.51,S02-Flag=N;S01-Rtd=21.16,S01-Flag=N&&7940\r\n"; +// String input="##0275QN=20211208195054384;ST=31;CN=2011;PW=123456;MN=SD371300004744;CP=&&DataTime=20211208194952;01-ZsRtd=0.52,01-Rtd=0.52,01-Flag=N;B02-Rtd=38.10,B02-Flag=N;S03-Rtd=8.66,S03-Flag=N;S05-Rtd=1.23,S05-Flag=N;S08-Rtd=0.00,S08-Flag=N;S02-Rtd=1.20,S02-Flag=N;S01-Rtd=21.61,S01-Flag=N&&4040\r\n"; +// String input="##0102QN=20211208194951184;ST=31;CN=1011;PW=123456;MN=SD371300004744;Flag=1;CP=&&SystemTime=20211208194951&&7F80\r\n"; +// String input="##0275QN=20211208194952060;ST=31;CN=2011;PW=123456;MN=SD371300004744;CP=&&DataTime=20211208194853;01-ZsRtd=0.52,01-Rtd=0.52,01-Flag=N;B02-Rtd=38.09,B02-Flag=N;S03-Rtd=8.65,S03-Flag=N;S05-Rtd=1.26,S05-Flag=N;S08-Rtd=0.00,S08-Flag=N;S02-Rtd=1.20,S02-Flag=N;S01-Rtd=21.61,S01-Flag=N&&5000\r\n"; + String input="##0441QN=20211208195157432;ST=31;CN=2051;PW=123456;MN=SD371300004744;CP=&&DataTime=20211208194000;01-ZsAvg=0.52,01-ZsMin=0.52,01-ZsMax=0.52,01-Cou=0.01,01-Avg=0.52,01-Min=0.52,01-Max=0.52;B02-Cou=22469.20,B02-Avg=37.45,B02-Min=34.90,B02-Max=38.10;S03-Avg=8.66,S03-Min=8.65,S03-Max=8.68;S05-Avg=1.27,S05-Min=1.23,S05-Max=1.29;S08-Avg=0.00,S08-Min=-0.01,S08-Max=0.00;S02-Avg=1.18,S02-Min=1.10,S02-Max=1.20;S01-Avg=21.61,S01-Min=21.60,S01-Max=21.62&&F9C1\r\n"; + ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(input.getBytes(CRC16Util.UTF8)); + + Hj212FrameInputVo decode = Hj212VoDecode.Decode(wrappedBuffer); + wrappedBuffer.release(); + System.out.println(JSON.toJSONString(decode)); + +// String inputDay = "##0638QN=20190425104000000;ST=22;CN=2051;PW=123456;MN=0100410001003;Flag=5;CP=&&DataTime=20190425104000;a01001-Min=0.0,a01001-Avg=0.0,a01001-Max=0.0,a01001-Flag=N;a01002-Min=0.0,a01002-Avg=0.0,a01002-Max=0.0,a01002-Flag=N;a01006-Min=0.00,a01006-Avg=101.08,a01006-Max=101.08,a01006-Flag=N;a01007-Min=0.0,a01007-Avg=0.0,a01007-Max=0.0,a01007-Flag=N;a01008-Min=0,a01008-Avg=0,a01008-Max=0,a01008-Flag=N;a34001-Min=0.0,a34001-Avg=0.0,a34001-Max=0.0,a34001-Flag=N;a34002-Min=0.0,a34002-Avg=21.6,a34002-Max=35.2,a34002-Flag=N;a34004-Min=0.0,a34004-Avg=10.8,a34004-Max=22.7,a34004-Flag=N;a50001-Min=0.0,a50001-Avg=30.0,a50001-Max=30.0,a50001-Flag=N;&&5B40\r\n"; +// wrappedBuffer = Unpooled.wrappedBuffer(inputDay.getBytes(CRC16Util.UTF8)); +// decode = Hj212VoDecode.Decode(wrappedBuffer); +// wrappedBuffer.release(); +// System.out.println(JSON.toJSONString(decode)); + + +// // 实时数据 +// String inputTest = "##0365QN=20190425110900000;ST=22;CN=2011;PW=123456;MN=01004190001003;Flag=5;CP=&&DataTime=20190425110800;a01001-Rtd=0.0,a01001-Flag=N;a01002-Rtd=0.0,a01002-Flag=N;a01006-Rtd=101.09,a01006-Flag=N;a01007-Rtd=0.0,a01007-Flag=N;a01008-Rtd=0,a01008-Flag=N;a34001-Rtd=0.0,a34001-Flag=N;a34002-Rtd=17.8,a34002-Flag=N;a34004-Rtd=7.7,a34004-Flag=N;a50001-Rtd=30.0,a50001-Flag=N;&&0880\r\n"; +// byte[] inputTestBytes = inputTest.getBytes(CRC16Util.UTF8); +// String hexString = HexUtils.toHexString(inputTestBytes); +// System.out.print(hexString); + + } +} diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/Hj212VoEncoder.java b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212VoEncoder.java new file mode 100644 index 0000000..9e208c3 --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/Hj212VoEncoder.java @@ -0,0 +1,47 @@ +package com.cisdi.data.HJ212.gateway; + +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class Hj212VoEncoder { + private static DateFormat qnFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + + public static byte[] encode(String st, String cn, String pw, String mn) { + String flag = "4"; + + String asciiString = combineAsciiString(null, st, cn, pw, mn, flag); + + return asciiString.getBytes(StandardCharsets.US_ASCII); + } + + private static String combineAsciiString(String qn, String st, String cn, String pw, String mn, String flag) { + Date date = new Date(); + StringBuilder builder = new StringBuilder(); + builder.append(String.format("QN=%s;", qn == null ? qnFormat.format(date) : qn)); + builder.append(String.format("ST=%s;", st)); + builder.append(String.format("CN=%s;", cn)); + builder.append(String.format("PW=%s;", pw)); + builder.append(String.format("MN=%s;", mn)); + builder.append(String.format("Flag=%s;", flag)); + builder.append("CP=&&&&"); + + String dataBody = builder.toString(); + int dataLength = builder.toString().length(); + + builder.insert(0, String.format("##%04d", dataLength)); + String crc16 = CRC16Util.CRC16(dataBody); + builder.append(crc16 + "\r\n"); + + return builder.toString(); + } + + public static void main(String[] args) { + String combine1 = combineAsciiString("20190425110917290", "22", "9014", "123456", "01004190001003", "4"); + System.out.println(combine1); + + String combine2 = combineAsciiString("20190425110913469", "22", "9014", "123456", "0100410001003", "4"); + System.out.println(combine2); + } +} \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/HJ212/gateway/StartHj212Test.java b/src/main/java/com/cisdi/data/HJ212/gateway/StartHj212Test.java new file mode 100644 index 0000000..279d82d --- /dev/null +++ b/src/main/java/com/cisdi/data/HJ212/gateway/StartHj212Test.java @@ -0,0 +1,62 @@ + package com.cisdi.data.HJ212.gateway; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cisdi.data.HJ212.YfHj212SocketGateway; +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.vo.GatewayVo; + +public class StartHj212Test { + private static Logger logger = LoggerFactory.getLogger(YfHj212SocketGateway.class); + + private static TcpIoService ioService = null; + @SuppressWarnings("unused") + private static SessionFactory sessionFactory = null; + + public static void main(String[] args) { + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new Hj212SessionFactory("10000022"); + + factory.init(null, null); + + Hj212ChannelInitializer channelInitializer = + new Hj212ChannelInitializer(factory); + + GatewayVo instanceVo = new GatewayVo(); + instanceVo.setByteOrder(ByteOrder.BIGENDIAN); + instanceVo.setParameter("{\"listenIp\":\"0.0.0.0\",\"listenPort\":9010,\"threadSize\":50,\"timeout\":2000}"); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if(open == true) { + + AtomicBoolean shouldRun = new AtomicBoolean(true); + AliveCheckTask task = new AliveCheckTask(120, shouldRun, factory); + + Thread thread = new Thread(task, "Hj212-alive-check-thread"); + thread.start(); + + logger.info("Hj212网关启动成功"); + }else { + logger.info("Hj212网关启动失败"); + } + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/IDCardReaderGateway.java b/src/main/java/com/cisdi/data/IDCardReader/IDCardReaderGateway.java new file mode 100644 index 0000000..8a51646 --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/IDCardReaderGateway.java @@ -0,0 +1,100 @@ +package com.cisdi.data.IDCardReader; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.IDCardReader.gateway.impl.ZKIDROnlineClient; +import com.cisdi.data.IDCardReader.gateway.impl.ZKIDROnlineListener; +import com.cisdi.data.IDCardReader.gateway.vo.ZKIDROnlineConfigVo; +import com.cisdi.data.IDCardReader.gateway.vo.ZKIDROnlineResponseVo; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.emserver.EmiaProSocketGateway; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.google.common.collect.Sets; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; + +public class IDCardReaderGateway extends SocketGatewayBase { + + private static Logger logger = LoggerFactory.getLogger(IDCardReaderGateway.class); + private ZKIDROnlineClient zkidrOnlineClient=null; + private String deviceId; + + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + ExeResultVo resultVo = new ExeResultVo(); + resultVo.setMessage("该协议不允许下发数据"); + resultVo.setSuccess(false); + logger.info("网关:{}->设备,消息:{},结果:{}",instanceVo.getRunId(), returnMsg, resultVo); + return resultVo; + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + String otherParameter = socketParam.getOtherParameter(); + ZKIDROnlineConfigVo zkidrOnlineConfigVo = null; + // 处理分组 + if (StringUtils.isNotBlank(otherParameter)) { + zkidrOnlineConfigVo = JSON.parseObject(otherParameter, ZKIDROnlineConfigVo.class); + } + if (zkidrOnlineConfigVo == null) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为ZKIDROnline网关,请配置正确的ZKIDROnline配置"); + } + + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为ZKIDROnline网关,只允许关联一个设备Id"); + } + + deviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(deviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + logger.info("ZKIDROnline网关:{}读取启动配置参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + zkidrOnlineClient=new ZKIDROnlineClient(deviceId,serviceProvider,this,zkidrOnlineConfigVo); + Thread thread=new Thread(zkidrOnlineClient); + thread.start(); + state = GatewayState.RUNNING; + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + if(zkidrOnlineClient.close()) + { + state=GatewayState.CLOSED; + logger.info("网关:" + instanceVo.getRunId() + "关闭成功", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds() { + Set set = Sets.newConcurrentHashSet(); + + if (StringUtils.isNotEmpty(deviceId)) { + set.add(deviceId); + } + + return set; + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/gateway/IDCardReaderTest.java b/src/main/java/com/cisdi/data/IDCardReader/gateway/IDCardReaderTest.java new file mode 100644 index 0000000..de09eda --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/gateway/IDCardReaderTest.java @@ -0,0 +1,35 @@ +package com.cisdi.data.IDCardReader.gateway; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.IDCardReader.gateway.impl.ZKIDROnlineClient; +import com.cisdi.data.IDCardReader.gateway.vo.IDCardVo; +import com.cisdi.data.IDCardReader.gateway.vo.ZKIDROnlineConfigVo; +import com.cisdi.data.sdk.enums.GatewayState; + +import java.io.UnsupportedEncodingException; + +public class IDCardReaderTest { + public static void main(String[] args) throws UnsupportedEncodingException + { + IDCardVo idCardVo=null; + try + { + idCardVo= JSON.parseObject("{\"Nation\":\"汉\",\"CardNumber\":\"\",\"Base64Photo\":\"/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAB+AGYDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD36iiigAo6DJo6DJrx74m/Ew2zPomiy4c5E9yh6f7K+/v/AJDSuB6JrHjLQNCVvt2pQq6jPlod7/kK88v/AI3QpcSLY2LPED8ju2Mj6YNeKSzSSuSxJ9T61ESQeKpRQHrh+N+o5bFlD7DP/wBatjRfjXbSzpHq9s0SkcvF82PwxXheT0pQcGnZCPrTQ/Fui+Ilb+zrxXZeqN8rfka26+PLG+uLC7ju7WVo5423I6nBBr3v4a/EJ/Ecf9mamR/aEa5WTtKP8f8AI9pcQuekUUUVIwooooAKKKKAPOvih40u/D1gbGwVVuLhMecSMoD6D1r5/jikuXMkhLMxznrXd/FiZrjxlPFkkRKBzVHw1o63Nt5jDoRSlLlVzSELs50aXLJ9xGP1FSL4fvG/5ZV6fa6VCmPlz9RV77Gin5RisHWZ0qgup43caLdwE5hP5VU+xy45jb8q9teyjk++uR6VEdAspEJ8lfyoVZieHXQ8WEbDgiremXk+l6lBe2z7JonDKff0+h6V3mq+FLcLI0KYPbArgLm1ltrgxuOVPNbwqXMJ0uU+s9F1AatotnfqpUXESvg9eavVx/wxlaXwHYFiSVyvJ9K7CmzIKKKKACiiigDwn4oaYg8ZyyRr/rYkZue/NL4ftzb2YXGAcVu/Eiyf/hKLWU42TxAKO/ynn+dQLELeEYHAFY1dVY66SVky3GPSpec1inVZInAETEfQ1Zg1hJXCuhUn2rn5TqTNTbkVJGDjaKpzXiRpuBql/b7o2xIc/wC1SUbjbsaVxGcHIrgtd0pTcSTsPmY5+ldvDetOwEinnvis/X7VTYPJjuO1aw91mFRXR3fw6tHs/BFgj4+dTIMehrqqx/CkTQeFdNicYZIFBHoa2K6jgYUUUUAFIxIRiBkgcD1paKAPJ9bvrjWb23kuIlDQzEDH8K5wV/TrUt18kZO3IHYVc8Raa2n6x+7GIJCXXnuTz+v86aGBrmlvqeioxtePU5LUNWnt7YvHZPIgYKMdfyp9mJbiQGSLYeMgHPWujltEYH5c5qFIViwB2qW1Y0USKezIttwHaudvLvULJkW3shLlwvXn613CBZLXa3aqf2RGk3AA0k7DauUbSe8kkVJLcAf3lORV3VbffpsiE7ehrUhRY484xiqt6fOjaPPWnfUnl6G14Nubi5nuy7M0KqoQk8E9666sfwxYix0OBRgs43k465rYrqjsefVtzuwUUUUzMKKKKAMLxTarLp6zEDdG3p2NcVko1eoSRpKhSRQyngg964fxDpqWV8DECI5BuH9ayqR6nVQqfZZktOQpJrLuNSS2V57gvsBwAq5NXn4OD0qvKto4xJt47msOp2LUjXxFbQ2xlZJTH6BTn8qmtL/7S6SwhvLcZ+YYxUKtp2/O5Cp96vRTWa4jiZM9sGhqw2i95x2EU2zhN3qEURUsGbBH86i3flXReFLcNPNOVyFACn3qoK7MakuWLZ1KKEQKowAMCnUUV1HnBRRRQAUUUUAFZHiO1W40mSQ8NCN4Na9UtWAbSboHoYzSlsVB2kjzCeTrzWPeWiXGWZ2+gNaN2Cp9qrKVxya4m3c9KLMiOyt1k2bHP1Y4rWtLK3gIkRArD0pwMG/J/OpmkhVcg5pczZo5FyO4J4J616NoFslvpEJUcyDex9zXl1qTNKCB8ma9X0n/AJBNr/1zFb0dzjxD0LtFFFdBxhRRRQAUVSu9WsbHInuFDD+Ecn8q5y/8aqGaKzixgffcf0oA6u4uYbSBpp5FjjQZLMcVwlx45/tmaa3022zYj5WuZMjef9kenvXM+M7+9vfDdy8k7u20ZyeMZ54+lSaSkS6dB5IATYMY+lZ1W0jalFN3ZcmhV1INYt5aMufLzW7xUMqZ6VynWcr9nvC3G/H1FaFnZSEjzSTWgY5N+O1XbeLjkUDuPtoFiUAVd/4TC+8P7BLY/atOHVoj+8T8O4qJVAqRYhIpVhlSKqD5XczmlLRnaaN4g07XbJLqxuFdHGcHgj8K068S8PZsPEesafD8sSOssYHG3PWuvg1m9spAvnMQSThvmFda1VzjkknY76iuYi8VPnD2ysAOWDYyfyop2JOKkLsm8ksrH5cdaYYiFGcsB1bNSBi6+YPlbpjJIwakKpCylRgMckAdSaoZFNbLeWE1u/KSIVH4jFZvheRzpf2aT/WW7GJh9K3AdsrrycHPP41lW8X2LxTPEDlLuMXGPRuh/pWVRXRrS3NJgRTMntV54lINQrEtctjqKrF6sW+4jmpfJWrMEK0hjVjzVlUCpmn7AnSkb7pJ6Y6UCOS0pN/jDV5j2CL/AD/wrduTg/KASexHWsjw4RNNql4RzLckAHsF4rTuJQHRG3Et8wwcYxXbD4UcU/iJYljkjxKXB67N2Nv5UVQWc+dIxUbtxGfairJP/9k=\",\"fp_feature1\":\"QwFEEgELSwAAAAAAAAAAAAAAAB0Bmv///////////4UoEfyuOgr8NEIg/LdXu/w9WCD8xmJg/lZrG/zkeaX8oH8D/C2KJfx4kBH8npO6/BejJfznraP8M7rS/LK9YP4/yR78FMwf/HrLC/zhzKP8yM6q/DDZGPxC2tD8X97K/BXi0/xU+xb8dhcO/ZUiYv+BQwP9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8=\",\"fp_feature2\":\"QwFEEgEQQAAAAAAAAAAAAAAAABcBmsByAP///////74Oovy6GFD+hCm0/Hgyv/yvM6z81jud/I1GAfyqT7r8glPD/MVatPxwZRT8xGpX/vdunPybggf825at/FegyPzAoWL+2ciu/KjWwvzp21P+c+EU/MQJt/2XHBn9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPw=\",\"Address\":\"重庆市长寿区建新中路513号12幢24-2\",\"EnName\":\"\",\"Sex\":\"男\",\"NationCode\":\"1\",\"Birthday\":\"19890909\",\"Base64Image\":\"(null)\",\"Name\":\"叶重阳\",\"ImageName\":\"\",\"CnName\":\"\",\"ValidDate\":\"20360202\",\"CardType\":\"1\",\"PassNum\":\"\",\"VisaTimes\":\"0\",\"PhotoName\":\"C:\\\\Program Files (x86)\\\\ZKIDROnline\\\\bin\\\\zp.bmp\",\"IDNumber\":\"500221198909094019\",\"IDIssued\":\"重庆市公安局长寿分局\",\"IssuedData\":\"20160202\",\"Other\":\"\"}\n",IDCardVo.class); + idCardVo.getCertificate().setReadID("123123123"); + idCardVo.getCertificate().setReadTime(System.currentTimeMillis()+""); + } + catch (Exception e) + { + System.out.println(e.getLocalizedMessage()); + //logger.error("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"解析数据发生异常:"+e.getLocalizedMessage()); + } + ZKIDROnlineConfigVo zkidrOnlineConfigVo=new ZKIDROnlineConfigVo(); + zkidrOnlineConfigVo.setUri("ws://10.234.6.254:24012/"); + zkidrOnlineConfigVo.setAutoReconnect(false); + zkidrOnlineConfigVo.setReconnectDelay(10000L); + zkidrOnlineConfigVo.setStartMsg("{\"module\":\"idcard\",\"function\":\"autoreadcard\",\"parameter\":{\"dev\":\"1\",\"repeat\":\"1\",\"readtype\":\"1\"}}"); + ZKIDROnlineClient zkidrOnlineClient=new ZKIDROnlineClient("10000000",null,null,zkidrOnlineConfigVo); +// boolean open = zkidrOnlineClient.start(); +// System.out.println(open); + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/gateway/impl/ZKIDROnlineClient.java b/src/main/java/com/cisdi/data/IDCardReader/gateway/impl/ZKIDROnlineClient.java new file mode 100644 index 0000000..bce3847 --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/gateway/impl/ZKIDROnlineClient.java @@ -0,0 +1,80 @@ +package com.cisdi.data.IDCardReader.gateway.impl; + +import com.cisdi.data.IDCardReader.gateway.vo.ZKIDROnlineConfigVo; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.service.ServiceProvider; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.drafts.Draft; +import org.java_websocket.handshake.ServerHandshake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ZKIDROnlineClient implements Runnable{ + + private String deviceId; + ServiceProvider serviceProvider; + SocketGatewayBase socketGateway; + private ZKIDROnlineConfigVo zkidrOnlineConfigVo; + private ZKIDROnlineListener zkidrOnlineListener=null; + private Logger logger= LoggerFactory.getLogger(ZKIDROnlineClient.class); + private AtomicBoolean shouldRun=new AtomicBoolean(true); + + private Boolean isOpen=false; + + public Boolean getOpen() { + return isOpen; + } + + public Boolean close() + { + zkidrOnlineListener.setShouldRun(false); + shouldRun.set(false); + return zkidrOnlineListener.endListen(); + } + + public ZKIDROnlineClient(String deviceId, ServiceProvider serviceProvider, SocketGatewayBase socketGateway, ZKIDROnlineConfigVo zkidrOnlineConfigVo) { + this.deviceId = deviceId; + this.serviceProvider = serviceProvider; + this.socketGateway = socketGateway; + this.zkidrOnlineConfigVo = zkidrOnlineConfigVo; + } + + @Override + public void run() { + while (shouldRun.get()) + { + try { + zkidrOnlineListener=new ZKIDROnlineListener(new URI(zkidrOnlineConfigVo.getUri()),deviceId,socketGateway,serviceProvider,zkidrOnlineConfigVo); + logger.info("ZKIDROnline网关:{}启动成功 参数:{}", socketGateway.getInstanceVo().getRunId(), socketGateway.getInstanceVo().getParameter()); + break; + } catch (URISyntaxException e) { + logger.error("连接ID:"+socketGateway.getInstanceVo().getRunId()+",设备ID:"+deviceId+",自动重连设置:"+zkidrOnlineConfigVo.getAutoReconnect()+"启动失败,错误原因:"+e.getMessage()); + try { + Thread.sleep(zkidrOnlineConfigVo.getReconnectDelay()); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + if(zkidrOnlineConfigVo.getAutoReconnect()) + { + logger.info("连接ID:"+socketGateway.getInstanceVo().getRunId()+",设备ID:"+deviceId+",自动重连设置:"+zkidrOnlineConfigVo.getAutoReconnect()+"启动失败,正在重连"); + } + else + { + logger.info("ZKIDROnline网关:{}启动失败 参数:{}", socketGateway.getInstanceVo().getRunId(), socketGateway.getInstanceVo().getParameter()); + break; + } + } + } + + if(zkidrOnlineListener!=null) { + zkidrOnlineListener.start(false); + isOpen=true; + return ; + } + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/gateway/impl/ZKIDROnlineListener.java b/src/main/java/com/cisdi/data/IDCardReader/gateway/impl/ZKIDROnlineListener.java new file mode 100644 index 0000000..487994d --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/gateway/impl/ZKIDROnlineListener.java @@ -0,0 +1,224 @@ +package com.cisdi.data.IDCardReader.gateway.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.cisdi.data.IDCardReader.gateway.vo.IDCardVo; +import com.cisdi.data.IDCardReader.gateway.vo.ZKIDROnlineConfigVo; +import com.cisdi.data.IDCardReader.gateway.vo.ZKIDROnlineResponseVo; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.Gateway; +import com.cisdi.data.sdk.gateway.base.GatewayBase; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.service.SendService; +import com.cisdi.data.sdk.service.ServiceProvider; +import org.java_websocket.WebSocket; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ZKIDROnlineListener extends WebSocketClient { + + private Integer reconnectCount=0; + + private String runId; + private String deviceId; + private Boolean autoReconnect=false; + private Long reconnectDelay =1000L; + private ServiceProvider serviceProvider=null; + private SocketGatewayBase socketGatewayBase=null; + private Logger logger= LoggerFactory.getLogger(ZKIDROnlineListener.class); + private ZKIDROnlineConfigVo zkidrOnlineConfigVo=null; + private Boolean startFlag=false; + private static final String SUCCESS_STR="BeginReadCard Succsefull"; + private AtomicBoolean shouldRun=new AtomicBoolean(true); + + public void setShouldRun(boolean b) + { + shouldRun.set(b); + } + + public ZKIDROnlineListener(URI serverUri, + String deviceId, + SocketGatewayBase socketGateway, + ServiceProvider serviceProvider, + ZKIDROnlineConfigVo zkidrOnlineConfigVo) { + super(serverUri); + if(socketGateway!=null) + { + this.runId=socketGateway.getInstanceVo().getRunId(); + } + if(zkidrOnlineConfigVo!=null) + { + this.autoReconnect=zkidrOnlineConfigVo.getAutoReconnect(); + this.reconnectDelay =zkidrOnlineConfigVo.getReconnectDelay(); + } + this.deviceId=deviceId; + this.socketGatewayBase=socketGateway; + this.serviceProvider=serviceProvider; + this.zkidrOnlineConfigVo=zkidrOnlineConfigVo; + } + + public Boolean endListen() + { + startFlag=false; + autoReconnect=false; + this.close(); + return true; + } + + public boolean start(boolean reconnect) + { + startFlag=false; + while (shouldRun.get()) + { + try + { + if(this.getReadyState().equals(READYSTATE.NOT_YET_CONNECTED)) + { + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"状态为:"+this.getReadyState()+"准备尝试连接"); + if(!this.connectBlocking()) + { + return false; + } + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"正在发送启动消息:"+zkidrOnlineConfigVo.getStartMsg()); + this.send(zkidrOnlineConfigVo.getStartMsg()); + return true; + } + else if(this.getReadyState().equals(READYSTATE.CLOSED)||this.getReadyState().equals(READYSTATE.CLOSING)) + { + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"状态为:"+this.getReadyState()+"准备尝试重连"); + if(!this.reconnectBlocking()) + { + return false; + } + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"正在发送启动消息:"+zkidrOnlineConfigVo.getStartMsg()); + this.send(zkidrOnlineConfigVo.getStartMsg()); + return true; + } + } + catch (Exception e) + { + logger.error("连接ID:"+socketGatewayBase.getInstanceVo().getRunId()+",设备ID:"+deviceId+",自动重连设置:"+zkidrOnlineConfigVo.getAutoReconnect()+"启动失败,错误原因:"+e.getLocalizedMessage()); + try { + Thread.sleep(zkidrOnlineConfigVo.getReconnectDelay()); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + if(reconnect) + { + logger.info("连接ID:"+socketGatewayBase.getInstanceVo().getRunId()+",设备ID:"+deviceId+",自动重连设置:"+zkidrOnlineConfigVo.getAutoReconnect()+"启动失败,正在重连"); + } + else + { + break; + } + } + } + return false; + } + + @Override + public void onOpen(ServerHandshake handshakedata) { + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"已经打开连接"); + } + + @Override + public void onMessage(String message) { + if(startFlag) + { + IDCardVo idCardVo=null; + try + { + idCardVo= JSON.parseObject(message,IDCardVo.class); + idCardVo.getCertificate().setReadID(deviceId); + idCardVo.getCertificate().setReadTime(System.currentTimeMillis()+""); + } + catch (Exception e) + { + logger.error("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"解析数据发生异常:"+e.getLocalizedMessage()); + } + + if(idCardVo!=null&&serviceProvider!=null&&socketGatewayBase!=null) + { + SendService service = (SendService)serviceProvider.getByName(ServiceName.Send); + SocketMessage socketMessage = socketGatewayBase.buildSocketMessage(); + socketMessage.setDeviceId(this.deviceId); + socketMessage.setMsgKey("default"); + socketMessage.setData(JSON.toJSONString(idCardVo.getCertificate()).getBytes()); + socketMessage.getPropsMap().put("charset", Charset.forName("GBK")); + service.sendMessage(socketMessage); + if(socketGatewayBase.getInstanceVo().getLogOpen()) + { + logger.error("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"收到数据:"+idCardVo.getCertificate()); + } + } + } + else + { + ZKIDROnlineResponseVo zkidrOnlineResponseVo=null; + try + { + zkidrOnlineResponseVo= JSON.parseObject(message,ZKIDROnlineResponseVo.class); + } + catch (Exception e) + { + logger.error("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"收到无法解析的数据:"+message); + } + if(zkidrOnlineResponseVo!=null&&SUCCESS_STR.equals(zkidrOnlineResponseVo.getData())) + { + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"请求自动获取身份证信息成功"); + startFlag=true; + } + else + { + //this.close(); + this.start(true); + } + } + + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if(startFlag&&remote) + { + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"正在尝试重连"); + //this.close(); + this.start(true); + } + else + { + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"正在关闭连接"); + } + } + + @Override + public void onError(Exception ex) { + logger.error("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"发生异常:"+ex.getLocalizedMessage()); + if(autoReconnect) + { + try + { + Thread.sleep(reconnectDelay); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + logger.info("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"正在尝试重连"); + //this.close(); + this.start(true); + } + else + { + logger.warn("连接ID:"+runId+",设备ID:"+deviceId+",自动重连设置:"+autoReconnect+"未配置重连,连接关闭"); + } + + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/IDCardVo.java b/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/IDCardVo.java new file mode 100644 index 0000000..5c2cd75 --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/IDCardVo.java @@ -0,0 +1,60 @@ +package com.cisdi.data.IDCardReader.gateway.vo; + +public class IDCardVo { + public class IDCardDataVo{ + private String IDNumber; + private String Name; + private String ReadID; + private String ReadTime; + + public String getIDNumber() { + return IDNumber; + } + + public void setIDNumber(String IDNumber) { + this.IDNumber = IDNumber; + } + + public String getName() { + return Name; + } + + public void setName(String name) { + Name = name; + } + + public String getReadID() { + return ReadID; + } + + public void setReadID(String readID) { + ReadID = readID; + } + + public String getReadTime() { + return ReadTime; + } + + public void setReadTime(String readTime) { + ReadTime = readTime; + } + } + private Integer ret; + private IDCardDataVo Certificate; + + public Integer getRet() { + return ret; + } + + public void setRet(Integer ret) { + this.ret = ret; + } + + public IDCardDataVo getCertificate() { + return Certificate; + } + + public void setCertificate(IDCardDataVo certificate) { + Certificate = certificate; + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/ZKIDROnlineConfigVo.java b/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/ZKIDROnlineConfigVo.java new file mode 100644 index 0000000..6acc8b1 --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/ZKIDROnlineConfigVo.java @@ -0,0 +1,42 @@ +package com.cisdi.data.IDCardReader.gateway.vo; + +import com.alibaba.fastjson.JSONObject; + +public class ZKIDROnlineConfigVo { + private String uri; + private Boolean autoReconnect; + private String startMsg; + private Long reconnectDelay; + + public String getStartMsg() { + return startMsg; + } + + public void setStartMsg(String startMsg) { + this.startMsg = startMsg; + } + + public Long getReconnectDelay() { + return reconnectDelay; + } + + public void setReconnectDelay(Long reconnectDelay) { + this.reconnectDelay = reconnectDelay; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public Boolean getAutoReconnect() { + return autoReconnect; + } + + public void setAutoReconnect(Boolean autoReconnect) { + this.autoReconnect = autoReconnect; + } +} diff --git a/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/ZKIDROnlineResponseVo.java b/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/ZKIDROnlineResponseVo.java new file mode 100644 index 0000000..1a145a4 --- /dev/null +++ b/src/main/java/com/cisdi/data/IDCardReader/gateway/vo/ZKIDROnlineResponseVo.java @@ -0,0 +1,31 @@ +package com.cisdi.data.IDCardReader.gateway.vo; + +public class ZKIDROnlineResponseVo { + private String data; + private String dev; + private String ret; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getDev() { + return dev; + } + + public void setDev(String dev) { + this.dev = dev; + } + + public String getRet() { + return ret; + } + + public void setRet(String ret) { + this.ret = ret; + } +} diff --git a/src/main/java/com/cisdi/data/OBLF/OBLFSocketGateway.java b/src/main/java/com/cisdi/data/OBLF/OBLFSocketGateway.java new file mode 100644 index 0000000..f39add3 --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/OBLFSocketGateway.java @@ -0,0 +1,145 @@ +package com.cisdi.data.OBLF; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.OBLF.impl.OBLFChannelInitializer; +import com.cisdi.data.OBLF.impl.OBLFIoSession; +import com.cisdi.data.OBLF.impl.OBLFSessionFactory; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.PlatformService; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.cisdi.data.sdk.vo.SslConfigVo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author cuianbing + * @version 1.0 + * @description: 炼钢OBLF750设备 + * @date 2021/7/21 11:33 + */ +@Slf4j +public class OBLFSocketGateway extends SocketGatewayBase { + + + TcpIoService ioService = null; + SessionFactory sessionFactory = null; + + // 当配置为单个设备模式时,此为该设备id + String onlyOneDeviceId = null; + + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage socketReturnMessage) { + throw new BusinessException("OBLF750不支持下发服务!"); + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + log.info("网关:{}读取启动配置参数:{},是否支持支持多个设备:{}", instanceVo.getRunId(), instanceVo.getParameter(), + instanceVo.getSuportMultiDevice()); + + + // 不支持多个设备,必须强制配置一个设备id + if (instanceVo.getSuportMultiDevice() != null && instanceVo.getSuportMultiDevice() == false) { + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为OBLF750网关,当前配置为只支持单个设备,但配置了多个设备。"); + } + + onlyOneDeviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(onlyOneDeviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + } + + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new OBLFSessionFactory(onlyOneDeviceId); + factory.init(serviceProvider, this); + + SslConfigVo sslConfigVo = null; + + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + if (socketParam != null && Boolean.TRUE.equals(socketParam.getEnableSsl())) { + + PlatformService platformService = (PlatformService) serviceProvider.getByName(ServiceName.Platform); + + sslConfigVo = platformService.getSslConfig(); + } + + + OBLFChannelInitializer channelInitializer = + new OBLFChannelInitializer(factory, getInstanceVo().getByteOrder(), sslConfigVo); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + state = GatewayState.RUNNING; + log.info("OBLF750网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + } else { + log.info("OBLF750网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + + if (sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + OBLFIoSession XrfIoSession = (OBLFIoSession) ioSession; + XrfIoSession.close(); + } + } + + boolean close = ioService.close(); + if (close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + log.info("OBLF750网关:{}关闭成功", instanceVo.getRunId()); + } else { + log.info("OBLF750网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds() { + if (onlyOneDeviceId != null) { + Set set = new HashSet(); + set.add(onlyOneDeviceId); + return set; + } else { + return sessionFactory.getActiveDeviceIds(); + } + } +} diff --git a/src/main/java/com/cisdi/data/OBLF/impl/OBLFChannelInitializer.java b/src/main/java/com/cisdi/data/OBLF/impl/OBLFChannelInitializer.java new file mode 100644 index 0000000..f2c1d1b --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/impl/OBLFChannelInitializer.java @@ -0,0 +1,75 @@ +package com.cisdi.data.OBLF.impl; + +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import com.cisdi.data.sdk.utils.NettySslUtils; +import com.cisdi.data.sdk.vo.SslConfigVo; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/20 21:27 + */ +@Slf4j +public class OBLFChannelInitializer extends AbstractChannelInitializer { + private SessionFactory factory; + private ByteOrder byteOrder; + private SslConfigVo sslConfigVo; + + + /** + * 构造通道初始化器对象 + * + * @param factory 会话工厂 + * @param byteOrder 大小端 + * @param sslConfig ssl证书配置,如果不启用tls,传null + */ + public OBLFChannelInitializer(SessionFactory factory, + ByteOrder byteOrder, + SslConfigVo sslConfig) { + this.factory = factory; + this.byteOrder = byteOrder; + this.sslConfigVo = sslConfig; + } + + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + ChannelHandler sslHandler = null; + try { + if (sslConfigVo != null) { + sslHandler = NettySslUtils.getServerSslHandler((SocketChannel) socketChannel, sslConfigVo); + } + } catch (Exception e) { + log.warn(e.getLocalizedMessage(), e); + } + return sslHandler; + } +} diff --git a/src/main/java/com/cisdi/data/OBLF/impl/OBLFIoSession.java b/src/main/java/com/cisdi/data/OBLF/impl/OBLFIoSession.java new file mode 100644 index 0000000..42b9e81 --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/impl/OBLFIoSession.java @@ -0,0 +1,169 @@ +package com.cisdi.data.OBLF.impl; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoSession; +import com.cisdi.data.sdk.service.PassthroughService; +import com.cisdi.data.sdk.service.SendService; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/20 21:28 + */ +@Slf4j +public class OBLFIoSession extends AbstractIoSession implements IoSession { + + private OBLFSessionFactory factory = null; + + private String deviceId = null; + /** + * 如果有值,代表所有会话都使用这一个设备id,且允许多会话共享此一个设备id,不除旧会话 + */ + private final String onlyOneDeviceId; + + private String gwPrefixCache = null; + + private ByteBuf allBuf = ByteBufAllocator.DEFAULT.buffer(); + + + public OBLFIoSession(OBLFSessionFactory factory, String onlyOneDeviceId) { + super(); + this.factory = factory; + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public String[] getDeviceIds() { + String cpDeviceId = deviceId; + String[] result = new String[cpDeviceId == null ? 0 : 1]; + if (cpDeviceId != null) { + result[0] = cpDeviceId; + } + return result; + } + + @Override + public void onRead(Object message) { + try { + + ByteBuf inBuf = (ByteBuf) message; + if (inBuf != null) { + allBuf.writeBytes(inBuf); + } else { + log.info("电文格式为空"); + } + } catch (Exception e) { + log.error("电文格式错误!"); + throw new BusinessException("电文格式错误!"); + } + + } + + @Override + public void onOpen() { + super.onOpen(); + log.info("建立连接,channel:{}", getChannel()); + } + + @Override + public void onClose() { + log.info("onClose"); + Map message = decode(allBuf); + + if (serviceProvider != null && socketGateway != null) { + SendService service = (SendService) serviceProvider.getByName(ServiceName.Send); + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(onlyOneDeviceId); + socketMessage.setData(JSON.toJSONString(message).getBytes(StandardCharsets.UTF_8)); + + String analysisProgram = message.get("AnalysisProgram_"); + + if ("FE 4".equals(analysisProgram)) { + socketMessage.setMsgKey("iron"); + } else { + socketMessage.setMsgKey("steel"); + } + + service.sendMessage(socketMessage); + if (socketGateway.getInstanceVo().getLogOpen()) { + log.info("类似为:{}:发送消息内容{}", message, message.toString()); + } + + } + super.onClose(); + factory = null; + } + + + public void close() { + if (getChannel() != null) { + try { + getChannel().close().sync(); + } catch (InterruptedException e) { + log.warn(e.getLocalizedMessage(), e); + } + } + } + + @Override + public void onError(Throwable t) { + // 这里会出现 java.io.IOException: 远程主机强迫关闭了一个现有的连接。 + log.info("远程主机强迫关闭了一个现有的连接。"); +// log.error("session exception:{}", t); + } + + + public Map decode(ByteBuf byteBuf) { + + List> dataList = new ArrayList<>(); + AtomicInteger dataFlag = new AtomicInteger(); + + String msg = byteBuf.toString(StandardCharsets.UTF_8); + Map data = JSON.parseObject(msg, Map.class); + log.debug("收到数据{}", data); + return data; + } + + + private void passThrough(ByteBuf passthroughBuf) { + try { + if (serviceProvider != null && socketGateway != null) { + PassthroughService service = (PassthroughService) serviceProvider.getByName(ServiceName.Passthrough); + service.transfer(passthroughBuf, socketGateway.getInstanceVo().getPassthroughAddress()); + } + } catch (Exception e) { + log.error("协议透传失败:" + e.getLocalizedMessage(), e); + } + } + + public String gwPrefix() { + if (gwPrefixCache != null) { + return gwPrefixCache; + } + + gwPrefixCache = ""; + if (socketGateway != null && socketGateway.getInstanceVo() != null) { + gwPrefixCache = "网关Id:" + socketGateway.getInstanceVo().getRunId() + "连接:" + getChannel() + " "; + } else { + gwPrefixCache = "连接:" + getChannel() + " "; + } + + return gwPrefixCache; + } + + +} diff --git a/src/main/java/com/cisdi/data/OBLF/impl/OBLFSessionFactory.java b/src/main/java/com/cisdi/data/OBLF/impl/OBLFSessionFactory.java new file mode 100644 index 0000000..6667776 --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/impl/OBLFSessionFactory.java @@ -0,0 +1,39 @@ +package com.cisdi.data.OBLF.impl; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/20 21:29 + */ +@Slf4j +public class OBLFSessionFactory extends AbstractSessionFactory { + + private String onlyOneDeviceId; + + /** + * @description: + * @param: onlyOneDeviceId 设备唯一ID + * @return: void + * @author cuianbing + * @date: 2021/7/7 22:10 + */ + public OBLFSessionFactory(String onlyOneDeviceId) { + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new OBLFIoSession(this, onlyOneDeviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } + +} \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/OBLF/test/ServiceProviderTest.java b/src/main/java/com/cisdi/data/OBLF/test/ServiceProviderTest.java new file mode 100644 index 0000000..4f6c213 --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/test/ServiceProviderTest.java @@ -0,0 +1,21 @@ +package com.cisdi.data.OBLF.test; + +import com.cisdi.data.sdk.service.Service; +import com.cisdi.data.sdk.service.ServiceProvider; +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:17 + */ +@Slf4j +public class ServiceProviderTest implements ServiceProvider { + + @Override + public Service getByName(String name) { + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/OBLF/test/StartTest.java b/src/main/java/com/cisdi/data/OBLF/test/StartTest.java new file mode 100644 index 0000000..7001b9b --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/test/StartTest.java @@ -0,0 +1,65 @@ +package com.cisdi.data.OBLF.test; + +import com.cisdi.data.OBLF.impl.OBLFChannelInitializer; +import com.cisdi.data.OBLF.impl.OBLFSessionFactory; +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.vo.GatewayVo; +import io.netty.handler.ssl.OpenSsl; +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:28 + */ +@Slf4j +public class StartTest { + + private static TcpIoService ioService = null; + + private static SessionFactory sessionFactory = null; + + public static void main(String[] args) { + boolean available = OpenSsl.isAvailable(); + log.info("openssl available={}", available); + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + + SessionFactory factory = new OBLFSessionFactory(null); + + ServiceProviderTest testServiceProvider = new ServiceProviderTest(); + + factory.init(testServiceProvider, null); + + OBLFChannelInitializer channelInitializer = + new OBLFChannelInitializer(factory, ByteOrder.BIGENDIAN, null); + + GatewayVo instanceVo = new GatewayVo(); + instanceVo.setByteOrder(ByteOrder.BIGENDIAN); + instanceVo.setParameter("{\"listenIp\":\"0.0.0.0\",\"listenPort\":8888,\"threadSize\":50,\"timeout\":2000}"); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + log.info("plc网关启动成功"); + } else { + log.info("plc网关启动失败"); + } + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cisdi/data/OBLF/util/ByteBufUtil.java b/src/main/java/com/cisdi/data/OBLF/util/ByteBufUtil.java new file mode 100644 index 0000000..c8b96f5 --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/util/ByteBufUtil.java @@ -0,0 +1,28 @@ +package com.cisdi.data.OBLF.util; + +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; + +import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump; +import static io.netty.util.internal.StringUtil.NEWLINE; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/9 15:32 + */ +@Slf4j +public class ByteBufUtil { + public static void log(ByteBuf byteBuf) { + int length = byteBuf.readableBytes(); + int row = length / 16 + (length % 15 == 0 ? 0 : 1) + 4; + StringBuilder buf = new StringBuilder(row * 80 * 2) + .append("read index:").append(byteBuf.readerIndex()) + .append(" write index:").append(byteBuf.writerIndex()) + .append(" capacity:").append(byteBuf.capacity()) + .append(NEWLINE); + appendPrettyHexDump(buf, byteBuf); + log.info(buf.toString()); + } +} diff --git a/src/main/java/com/cisdi/data/OBLF/util/HexTransform.java b/src/main/java/com/cisdi/data/OBLF/util/HexTransform.java new file mode 100644 index 0000000..095730f --- /dev/null +++ b/src/main/java/com/cisdi/data/OBLF/util/HexTransform.java @@ -0,0 +1,27 @@ +package com.cisdi.data.OBLF.util; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: 16进制转换工具类 + * @date 2021/7/7 21:41 + */ +@Slf4j +public class HexTransform { + + /** + * @description: + * @param: hex 16进制字符串 + * @param: chartSet 字符集 UTF-8 GB2312 ASCII + * @return: java.lang.String + * @author cuianbing + * @date: 2021/7/7 21:43 + */ + public static String HexToString(String hex, String chartSet) { + + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidClient.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidClient.java new file mode 100644 index 0000000..a5239fd --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidClient.java @@ -0,0 +1,135 @@ +package com.cisdi.data.RFID.gateway; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class RfidClient { + private static final Logger logger = LoggerFactory.getLogger(RfidClient.class); + private volatile Channel channel = null; + private EventLoopGroup workerGroup = null; + private RfidTaskStartVo startVo; + private Long lastHeartbeatTime = System.currentTimeMillis(); + + public RfidClient(RfidTaskStartVo startVo) { + this.startVo = startVo; + + if (Epoll.isAvailable()) { + logger.info("rfid enable epoll and have tcp SO_KEEPALIVE check"); + } else { + logger.info("rfid disable epoll and not have custom SO_KEEPALIVE check"); + } + } + + public boolean close() { + boolean result = false; + if (workerGroup == null && channel == null) { + return true; + } + + if (channel != null) { + try { + channel.close().sync(); + channel = null; + result = true; + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + } + + if (workerGroup != null) { + try { + workerGroup.shutdownGracefully().sync(); + workerGroup = null; + result = true; + } catch (InterruptedException e) { + logger.error("永锋物流rfid到 {}:{}的连接关闭失败,{}", + startVo.getSocketParam().getPeerIp(), startVo.getSocketParam().getPeerPort(), e); + } + } + + if (channel != null || workerGroup != null) { + logger.info("channel:{},workgoup:{}", channel, workerGroup); + } + + return result; + } + + public void run() throws IOException { + start(); + } + + private void start() { + lastHeartbeatTime = System.currentTimeMillis(); + if (channel != null) { + close(); + } + + //设置一个多线程循环器 + //启动附注类 + Bootstrap bootstrap = new Bootstrap(); + bootstrap.option(ChannelOption.SO_KEEPALIVE, true); + + if (Epoll.isAvailable()) { + bootstrap.channel(EpollSocketChannel.class); + bootstrap.option(EpollChannelOption.EPOLL_MODE, EpollMode.LEVEL_TRIGGERED); + bootstrap.option(EpollChannelOption.TCP_KEEPIDLE, 10); + bootstrap.option(EpollChannelOption.TCP_KEEPINTVL, 5); + bootstrap.option(EpollChannelOption.TCP_KEEPCNT, 2); + workerGroup = new EpollEventLoopGroup(2); + } else { + workerGroup = new NioEventLoopGroup(2); + bootstrap.channel(NioSocketChannel.class); + } + + bootstrap.group(workerGroup); + //指定所使用的NIO传输channel + //指定客户端初始化处理 + bootstrap.handler(new RfidClientInitializer(startVo, this)); + try { + channel = bootstrap.connect(startVo.getSocketParam().getPeerIp(), + startVo.getSocketParam().getPeerPort()).sync().channel(); + //channel.closeFuture().sync(); // 如果会话关闭,会退出当前线程 + } catch (InterruptedException e) { + logger.warn(e.getMessage(), e); + } + } + + public void reconnect() { + logger.info("永锋物流rfid重新连接中......{}:{}", getIp(), getPort()); + close(); + start(); + } + + public Channel getChannel() { + return channel; + } + + public void setChannel(Channel channel) { + this.channel = channel; + } + + public String getIp() { + return startVo.getSocketParam().getPeerIp(); + } + + public int getPort() { + return startVo.getSocketParam().getPeerPort(); + } + + public Long getLastHeartbeatTime() { + return lastHeartbeatTime; + } + + public void setLastHeartbeatTime(Long lastHeartbeatTime) { + this.lastHeartbeatTime = lastHeartbeatTime; + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidClientHandler.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidClientHandler.java new file mode 100644 index 0000000..02fada6 --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidClientHandler.java @@ -0,0 +1,110 @@ +package com.cisdi.data.RFID.gateway; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.service.SendService; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.ReferenceCountUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RfidClientHandler extends SimpleChannelInboundHandler { + + private static final Logger logger = LoggerFactory.getLogger(RfidClientHandler.class); + private static final Charset Encode_Charset = Charset.forName("UTF-8"); + private static final String HEARTBEAT_CODE = "HeartBea"; + + private RfidClient rfidClient; + + private RfidTaskStartVo startVo; + + public RfidClientHandler(RfidTaskStartVo startVo, RfidClient rfidClient) { + this.startVo = startVo; + this.rfidClient = rfidClient; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { + + try { + List decode = RfidVoDecoder.Decode(msg); + if(decode.size() == 1 && HEARTBEAT_CODE.equalsIgnoreCase(decode.get(0).getCode())) { + logger.info("永锋物流Rfid {},启用心跳:{} , 心跳 vo:{}", rfidClient.getChannel(), + startVo.getCustomParam() == null ? false : startVo.getCustomParam().getEnableHeartbeat(), + decode); + rfidClient.setLastHeartbeatTime(System.currentTimeMillis()); + return; + } + + decode.forEach((RfidItemVo i) -> { + i.setServerIp(startVo.getSocketParam().getPeerIp()); + i.setServerPort(startVo.getSocketParam().getPeerPort()); + }); + + + if (startVo.getGatewayBase() != null && Boolean.TRUE.equals(startVo.getGatewayBase().getInstanceVo().getLogOpen())) { + logger.info("永锋物流Rfid {} vo:{} ", rfidClient.getChannel(), decode); + } + + if (startVo.getGatewayBase() != null && startVo.getServiceProvider() != null) { + Map data = new HashMap<>(); + SendService service = (SendService) startVo.getServiceProvider().getByName(ServiceName.Send); + SocketMessage socketMessage = startVo.getGatewayBase().buildSocketMessage(); + socketMessage.setDeviceId(startVo.getDeviceId()); + socketMessage.setProviderCode(null); + socketMessage.setMsgKey("666"); + data.put("data", decode); + String jsonString = JSON.toJSONString(data, SerializerFeature.WriteMapNullValue); + byte[] body = jsonString.getBytes(Encode_Charset); + socketMessage.setData(body); + logger.info("socketMess" + socketMessage.toString()); + service.sendMessage(socketMessage); + } else { + logger.info("永锋物流Rfid收到消息{}", decode); + } + } catch (Exception e) { + logger.error(e.getLocalizedMessage(), e); + } + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + logger.info("{} channelActive active", ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + logger.info("{} channelInactive inactive", ctx.channel()); + + try { + rfidClient.setChannel(null); + logger.info("set channel to null raise reconnect"); + } finally { + // 释放引用 + rfidClient = null; + } + + super.channelInactive(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) throws Exception { + logger.error("exceptionCaught:{}", t); + super.exceptionCaught(ctx, t); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + logger.error("userEventTriggered:{}", evt); + super.userEventTriggered(ctx, evt); + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidClientInitializer.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidClientInitializer.java new file mode 100644 index 0000000..bd1df26 --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidClientInitializer.java @@ -0,0 +1,24 @@ +package com.cisdi.data.RFID.gateway; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; + +public class RfidClientInitializer extends ChannelInitializer { + + private RfidClient rfidClient; + private RfidTaskStartVo startVo; + + public RfidClientInitializer(RfidTaskStartVo startVo, RfidClient rfidClient) { + this.startVo = startVo; + this.rfidClient = rfidClient; + } + + @Override + protected void initChannel(SocketChannel socketChannel) throws Exception { + //注册管道 + ChannelPipeline pipeline = socketChannel.pipeline(); + pipeline.addLast(new RfidFrameDecoder(startVo)); + pipeline.addLast("chat", new RfidClientHandler(startVo, rfidClient)); + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidCustomParam.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidCustomParam.java new file mode 100644 index 0000000..405d9f1 --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidCustomParam.java @@ -0,0 +1,81 @@ +package com.cisdi.data.RFID.gateway; + +import com.alibaba.fastjson.JSON; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Rfid server有多个,所以需要支持配置多个服务端地址的 + * @author cup + * @date 2021/03/03 + */ +public class RfidCustomParam implements Serializable { + private static final long serialVersionUID = -5017472517219960138L; + + /** + * 每个String 如 192.168.1.5:8823 形式 + */ + private List serverList = new ArrayList(); + + private Boolean enableHeartbeat = Boolean.FALSE; + + private Integer heartbeatTimeInMs = 5000; + private Integer heartbeatMaxLostCount = 3; + + public List getServerList() { + return serverList; + } + + public void setServerList(List serverList) { + this.serverList = serverList; + } + + public Boolean getEnableHeartbeat() { + return enableHeartbeat; + } + + public void setEnableHeartbeat(Boolean enableHeartbeat) { + this.enableHeartbeat = enableHeartbeat; + } + + public Integer getHeartbeatTimeInMs() { + return heartbeatTimeInMs; + } + + public void setHeartbeatTimeInMs(Integer heartbeatTimeInMs) { + this.heartbeatTimeInMs = heartbeatTimeInMs; + } + + public Integer getHeartbeatMaxLostCount() { + return heartbeatMaxLostCount; + } + + public void setHeartbeatMaxLostCount(Integer heartbeatMaxLostCount) { + this.heartbeatMaxLostCount = heartbeatMaxLostCount; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } + + public static void main(String[] args){ + RfidCustomParam dto = new RfidCustomParam(); + dto.getServerList().add("192.168.6.55:10000"); + dto.getServerList().add("192.168.6.56:10000"); + dto.getServerList().add("192.165.20.111:10000"); + dto.getServerList().add("192.165.20.112:10000"); + dto.getServerList().add("192.165.20.113:10000"); + dto.getServerList().add("192.165.20.119:10000"); + dto.getServerList().add("192.165.20.118:10000"); + dto.getServerList().add("192.165.20.120:10000"); + dto.getServerList().add("192.165.20.121:10000"); + dto.getServerList().add("192.165.20.121:10000"); + List customParams=dto.getServerList().stream().distinct().collect(Collectors.toList()); + System.out.println(customParams); + System.out.println(dto); + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidDaemonTask.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidDaemonTask.java new file mode 100644 index 0000000..ef3112e --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidDaemonTask.java @@ -0,0 +1,150 @@ +package com.cisdi.data.RFID.gateway; + +import com.cisdi.data.common.dto.AlarmDto; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.service.AlarmService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Date; + +public class RfidDaemonTask implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(RfidDaemonTask.class); + + private RfidClient rfidClient; + private RfidTaskStartVo startVo; + + public RfidDaemonTask(RfidClient rfidClient, RfidTaskStartVo startVo) { + this.rfidClient = rfidClient; + this.startVo = startVo; + } + + @Override + public void run() { + String channelInfo = null; + while (startVo.getShouldRun().get() == true) { + channelInfo = String.format("channel:%s, active:%s,open:%s,writable:%s", rfidClient.getChannel(), + rfidClient.getChannel() == null ? "" : rfidClient.getChannel().isActive(), + rfidClient.getChannel() == null ? "" : rfidClient.getChannel().isOpen(), + rfidClient.getChannel() == null ? "" : rfidClient.getChannel().isWritable()); + try { + if (rfidClient != null && rfidClient.getChannel() != null + && rfidClient.getChannel().isActive() == true) { + if (startVo.getGatewayBase() != null + && Boolean.TRUE.equals(startVo.getGatewayBase().getInstanceVo().getLogOpen())) { + logger.info("当前永锋物流Rfid拉取连接通道处于连接状态,连接id:{},连接信息:{}", + startVo.getGatewayBase().getInstanceVo().getRunId(), channelInfo); + } + + if(startVo.getCustomParam() != null + && Boolean.TRUE.equals(startVo.getCustomParam().getEnableHeartbeat()) + && Math.abs(System.currentTimeMillis() - rfidClient.getLastHeartbeatTime()) > + startVo.getCustomParam().getHeartbeatMaxLostCount() * startVo.getCustomParam().getHeartbeatTimeInMs()){ + logger.warn("当前永锋物流Rfid拉取连接通道心跳启用,且心跳超时,触发重连,连接id:{},连接信息:{}", + startVo.getGatewayBase().getInstanceVo().getRunId(), channelInfo); + //启用了心跳,且心跳时间超时 + reconnectToRfid(channelInfo, true); + } + } else if (rfidClient.getChannel() == null) { + String errorMsg = String.format("永锋物流Rfid到%s:%s的连接未建立或已关闭, %s", rfidClient.getIp(), + rfidClient.getPort(), channelInfo); + logger.warn(errorMsg); + sendAlarmToPlatform(errorMsg); + reconnectToRfid(channelInfo, false); + } + else { + String errorMsg = String.format("永锋物流Rfid到%s:%s的连接未建立或已关闭, %s", rfidClient.getIp(), + rfidClient.getPort(), channelInfo); + logger.warn(errorMsg); + + sendAlarmToPlatform(errorMsg); + + if(startVo.getCustomParam() != null + && Boolean.TRUE.equals(startVo.getCustomParam().getEnableHeartbeat()) + && Math.abs(System.currentTimeMillis() - rfidClient.getLastHeartbeatTime()) > + startVo.getCustomParam().getHeartbeatMaxLostCount() * startVo.getCustomParam().getHeartbeatTimeInMs()){ + logger.warn("当前永锋物流Rfid拉取连接通道心跳启用,且心跳超时,触发重连,连接id:{},连接信息:{}", + startVo.getGatewayBase().getInstanceVo().getRunId(), channelInfo); + //启用了心跳,且心跳时间超时 + reconnectToRfid(channelInfo, true); + } + } + + Thread.sleep(startVo.getSocketParam().getPullInternal()); + } catch (Exception e) { + logger.warn(e.getMessage(), e); + } + } + + if (rfidClient != null) { + rfidClient.close(); + rfidClient = null; + } + logger.info("退出永锋物流Rfid拉取连接线程 {}", channelInfo); + } + + private void reconnectToRfid(final String copyChannelInfo, boolean forceConnect) throws InterruptedException { + Thread.sleep(5000); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + logger.info("永锋物流Rfid触发重连强制:{}....{}:{} {}", forceConnect, rfidClient.getIp(), rfidClient.getPort(), + copyChannelInfo); + if (rfidClient.getChannel() == null || forceConnect) { + rfidClient.reconnect(); + } + } catch (Exception e) { + logger.info(e.getMessage(), e); + } + } + }); + thread.start(); + } + + public void forceReconnectToRfid() { + String channelInfo = String.format("channel:%s, active:%s,open:%s,writable:%s", rfidClient.getChannel(), + rfidClient.getChannel() == null ? "" : rfidClient.getChannel().isActive(), + rfidClient.getChannel() == null ? "" : rfidClient.getChannel().isOpen(), + rfidClient.getChannel() == null ? "" : rfidClient.getChannel().isWritable()); + try { + Thread.sleep(500); + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + logger.info("永锋物流Rfid触发重连强制:{}....{}:{} {}", true, rfidClient.getIp(), rfidClient.getPort(), + channelInfo); + rfidClient.reconnect(); + } catch (Exception e) { + logger.info(e.getMessage(), e); + } + } + }); + thread.start(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void sendAlarmToPlatform(String detail) { + if (startVo.getServiceProvider() != null && startVo.getGatewayBase() != null) { + AlarmService alarmService = (AlarmService)startVo.getServiceProvider().getByName(ServiceName.Alarm); + if (alarmService != null) { + AlarmDto alarmDto = new AlarmDto(); + + alarmDto.setAlarmType(AlarmDto.Connect_Fail_Alarm); + alarmDto.setDetail(detail); + alarmDto.setDeviceId(startVo.getDeviceId()); + alarmDto.setGatewayId(startVo.getGatewayBase().getInstanceVo().getRunId()); + alarmDto.setGatewayName(startVo.getGatewayBase().getInstanceVo().getName()); + alarmDto.setMsgKey(null); + alarmDto.setUpper(Boolean.TRUE); + alarmDto.setTime(new Date()); + + alarmService.raiseAlarm(Arrays.asList(alarmDto)); + } + } + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidFrameDecoder.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidFrameDecoder.java new file mode 100644 index 0000000..f256744 --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidFrameDecoder.java @@ -0,0 +1,64 @@ +package com.cisdi.data.RFID.gateway; + +import com.cisdi.data.common.exception.BusinessException; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @Description: 获取完整的报文 + * @Creator: Mo + * @Date: 2021/6/2 23:37 + */ +@Slf4j +public class RfidFrameDecoder extends ByteToMessageDecoder { + private static final Logger logger = LoggerFactory.getLogger(RfidFrameDecoder.class); + + private RfidTaskStartVo startVo; + + private final static Integer MIN_LENGTH = 10; + private final byte START_SPLIT = 0x02; + private final byte END_SPLIT = 0x03; + private final Set validDataSizeInByte = new HashSet(Arrays.asList(10)); + + public RfidFrameDecoder(RfidTaskStartVo startVo) { + this.startVo = startVo; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bufferIn, List out) throws Exception { + if (bufferIn.readableBytes() < MIN_LENGTH) { + return; + } + String channelInfo = ctx.toString(); + + // 心跳报文为 02 48 65 61 72 74 42 65 61 03 长度10个字节,拥有独立的起始符和结束符,不会和数据报文共享起始符和结束符 + // 数据体长度固定为 8,心跳报文数据内容为 HeartBea + int beginIndex = bufferIn.readerIndex(); + + for (int i = 0; i + 9 < bufferIn.readableBytes(); i += 10) { + byte curByte = bufferIn.getByte(beginIndex + i); + if(curByte != START_SPLIT){ + ctx.close(); + throw new BusinessException(channelInfo + "完整报文第一个字节内容不正确,期望: 0x02,实际:" + curByte); + } + + byte endByte = bufferIn.getByte(beginIndex + i + 9); + if(endByte != END_SPLIT){ + ctx.close(); + throw new BusinessException(channelInfo + "完整报文最后一个字节内容不正确,期望: 0x03,实际:" + endByte); + } + + out.add(bufferIn.retainedSlice(beginIndex + i, 10)); + bufferIn.readerIndex(beginIndex + i + 10); + } + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidItemVo.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidItemVo.java new file mode 100644 index 0000000..4826c6f --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidItemVo.java @@ -0,0 +1,58 @@ +package com.cisdi.data.RFID.gateway; + +import com.alibaba.fastjson.JSON; + +import java.io.Serializable; + +/** + * 读取到标签或标签离开,解析后的内容都一致 + * + * @author cup + * @date 2021/03/03 + */ +public class RfidItemVo implements Serializable { + private static final long serialVersionUID = -4290553035349526464L; + /** + * rfid 服务器Ip + */ + private String serverIp; + + /** + * rfid 服务器端口 + */ + private Integer serverPort; + + /** + * 编号 + */ + private String code; + + public String getServerIp() { + return serverIp; + } + + public void setServerIp(String serverIp) { + this.serverIp = serverIp; + } + + public Integer getServerPort() { + return serverPort; + } + + public void setServerPort(Integer serverPort) { + this.serverPort = serverPort; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidPullSocketGateway.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidPullSocketGateway.java new file mode 100644 index 0000000..f8accca --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidPullSocketGateway.java @@ -0,0 +1,172 @@ +package com.cisdi.data.RFID.gateway; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.common.utils.Strings; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.PulledSocketGatewayBase; +import com.cisdi.data.sdk.param.PullSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +/** + * 物流 rfid 驱动,平台:客户端 rfid:server,多个server,使用tcp协议 + * + * @author cup + * @date 2021/03/04 + */ +public class RfidPullSocketGateway extends PulledSocketGatewayBase { + private static Logger logger = LoggerFactory.getLogger(RfidPullSocketGateway.class); + + private String deviceId; + private AtomicBoolean shouldRun = null; + private static final int Min_Pull_Internal = 1; // 允许最小的拉取间隔为1毫秒 + private static final int Min_Timeout = 1000; // 允许最小的超时时间间隔,为1000毫秒 + + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + throw new BusinessException("永锋物流Rfid协议不支持下发命令。"); + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + + try { + startInternal(); + state = GatewayState.RUNNING; + logger.info("永锋物流Rfid报警socket拉取网关:{}启动成功", instanceVo.getRunId()); + } catch (Exception e) { + logger.error("永锋物流Rfid报警socket拉取网关:{}启动失败", instanceVo.getRunId()); + logger.error(e.getLocalizedMessage(), e); + } + } + + private void startInternal() { + socketParam = JSON.parseObject(getInstanceVo().getParameter(), PullSocketParam.class); + + if (StringUtils.isEmpty(socketParam.getPeerIp()) || socketParam.getPeerPort() == null) { + throw new BusinessException("未正确配置拉取对端ip地址和对端端口。"); + } + + if (socketParam.getPullInternal() == null) { + throw new BusinessException("未正确配置拉取间隔"); + } + + if (socketParam.getTimeout() == null) { + throw new BusinessException("未正确配置拉取超时时间"); + } + + socketParam.setPullInternal(socketParam.getPullInternal() < Min_Pull_Internal ? + Min_Pull_Internal : socketParam.getPullInternal()); + + socketParam.setTimeout(socketParam.getTimeout() < Min_Timeout ? Min_Timeout : socketParam.getTimeout()); + + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为永锋物流Rfid报警socket拉取网关,只允许关联一个设备Id"); + } + + deviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(deviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + + logger.info("永锋物流Rfid报警socket拉取网关:{}读取启动配置参数:{}", getInstanceVo().getRunId(), socketParam); + + closePrevState(); + shouldRun = new AtomicBoolean(true); + + String parameter = socketParam.getOtherParameter(); + Set targetServerSet = new HashSet(); + targetServerSet.add(String.format("%s:%s", socketParam.getPeerIp(), socketParam.getPeerPort())); + RfidCustomParam customParam = null; + if (parameter != null) { + customParam = JSON.parseObject(parameter, RfidCustomParam.class); + if (customParam != null) { + /** + * RFID服务端参数去重 + */ + customParam.setServerList(customParam.getServerList().stream().distinct().collect(Collectors.toList())); + for (String server : customParam.getServerList()) { + targetServerSet.add(server.trim()); + } + } + } + + int index = 1; + for (String server : targetServerSet) { + // 复制已有参数 + PullSocketParam serverParam = JSON.parseObject(JSON.toJSONString(socketParam), PullSocketParam.class); + + serverParam.setPeerIp(Strings.split(server, ":")[0]); + serverParam.setPeerPort(Integer.valueOf(Strings.split(server, ":")[1])); + + // 启动线程 + RfidTaskStartVo startVo = new RfidTaskStartVo(); + startVo.setDeviceId(deviceId); + startVo.setGatewayBase(this); + startVo.setServiceProvider(serviceProvider); + startVo.setSocketParam(serverParam); + startVo.setShouldRun(shouldRun); + startVo.setCustomParam(customParam); + + RfidClient fireClient = new RfidClient(startVo); + RfidDaemonTask daemonTask = new RfidDaemonTask(fireClient, startVo); + startVo.setRfidDaemonTask(daemonTask); + Thread thread = new Thread(daemonTask, "Fire-Daemon-Task-runid-" + instanceVo.getRunId() + "-s" + index++); + thread.start(); + } + + + state = GatewayState.RUNNING; + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + + closePrevState(); + + logger.info("永锋物流Rfid报警socket拉取网关:{}关闭中", getInstanceVo().getRunId()); + + state = GatewayState.CLOSED; + + logger.info("永锋物流Rfid报警socket拉取网关:{}关闭成功", getInstanceVo().getRunId()); + } + + private void closePrevState() { + if (shouldRun != null) { + shouldRun.set(false); + shouldRun = null; + } + } + + @Override + public Set getActiveDeviceIds() { + Set set = new HashSet(); + if (deviceId != null) { + set.add(deviceId); + } + return set; + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidTaskStartVo.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidTaskStartVo.java new file mode 100644 index 0000000..8d6931b --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidTaskStartVo.java @@ -0,0 +1,62 @@ +package com.cisdi.data.RFID.gateway; + +import com.cisdi.data.sdk.gateway.base.PulledSocketGatewayBase; +import com.cisdi.data.sdk.param.PullSocketParam; +import com.cisdi.data.sdk.service.ServiceProvider; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class RfidTaskStartVo { + private AtomicBoolean shouldRun; + private ServiceProvider serviceProvider; + private String deviceId; + private PulledSocketGatewayBase gatewayBase; + private PullSocketParam socketParam; + private RfidCustomParam customParam; + private RfidDaemonTask rfidDaemonTask; + + public AtomicBoolean getShouldRun() { + return shouldRun; + } + public void setShouldRun(AtomicBoolean shouldRun) { + this.shouldRun = shouldRun; + } + public ServiceProvider getServiceProvider() { + return serviceProvider; + } + public void setServiceProvider(ServiceProvider serviceProvider) { + this.serviceProvider = serviceProvider; + } + public String getDeviceId() { + return deviceId; + } + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + public PulledSocketGatewayBase getGatewayBase() { + return gatewayBase; + } + public void setGatewayBase(PulledSocketGatewayBase gatewayBase) { + this.gatewayBase = gatewayBase; + } + public PullSocketParam getSocketParam() { + return socketParam; + } + public void setSocketParam(PullSocketParam socketParam) { + this.socketParam = socketParam; + } + public RfidCustomParam getCustomParam() { + return customParam; + } + public void setCustomParam(RfidCustomParam customParam) { + this.customParam = customParam; + } + + public RfidDaemonTask getRfidDaemonTask() { + return rfidDaemonTask; + } + + public void setRfidDaemonTask(RfidDaemonTask rfidDaemonTask) { + this.rfidDaemonTask = rfidDaemonTask; + } +} diff --git a/src/main/java/com/cisdi/data/RFID/gateway/RfidVoDecoder.java b/src/main/java/com/cisdi/data/RFID/gateway/RfidVoDecoder.java new file mode 100644 index 0000000..748ef16 --- /dev/null +++ b/src/main/java/com/cisdi/data/RFID/gateway/RfidVoDecoder.java @@ -0,0 +1,87 @@ +package com.cisdi.data.RFID.gateway; + +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.util.HexUtil; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; + +import java.util.ArrayList; +import java.util.List; + +public class RfidVoDecoder { + + public static List Decode(ByteBuf in) { + RfidItemVo vo = new RfidItemVo(); + List result = new ArrayList<>(); + //消息体部分 + int length; + if (in == null) { + throw new BusinessException("ByteBuf 入参为null"); + } else if (in.readableBytes() == 10) { + length = 8; + } + else { + throw new BusinessException("报文长度不匹配 ,期望:10,实际:" + in.readableBytes()); + } + //去掉报文头 和 结束符 + ByteBuf proceedIn = in.retainedSlice(1, length); + result.add(split(proceedIn)); + ReferenceCountUtil.release(proceedIn); + return result; + } + + + /** + * @param in 去掉头尾的报文 + * @Description: 拆分报文 + * @author: Mo + * @Date: 2021/6/2 10:51 + * @return: com.cisdi.data.RFID.gateway.RfidItemVo + */ + public static RfidItemVo split(ByteBuf in) { + RfidItemVo rfidItemVo = new RfidItemVo(); + ByteBuf buf = in.readBytes(8); + // 编号 + String code = buf.toString(CharsetUtil.UTF_8); + rfidItemVo.setCode(code); + ReferenceCountUtil.release(buf); + return rfidItemVo; + } + + public static void main(String[] args) throws Exception { + // 标签读取 +// byte[] readBytes1 = HexUtil.hexStringToBytes("001c1952000b000e3000eabcdef01234567890003001000400000501"); +// ByteBuf copiedBuffer = Unpooled.copiedBuffer(readBytes1); +// RfidItemVo decoderVo = RfidVoDecoder.Decode(copiedBuffer); +// logger.info("vo1={}", decoderVo); +// +// // 标签离开 +// readBytes1 = HexUtil.hexStringToBytes("00161942050c000e3000eabcdef01234567890003001"); +// copiedBuffer = Unpooled.copiedBuffer(readBytes1); +// decoderVo = RfidVoDecoder.Decode(copiedBuffer); +// logger.info("vo1={}", decoderVo); + + //标签读取 + + + + byte[] readBytes1 = HexUtil.hexStringToBytes("02524630313030303103"); + ByteBuf copiedBuff = Unpooled.copiedBuffer(readBytes1); + List rfidItemVoList = RfidVoDecoder.Decode(copiedBuff); + rfidItemVoList.forEach(System.out::println); + + + readBytes1 = HexUtil.hexStringToBytes("0248656172744265617403"); + copiedBuff = Unpooled.copiedBuffer(readBytes1); + rfidItemVoList = RfidVoDecoder.Decode(copiedBuff); + rfidItemVoList.forEach(System.out::println); + + RfidFrameDecoder decoder = new RfidFrameDecoder(null); + readBytes1 = HexUtil.hexStringToBytes("0248656172744265617403"); + copiedBuff = Unpooled.copiedBuffer(readBytes1); + decoder.decode(null, copiedBuff, new ArrayList()); + + } +} diff --git a/src/main/java/com/cisdi/data/Spectrograph8860/SGSocketGateway.java b/src/main/java/com/cisdi/data/Spectrograph8860/SGSocketGateway.java new file mode 100644 index 0000000..0a47005 --- /dev/null +++ b/src/main/java/com/cisdi/data/Spectrograph8860/SGSocketGateway.java @@ -0,0 +1,143 @@ +package com.cisdi.data.Spectrograph8860; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.Spectrograph8860.impl.SGSessionFactory; +import com.cisdi.data.Spectrograph8860.impl.SGChannelInitializer; +import com.cisdi.data.Spectrograph8860.impl.SGIoSession; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.PlatformService; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.cisdi.data.sdk.vo.SslConfigVo; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +public class SGSocketGateway extends SocketGatewayBase { + + private static Logger logger = LoggerFactory.getLogger(SGSocketGateway.class); + + TcpIoService ioService = null; + SessionFactory sessionFactory = null; + + // 当配置为单个设备模式时,此为该设备id + String onlyOneDeviceId = null; + + /** + * 下发的时候,查配置路由,如果配置路由里面有匹配的,则走配置路由;如果没有匹配的,则原路返回 + */ + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + throw new BusinessException("光谱仪8860不支持下发服务!"); + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + + logger.info("plc网关:{}读取启动配置参数:{},支持多个设备:{}", instanceVo.getRunId(), instanceVo.getParameter(), + instanceVo.getSuportMultiDevice()); + + // 不支持多个设备,必须强制配置一个设备id + if (instanceVo.getSuportMultiDevice() != null && instanceVo.getSuportMultiDevice() == false) { + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为光谱仪8860网关,当前配置为只支持单个设备,但配置了多个设备。"); + } + + onlyOneDeviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(onlyOneDeviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + } + + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new SGSessionFactory(onlyOneDeviceId); + factory.init(serviceProvider, this); + + SslConfigVo sslConfigVo = null; + + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + if (socketParam != null && Boolean.TRUE.equals(socketParam.getEnableSsl())) { + + PlatformService platformService = (PlatformService) serviceProvider.getByName(ServiceName.Platform); + + sslConfigVo = platformService.getSslConfig(); + } + + + SGChannelInitializer channelInitializer = + new SGChannelInitializer(factory, getInstanceVo().getByteOrder(), sslConfigVo); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + state = GatewayState.RUNNING; + logger.info("光谱仪8860网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + } else { + logger.info("光谱仪8860网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + + if (sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + SGIoSession SGIoSession = (SGIoSession) ioSession; + SGIoSession.close(); + } + } + + boolean close = ioService.close(); + if (close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + logger.info("光谱仪8860网关:{}关闭成功", instanceVo.getRunId()); + } else { + logger.info("光谱仪8860网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds() { + if (onlyOneDeviceId != null) { + Set set = new HashSet(); + set.add(onlyOneDeviceId); + return set; + } else { + return sessionFactory.getActiveDeviceIds(); + } + } +} diff --git a/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGChannelInitializer.java b/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGChannelInitializer.java new file mode 100644 index 0000000..7d638a7 --- /dev/null +++ b/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGChannelInitializer.java @@ -0,0 +1,73 @@ +package com.cisdi.data.Spectrograph8860.impl; + +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import com.cisdi.data.sdk.utils.NettySslUtils; +import com.cisdi.data.sdk.vo.SslConfigVo; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class SGChannelInitializer extends AbstractChannelInitializer { + + private static final Logger logger = LoggerFactory.getLogger(SGChannelInitializer.class); + + private SessionFactory factory; + private ByteOrder byteOrder; + private SslConfigVo sslConfigVo; + + /** + * 构造通道初始化器对象 + * + * @param factory 会话工厂 + * @param byteOrder 大小端 + * @param sslConfig ssl证书配置,如果不启用tls,传null + */ + public SGChannelInitializer(SessionFactory factory, + ByteOrder byteOrder, + SslConfigVo sslConfig) { + this.factory = factory; + this.byteOrder = byteOrder; + this.sslConfigVo = sslConfig; + } + + @SuppressWarnings("unchecked") + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + ChannelHandler sslHandler = null; + try { + if (sslConfigVo != null) { + sslHandler = NettySslUtils.getServerSslHandler((SocketChannel) socketChannel, sslConfigVo); + } + } catch (Exception e) { + logger.warn(e.getLocalizedMessage(), e); + } + return sslHandler; + } + +} diff --git a/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGIoSession.java b/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGIoSession.java new file mode 100644 index 0000000..748dd36 Binary files /dev/null and b/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGIoSession.java differ diff --git a/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGSessionFactory.java b/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGSessionFactory.java new file mode 100644 index 0000000..3f618be --- /dev/null +++ b/src/main/java/com/cisdi/data/Spectrograph8860/impl/SGSessionFactory.java @@ -0,0 +1,23 @@ +package com.cisdi.data.Spectrograph8860.impl; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; + +import java.util.UUID; + +public class SGSessionFactory extends AbstractSessionFactory { + + private String onlyOneDeviceId; + + public SGSessionFactory(String onlyoneDeviceId) { + this.onlyOneDeviceId = onlyoneDeviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new SGIoSession(this, onlyOneDeviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } +} diff --git a/src/main/java/com/cisdi/data/Spectrograph8860/test/ServiceProviderTest.java b/src/main/java/com/cisdi/data/Spectrograph8860/test/ServiceProviderTest.java new file mode 100644 index 0000000..5ab3de7 --- /dev/null +++ b/src/main/java/com/cisdi/data/Spectrograph8860/test/ServiceProviderTest.java @@ -0,0 +1,14 @@ +package com.cisdi.data.Spectrograph8860.test; + +import com.cisdi.data.sdk.service.Service; +import com.cisdi.data.sdk.service.ServiceProvider; + +public class ServiceProviderTest implements ServiceProvider { + + @Override + public Service getByName(String name) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/Spectrograph8860/test/StartPlcTest.java b/src/main/java/com/cisdi/data/Spectrograph8860/test/StartPlcTest.java new file mode 100644 index 0000000..074e8f7 --- /dev/null +++ b/src/main/java/com/cisdi/data/Spectrograph8860/test/StartPlcTest.java @@ -0,0 +1,61 @@ +package com.cisdi.data.Spectrograph8860.test; + +import com.cisdi.data.Spectrograph8860.SGSocketGateway; +import com.cisdi.data.Spectrograph8860.impl.SGSessionFactory; +import com.cisdi.data.Spectrograph8860.impl.SGChannelInitializer; +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.vo.GatewayVo; +import io.netty.handler.ssl.OpenSsl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StartPlcTest { + private static Logger logger = LoggerFactory.getLogger(SGSocketGateway.class); + + private static TcpIoService ioService = null; + @SuppressWarnings("unused") + private static SessionFactory sessionFactory = null; + + public static void main(String[] args) { + boolean available = OpenSsl.isAvailable(); + logger.info("openssl available={}", available); + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new SGSessionFactory(null); + + ServiceProviderTest testServiceProvider = new ServiceProviderTest(); + //String passthroughAddress = "192.168.235.1:9098"; + + factory.init(testServiceProvider, null); + + SGChannelInitializer channelInitializer = + new SGChannelInitializer(factory, ByteOrder.BIGENDIAN, null); + + GatewayVo instanceVo = new GatewayVo(); + instanceVo.setByteOrder(ByteOrder.BIGENDIAN); + instanceVo.setParameter("{\"listenIp\":\"10.76.49.121\",\"listenPort\":5002,\"threadSize\":50,\"timeout\":2000}"); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + logger.info("plc网关启动成功"); + } else { + logger.info("plc网关启动失败"); + } + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/XRFLEFTSocketGateway.java b/src/main/java/com/cisdi/data/XRFLEFT/XRFLEFTSocketGateway.java new file mode 100644 index 0000000..73edab2 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/XRFLEFTSocketGateway.java @@ -0,0 +1,145 @@ +package com.cisdi.data.XRFLEFT; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.XRFLEFT.impl.XRFLeftChannelInitializer; +import com.cisdi.data.XRFLEFT.impl.XRFLeftIoSession; +import com.cisdi.data.XRFLEFT.impl.XRFLeftSessionFactory; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.PlatformService; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.cisdi.data.sdk.vo.SslConfigVo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @description: 左侧荧光仪 10.233.2.132 + * @author cuianbing + * @date 2021/7/15 9:27 + * @version 1.0 + */ +@Slf4j +public class XRFLEFTSocketGateway extends SocketGatewayBase { + + + TcpIoService ioService = null; + SessionFactory sessionFactory = null; + + // 当配置为单个设备模式时,此为该设备id + String onlyOneDeviceId = null; + + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage socketReturnMessage) { + throw new BusinessException("X射线荧光光谱仪不支持下发服务!"); + } + + @Override + public void start() { + if (state == GatewayState.RUNNING){ + return; + } + log.info("网关:{}读取启动配置参数:{},是否支持支持多个设备:{}", instanceVo.getRunId(), instanceVo.getParameter(), + instanceVo.getSuportMultiDevice()); + + + // 不支持多个设备,必须强制配置一个设备id + if (instanceVo.getSuportMultiDevice() != null && instanceVo.getSuportMultiDevice() == false) { + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为X射线荧光光谱仪网关,当前配置为只支持单个设备,但配置了多个设备。"); + } + + onlyOneDeviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(onlyOneDeviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + } + + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new XRFLeftSessionFactory(onlyOneDeviceId); + factory.init(serviceProvider, this); + + SslConfigVo sslConfigVo = null; + + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + if (socketParam != null && Boolean.TRUE.equals(socketParam.getEnableSsl())) { + + PlatformService platformService = (PlatformService) serviceProvider.getByName(ServiceName.Platform); + + sslConfigVo = platformService.getSslConfig(); + } + + + XRFLeftChannelInitializer channelInitializer = + new XRFLeftChannelInitializer(factory, getInstanceVo().getByteOrder(), sslConfigVo); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + state = GatewayState.RUNNING; + log.info("X射线荧光光谱仪网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + } else { + log.info("X射线荧光光谱仪网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + + if (sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + XRFLeftIoSession XrfIoSession = (XRFLeftIoSession) ioSession; + XrfIoSession.close(); + } + } + + boolean close = ioService.close(); + if (close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + log.info("X射线荧光光谱仪网关:{}关闭成功", instanceVo.getRunId()); + } else { + log.info("X射线荧光光谱仪网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds() { + if (onlyOneDeviceId != null) { + Set set = new HashSet(); + set.add(onlyOneDeviceId); + return set; + } else { + return sessionFactory.getActiveDeviceIds(); + } + } +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftChannelInitializer.java b/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftChannelInitializer.java new file mode 100644 index 0000000..2e3d7e9 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftChannelInitializer.java @@ -0,0 +1,75 @@ +package com.cisdi.data.XRFLEFT.impl; + +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import com.cisdi.data.sdk.utils.NettySslUtils; +import com.cisdi.data.sdk.vo.SslConfigVo; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 18:57 + */ +@Slf4j +public class XRFLeftChannelInitializer extends AbstractChannelInitializer { + + private SessionFactory factory; + private ByteOrder byteOrder; + private SslConfigVo sslConfigVo; + + + /** + * 构造通道初始化器对象 + * @param factory 会话工厂 + * @param byteOrder 大小端 + * @param sslConfig ssl证书配置,如果不启用tls,传null + */ + public XRFLeftChannelInitializer(SessionFactory factory, + ByteOrder byteOrder, + SslConfigVo sslConfig) { + this.factory = factory; + this.byteOrder = byteOrder; + this.sslConfigVo = sslConfig; + } + + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + ChannelHandler sslHandler = null; + try { + if(sslConfigVo != null) { + sslHandler = NettySslUtils.getServerSslHandler((SocketChannel)socketChannel, sslConfigVo); + } + } catch (Exception e) { + log.warn(e.getLocalizedMessage(), e); + } + return sslHandler; + } +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftIoSession.java b/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftIoSession.java new file mode 100644 index 0000000..7e11f30 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftIoSession.java @@ -0,0 +1,198 @@ +package com.cisdi.data.XRFLEFT.impl; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoSession; +import com.cisdi.data.sdk.service.PassthroughService; +import com.cisdi.data.sdk.service.SendService; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @description: 荧光光谱仪左侧解码 + * @author cuianbing + * @date 2021/7/15 9:19 + * @version 1.0 + */ +@Slf4j +public class XRFLeftIoSession extends AbstractIoSession implements IoSession { + + private XRFLeftSessionFactory factory = null; + + private String deviceId = null; + /** + * 如果有值,代表所有会话都使用这一个设备id,且允许多会话共享此一个设备id,不除旧会话 + */ + private final String onlyOneDeviceId; + + private String gwPrefixCache = null; + + private ByteBuf allBuf = ByteBufAllocator.DEFAULT.buffer(); + + + public XRFLeftIoSession(XRFLeftSessionFactory factory, String onlyOneDeviceId) { + super(); + this.factory = factory; + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public String[] getDeviceIds() { + String cpDeviceId = deviceId; + String[] result = new String[cpDeviceId == null ? 0 : 1]; + if (cpDeviceId != null) { + result[0] = cpDeviceId; + } + return result; + } + + @Override + public void onRead(Object message) { + try { + + ByteBuf inBuf = (ByteBuf) message; + if (inBuf != null) { + allBuf.writeBytes(inBuf); + }else { + log.info("电文格式为空"); + } + }catch (Exception e){ + log.error("电文格式错误!"); + throw new BusinessException("电文格式错误!"); + } + + } + + @Override + public void onOpen() { + super.onOpen(); + log.info("建立连接,channel:{}", getChannel()); + } + + @Override + public void onClose() { + log.info("onClose"); + List> messages = decode(allBuf); + Map sendMessage =new HashMap<>(); + + if (serviceProvider != null && socketGateway != null) { + for (Map message : messages){ + sendMessage.put("data",message); + SendService service = (SendService) serviceProvider.getByName(ServiceName.Send); + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(onlyOneDeviceId); + socketMessage.setData(JSON.toJSONString(sendMessage).getBytes(StandardCharsets.UTF_8)); + socketMessage.setMsgKey("default"); + service.sendMessage(socketMessage); + if (socketGateway.getInstanceVo().getLogOpen()) { + log.info("类似为:{}:发送消息内容{}", sendMessage.get("data"), sendMessage.toString()); + } + } + } + super.onClose(); + factory = null; + } + + + + public void close() { + if (getChannel() != null) { + try { + getChannel().close().sync(); + } catch (InterruptedException e) { + log.warn(e.getLocalizedMessage(), e); + } + } + } + + public void onError(Throwable t) { + // 这里会出现 java.io.IOException: 远程主机强迫关闭了一个现有的连接。 + log.info("远程主机强迫关闭了一个现有的连接。"); +// log.error("session exception:{}", t); + } + + + public List> decode(ByteBuf byteBuf){ + + List> dataList= new ArrayList<>(); + AtomicInteger dataFlag = new AtomicInteger(); + Map data = null; + + String msg = byteBuf.toString(Charset.forName("GB2312")); + + log.debug("{}",msg); + String[] split = msg.split("\n\r"); + for (String s: split){ + char tag = s.charAt(0); + if (tag == '3'){ + String[] dataArray = s.split("\\s+"); + if (dataArray.length > 1){ // 分析错误会出现 `3 (Error) `的数据 + assert data != null; + data.put(dataArray[1],dataArray[2]); + } + }else if (tag == '2'){ + if (dataFlag.getAndIncrement() > 0){ + log.debug("解析的数据为:{}",data); + dataList.add(data); + } + data = new HashMap<>(); + //2 高炉渣 323 2021- 7-14 15:41 101 + String[] dateArray = s.split("\\s+"); + StringBuilder builder = new StringBuilder(); + // 时间会出现多余空格的问题 2021- 7-14 15:41 ,但是10月之后就可以 + for (int i = 3 , j = dateArray.length - 1 ; i < j ; i++){ + builder.append(dateArray[i]); + } + data.put("分析方法",dateArray[1]); + data.put("样品名称",dateArray[2]); + data.put("分析时间",builder.toString()); + }else if(tag == '5'){ + dataList.add(data); + log.debug("解析的数据为:{}",data); + } + } + + + allBuf.clear(); + log.info("所有的的数据为:{}",dataList); + return dataList; + } + + + private void passThrough(ByteBuf passthroughBuf) { + try { + if (serviceProvider != null && socketGateway != null) { + PassthroughService service = (PassthroughService) serviceProvider.getByName(ServiceName.Passthrough); + service.transfer(passthroughBuf, socketGateway.getInstanceVo().getPassthroughAddress()); + } + } catch (Exception e) { + log.error("荧光光谱仪协议透传失败:" + e.getLocalizedMessage(), e); + } + } + + public String gwPrefix() { + if (gwPrefixCache != null) { + return gwPrefixCache; + } + + gwPrefixCache = ""; + if (socketGateway != null && socketGateway.getInstanceVo() != null) { + gwPrefixCache = "网关Id:" + socketGateway.getInstanceVo().getRunId() + "连接:" + getChannel() + " "; + } else { + gwPrefixCache = "连接:" + getChannel() + " "; + } + + return gwPrefixCache; + } + + +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftSessionFactory.java b/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftSessionFactory.java new file mode 100644 index 0000000..4c77bc0 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/impl/XRFLeftSessionFactory.java @@ -0,0 +1,39 @@ +package com.cisdi.data.XRFLEFT.impl; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 18:52 + */ +@Slf4j +public class XRFLeftSessionFactory extends AbstractSessionFactory { + + private String onlyOneDeviceId; + + /** + * @description: + * @param: onlyOneDeviceId 设备唯一ID + * @return: void + * @author cuianbing + * @date: 2021/7/7 22:10 + */ + public XRFLeftSessionFactory(String onlyOneDeviceId) { + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new XRFLeftIoSession(this, onlyOneDeviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } + +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/test/ServiceProviderTest.java b/src/main/java/com/cisdi/data/XRFLEFT/test/ServiceProviderTest.java new file mode 100644 index 0000000..61035ff --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/test/ServiceProviderTest.java @@ -0,0 +1,21 @@ +package com.cisdi.data.XRFLEFT.test; + +import com.cisdi.data.sdk.service.Service; +import com.cisdi.data.sdk.service.ServiceProvider; +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:17 + */ +@Slf4j +public class ServiceProviderTest implements ServiceProvider { + + @Override + public Service getByName(String name) { + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/test/StartTest.java b/src/main/java/com/cisdi/data/XRFLEFT/test/StartTest.java new file mode 100644 index 0000000..4fee078 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/test/StartTest.java @@ -0,0 +1,65 @@ +package com.cisdi.data.XRFLEFT.test; + +import com.cisdi.data.XRFRIGHT.impl.XRFRightChannelInitializer; +import com.cisdi.data.XRFRIGHT.impl.XRFRightSessionFactory; +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.vo.GatewayVo; +import io.netty.handler.ssl.OpenSsl; +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:28 + */ +@Slf4j +public class StartTest { + + private static TcpIoService ioService = null; + + private static SessionFactory sessionFactory = null; + + public static void main(String[] args) { + boolean available = OpenSsl.isAvailable(); + log.info("openssl available={}", available); + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + + SessionFactory factory = new XRFRightSessionFactory(null); + + ServiceProviderTest testServiceProvider = new ServiceProviderTest(); + + factory.init(testServiceProvider, null); + + XRFRightChannelInitializer channelInitializer = + new XRFRightChannelInitializer(factory, ByteOrder.BIGENDIAN, null); + + GatewayVo instanceVo = new GatewayVo(); + instanceVo.setByteOrder(ByteOrder.BIGENDIAN); + instanceVo.setParameter("{\"listenIp\":\"0.0.0.0\",\"listenPort\":8888,\"threadSize\":50,\"timeout\":2000}"); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + log.info("plc网关启动成功"); + } else { + log.info("plc网关启动失败"); + } + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/util/ByteBufUtil.java b/src/main/java/com/cisdi/data/XRFLEFT/util/ByteBufUtil.java new file mode 100644 index 0000000..8a060ff --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/util/ByteBufUtil.java @@ -0,0 +1,28 @@ +package com.cisdi.data.XRFLEFT.util; + +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; + +import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump; +import static io.netty.util.internal.StringUtil.NEWLINE; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/9 15:32 + */ +@Slf4j +public class ByteBufUtil { + public static void log(ByteBuf byteBuf){ + int length = byteBuf.readableBytes(); + int row = length / 16 +(length % 15 == 0 ? 0 : 1) + 4 ; + StringBuilder buf = new StringBuilder(row * 80 * 2) + .append("read index:").append(byteBuf.readerIndex()) + .append(" write index:").append(byteBuf.writerIndex()) + .append(" capacity:").append(byteBuf.capacity()) + .append(NEWLINE); + appendPrettyHexDump(buf,byteBuf); + log.info(buf.toString()); + } +} diff --git a/src/main/java/com/cisdi/data/XRFLEFT/util/HexTransform.java b/src/main/java/com/cisdi/data/XRFLEFT/util/HexTransform.java new file mode 100644 index 0000000..d9c5965 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFLEFT/util/HexTransform.java @@ -0,0 +1,27 @@ +package com.cisdi.data.XRFLEFT.util; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: 16进制转换工具类 + * @date 2021/7/7 21:41 + */ +@Slf4j +public class HexTransform { + + /** + * @description: + * @param: hex 16进制字符串 + * @param: chartSet 字符集 UTF-8 GB2312 ASCII + * @return: java.lang.String + * @author cuianbing + * @date: 2021/7/7 21:43 + */ + public static String HexToString(String hex,String chartSet){ + + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/XRFRightSocketGateway.java b/src/main/java/com/cisdi/data/XRFRIGHT/XRFRightSocketGateway.java new file mode 100644 index 0000000..d80ceee --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/XRFRightSocketGateway.java @@ -0,0 +1,145 @@ +package com.cisdi.data.XRFRIGHT; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.XRFRIGHT.impl.XRFRightChannelInitializer; +import com.cisdi.data.XRFRIGHT.impl.XRFRightIoSession; +import com.cisdi.data.XRFRIGHT.impl.XRFRightSessionFactory; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.PlatformService; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.cisdi.data.sdk.vo.SslConfigVo; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:18 + */ +@Slf4j +public class XRFRightSocketGateway extends SocketGatewayBase { + + + TcpIoService ioService = null; + SessionFactory sessionFactory = null; + + // 当配置为单个设备模式时,此为该设备id + String onlyOneDeviceId = null; + + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage socketReturnMessage) { + throw new BusinessException("X射线荧光光谱仪不支持下发服务!"); + } + + @Override + public void start() { + if (state == GatewayState.RUNNING) { + return; + } + log.info("网关:{}读取启动配置参数:{},是否支持支持多个设备:{}", instanceVo.getRunId(), instanceVo.getParameter(), + instanceVo.getSuportMultiDevice()); + + + // 不支持多个设备,必须强制配置一个设备id + if (instanceVo.getSuportMultiDevice() != null && instanceVo.getSuportMultiDevice() == false) { + RouteService routeService = (RouteService) serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if (deviceVos.size() > 1) { + throw new BusinessException("网关Id:" + instanceVo.getRunId() + "为X射线荧光光谱仪网关,当前配置为只支持单个设备,但配置了多个设备。"); + } + + onlyOneDeviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if (StringUtils.isEmpty(onlyOneDeviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + } + + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new XRFRightSessionFactory(onlyOneDeviceId); + factory.init(serviceProvider, this); + + SslConfigVo sslConfigVo = null; + + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + if (socketParam != null && Boolean.TRUE.equals(socketParam.getEnableSsl())) { + + PlatformService platformService = (PlatformService) serviceProvider.getByName(ServiceName.Platform); + + sslConfigVo = platformService.getSslConfig(); + } + + + XRFRightChannelInitializer channelInitializer = + new XRFRightChannelInitializer(factory, getInstanceVo().getByteOrder(), sslConfigVo); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + state = GatewayState.RUNNING; + log.info("X射线荧光光谱仪网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + } else { + log.info("X射线荧光光谱仪网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + + } + + @Override + public void shutdown() { + if (state == GatewayState.CLOSED) { + return; + } + + if (sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + XRFRightIoSession xrfRightIoSession = (XRFRightIoSession) ioSession; + xrfRightIoSession.close(); + } + } + + boolean close = ioService.close(); + if (close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + log.info("X射线荧光光谱仪网关:{}关闭成功", instanceVo.getRunId()); + } else { + log.info("X射线荧光光谱仪网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds() { + if (onlyOneDeviceId != null) { + Set set = new HashSet(); + set.add(onlyOneDeviceId); + return set; + } else { + return sessionFactory.getActiveDeviceIds(); + } + } +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightChannelInitializer.java b/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightChannelInitializer.java new file mode 100644 index 0000000..f4c7f0a --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightChannelInitializer.java @@ -0,0 +1,75 @@ +package com.cisdi.data.XRFRIGHT.impl; + +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import com.cisdi.data.sdk.utils.NettySslUtils; +import com.cisdi.data.sdk.vo.SslConfigVo; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 18:57 + */ +@Slf4j +public class XRFRightChannelInitializer extends AbstractChannelInitializer { + + private SessionFactory factory; + private ByteOrder byteOrder; + private SslConfigVo sslConfigVo; + + + /** + * 构造通道初始化器对象 + * @param factory 会话工厂 + * @param byteOrder 大小端 + * @param sslConfig ssl证书配置,如果不启用tls,传null + */ + public XRFRightChannelInitializer(SessionFactory factory, + ByteOrder byteOrder, + SslConfigVo sslConfig) { + this.factory = factory; + this.byteOrder = byteOrder; + this.sslConfigVo = sslConfig; + } + + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + ChannelHandler sslHandler = null; + try { + if(sslConfigVo != null) { + sslHandler = NettySslUtils.getServerSslHandler((SocketChannel)socketChannel, sslConfigVo); + } + } catch (Exception e) { + log.warn(e.getLocalizedMessage(), e); + } + return sslHandler; + } +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightIoSession.java b/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightIoSession.java new file mode 100644 index 0000000..a603ba5 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightIoSession.java @@ -0,0 +1,183 @@ +package com.cisdi.data.XRFRIGHT.impl; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.gateway.message.SocketMessage; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoSession; +import com.cisdi.data.sdk.service.PassthroughService; +import com.cisdi.data.sdk.service.SendService; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import lombok.extern.slf4j.Slf4j; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 18:53 + */ +@Slf4j +public class XRFRightIoSession extends AbstractIoSession implements IoSession { + + private XRFRightSessionFactory factory = null; + + private String deviceId = null; + /** + * 如果有值,代表所有会话都使用这一个设备id,且允许多会话共享此一个设备id,不T除旧会话 + */ + private String onlyOneDeviceId; + + private String gwPrefixCache = null; + + private ByteBuf allBuf = ByteBufAllocator.DEFAULT.buffer(); + + + public XRFRightIoSession(XRFRightSessionFactory factory, String onlyOneDeviceId) { + super(); + this.factory = factory; + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public String[] getDeviceIds() { + String cpDeviceId = deviceId; + String[] result = new String[cpDeviceId == null ? 0 : 1]; + if (cpDeviceId != null) { + result[0] = cpDeviceId; + } + return result; + } + + @Override + public void onRead(Object message) { + try { + + Map resultMap = new HashMap<>(); + Integer readerIndex = null; + /** + * message head + */ + ByteBuf inBuf = (ByteBuf) message; + if (inBuf != null) { + allBuf.writeBytes(inBuf); + }else { + log.info("电文格式为空"); + } + }catch (Exception e){ + log.error("电文格式错误!"); + throw new BusinessException("电文格式错误!"); + } + + } + + @Override + public void onOpen() { + super.onOpen(); + log.info("建立连接,channel:{}", getChannel()); + } + + @Override + public void onClose() { + log.info("onClose"); + if (allBuf != null && allBuf.readableBytes() > 0){ + Map message = decode(allBuf); + Map sendMessage =new HashMap<>(); + sendMessage.put("data",message); + if (serviceProvider != null && socketGateway != null) { + SendService service = (SendService) serviceProvider.getByName(ServiceName.Send); + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(onlyOneDeviceId); + socketMessage.setData(JSON.toJSONString(sendMessage).getBytes(Charset.forName("UTF-8"))); + socketMessage.setMsgKey("default"); + service.sendMessage(socketMessage); + if (socketGateway.getInstanceVo().getLogOpen()) { + log.info("类似为:{}:发送消息内容{}", sendMessage.get("data"), sendMessage.toString()); + } + } + } + super.onClose(); + factory = null; + } + + + private void passThrough(ByteBuf passthroughBuf) { + try { + if (serviceProvider != null && socketGateway != null) { + PassthroughService service = (PassthroughService) serviceProvider.getByName(ServiceName.Passthrough); + service.transfer(passthroughBuf, socketGateway.getInstanceVo().getPassthroughAddress()); + } + } catch (Exception e) { + log.error("荧光光谱仪协议透传失败:" + e.getLocalizedMessage(), e); + } + } + + public String gwPrefix() { + if (gwPrefixCache != null) { + return gwPrefixCache; + } + + gwPrefixCache = ""; + if (socketGateway != null && socketGateway.getInstanceVo() != null) { + gwPrefixCache = "网关Id:" + socketGateway.getInstanceVo().getRunId() + "连接:" + getChannel() + " "; + } else { + gwPrefixCache = "连接:" + getChannel() + " "; + } + + return gwPrefixCache; + } + + + public void close() { + if (getChannel() != null) { + try { + getChannel().close().sync(); + } catch (InterruptedException e) { + log.warn(e.getLocalizedMessage(), e); + } + } + } + + + public Map decode(ByteBuf byteBuf){ + Map data = new HashMap(); + String msg = byteBuf.toString(Charset.forName("GB2312")); + String[] strLines = msg.split("\n\r"); + for (String strLine : strLines){ + char startChat = strLine.charAt(0); + if (startChat == '2'){ + // 27 石灰石 2021071600113-010720210717150951 + // 27 硅锰合金 071600096 1120210717155618 + String[] word = strLine.split("\\s+"); + data.put("编码",word[0]); + data.put("code",word[1]); + if (word[2].length() > 16){ + data.put("name",word[2].substring(0,16)); + data.put("dateTime",word[2].substring(18,30)); + }else { + data.put("name",word[2]); + data.put("dateTime",word[3].substring(2,14)); + } + + + }else if (startChat == '3'){ + String[] word = strLine.substring(1).split("\\s+"); + for (int i = 0; i < word.length -1 ;i = i+2){ + data.put(word[i],word[i+1]); + } + } + } + + allBuf.clear(); + log.debug("解析的数据为:{}",data); + return data; + } + + + +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightSessionFactory.java b/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightSessionFactory.java new file mode 100644 index 0000000..20793b5 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/impl/XRFRightSessionFactory.java @@ -0,0 +1,39 @@ +package com.cisdi.data.XRFRIGHT.impl; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 18:52 + */ +@Slf4j +public class XRFRightSessionFactory extends AbstractSessionFactory { + + private String onlyOneDeviceId; + + /** + * @description: + * @param: onlyOneDeviceId 设备唯一ID + * @return: void + * @author cuianbing + * @date: 2021/7/7 22:10 + */ + public XRFRightSessionFactory(String onlyOneDeviceId) { + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new XRFRightIoSession(this, onlyOneDeviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } + +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/test/ServiceProviderTest.java b/src/main/java/com/cisdi/data/XRFRIGHT/test/ServiceProviderTest.java new file mode 100644 index 0000000..904ee5f --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/test/ServiceProviderTest.java @@ -0,0 +1,21 @@ +package com.cisdi.data.XRFRIGHT.test; + +import com.cisdi.data.sdk.service.Service; +import com.cisdi.data.sdk.service.ServiceProvider; +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:17 + */ +@Slf4j +public class ServiceProviderTest implements ServiceProvider { + + @Override + public Service getByName(String name) { + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/test/StartTest.java b/src/main/java/com/cisdi/data/XRFRIGHT/test/StartTest.java new file mode 100644 index 0000000..08bdf9b --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/test/StartTest.java @@ -0,0 +1,67 @@ +package com.cisdi.data.XRFRIGHT.test; + +import com.cisdi.data.XRFRIGHT.impl.XRFRightChannelInitializer; +import com.cisdi.data.XRFRIGHT.impl.XRFRightSessionFactory; +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.vo.GatewayVo; +import io.netty.handler.ssl.OpenSsl; +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/7 19:28 + */ +@Slf4j +public class StartTest { + + private static TcpIoService ioService = null; + + private static SessionFactory sessionFactory = null; + + public static void main(String[] args) { + boolean available = OpenSsl.isAvailable(); + log.info("哈哈哈哈哈哈"); + log.info("铁矿石"); + log.info("openssl available={}", available); + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + + SessionFactory factory = new XRFRightSessionFactory(null); + + ServiceProviderTest testServiceProvider = new ServiceProviderTest(); + + factory.init(testServiceProvider, null); + + XRFRightChannelInitializer channelInitializer = + new XRFRightChannelInitializer(factory, ByteOrder.BIGENDIAN, null); + + GatewayVo instanceVo = new GatewayVo(); + instanceVo.setByteOrder(ByteOrder.BIGENDIAN); + instanceVo.setParameter("{\"listenIp\":\"0.0.0.0\",\"listenPort\":8888,\"threadSize\":50,\"timeout\":2000}"); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if (open == true) { + log.info("plc网关启动成功"); + } else { + log.info("plc网关启动失败"); + } + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/util/ByteBufUtil.java b/src/main/java/com/cisdi/data/XRFRIGHT/util/ByteBufUtil.java new file mode 100644 index 0000000..64c0953 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/util/ByteBufUtil.java @@ -0,0 +1,28 @@ +package com.cisdi.data.XRFRIGHT.util; + +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; + +import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump; +import static io.netty.util.internal.StringUtil.NEWLINE; + +/** + * @author cuianbing + * @version 1.0 + * @description: TODO + * @date 2021/7/9 15:32 + */ +@Slf4j +public class ByteBufUtil { + public static void log(ByteBuf byteBuf){ + int length = byteBuf.readableBytes(); + int row = length / 16 +(length % 15 == 0 ? 0 : 1) + 4 ; + StringBuilder buf = new StringBuilder(row * 80 * 2) + .append("read index:").append(byteBuf.readerIndex()) + .append(" write index:").append(byteBuf.writerIndex()) + .append(" capacity:").append(byteBuf.capacity()) + .append(NEWLINE); + appendPrettyHexDump(buf,byteBuf); + log.info(buf.toString()); + } +} diff --git a/src/main/java/com/cisdi/data/XRFRIGHT/util/HexTransform.java b/src/main/java/com/cisdi/data/XRFRIGHT/util/HexTransform.java new file mode 100644 index 0000000..f63f444 --- /dev/null +++ b/src/main/java/com/cisdi/data/XRFRIGHT/util/HexTransform.java @@ -0,0 +1,27 @@ +package com.cisdi.data.XRFRIGHT.util; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author cuianbing + * @version 1.0 + * @description: 16进制转换工具类 + * @date 2021/7/7 21:41 + */ +@Slf4j +public class HexTransform { + + /** + * @description: + * @param: hex 16进制字符串 + * @param: chartSet 字符集 UTF-8 GB2312 ASCII + * @return: java.lang.String + * @author cuianbing + * @date: 2021/7/7 21:43 + */ + public static String HexToString(String hex,String chartSet){ + + return null; + } + +} diff --git a/src/main/java/com/cisdi/data/emserver/EmiaProSocketGateway.java b/src/main/java/com/cisdi/data/emserver/EmiaProSocketGateway.java new file mode 100644 index 0000000..e481cb0 --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/EmiaProSocketGateway.java @@ -0,0 +1,173 @@ +package com.cisdi.data.emserver; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.cisdi.data.emserver.gateway.impl.EmiaProIoSession; +import com.cisdi.data.emserver.gateway.impl.EmiaProSessionFactory; +import com.cisdi.data.emserver.gateway.vo.OtherParameterVo; +import com.cisdi.data.emserver.gateway.impl.AliveCheckTask; +import com.cisdi.data.emserver.gateway.impl.EmiaProChannelInitializer; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.cisdi.data.common.exception.BusinessException; + +import com.cisdi.data.sdk.consts.ServiceName; +import com.cisdi.data.sdk.enums.GatewayState; +import com.cisdi.data.sdk.gateway.base.SocketGatewayBase; +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.param.ListenSocketParam; +import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +import com.cisdi.data.sdk.service.PlatformService; +import com.cisdi.data.sdk.service.RouteService; +import com.cisdi.data.sdk.vo.DeviceVo; +import com.cisdi.data.sdk.vo.ExeResultVo; +import com.cisdi.data.sdk.vo.SslConfigVo; + + +public class EmiaProSocketGateway extends SocketGatewayBase { + + private static Logger logger = LoggerFactory.getLogger(EmiaProSocketGateway.class); + + TcpIoService ioService = null; + SessionFactory sessionFactory = null; + OtherParameterVo parameterVo = null; + AtomicBoolean shouldRun = null; + + // 当配置为单个设备模式时,此为该设备id + String onlyOneDeviceId = null; + + /** + * 下发的时候,查配置路由,如果配置路由里面有匹配的,则走配置路由;如果没有匹配的,则原路返回 + */ + @Override + public ExeResultVo sendReturnMessage(SocketReturnMessage returnMsg) { + ExeResultVo resultVo = new ExeResultVo(); + resultVo.setMessage("该协议不允许下发数据"); + resultVo.setSuccess(false); + + logger.info("plc网关:{}->设备,消息:{},结果:{}",instanceVo.getRunId(), returnMsg, resultVo); + + return resultVo; + + } + + + @Override + public void start() { + if(state == GatewayState.RUNNING) { + return; + } + + logger.info("EMIA-PRO网关:{}读取启动配置参数:{},支持多个设备:{}",instanceVo.getRunId(), instanceVo.getParameter(), + instanceVo.getSuportMultiDevice()); + + // 不支持多个设备,必须强制配置一个设备id + if(instanceVo.getSuportMultiDevice() != null && instanceVo.getSuportMultiDevice() == false) { + RouteService routeService = (RouteService)serviceProvider.getByName(ServiceName.Route); + List deviceVos = routeService.findByRunId(instanceVo.getRunId()); + + if(deviceVos.size() > 1) { + throw new BusinessException("网关Id:" +instanceVo.getRunId() + "为EMIA-PRO网关,当前配置为只支持单个设备,但配置了多个设备。"); + } + + onlyOneDeviceId = deviceVos.size() > 0 ? deviceVos.get(0).getDeviceId() : null; + + if(StringUtils.isEmpty(onlyOneDeviceId)) { + throw new BusinessException("runId=" + getInstanceVo().getRunId() + "未正确配置网关和设备关联"); + } + } + + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new EmiaProSessionFactory(onlyOneDeviceId); + factory.init(serviceProvider, this); + + SslConfigVo sslConfigVo = null; + + ListenSocketParam socketParam = JSON.parseObject(getInstanceVo().getParameter(), ListenSocketParam.class); + + if(socketParam != null && Boolean.TRUE.equals(socketParam.getEnableSsl())) { + + PlatformService platformService = (PlatformService)serviceProvider.getByName(ServiceName.Platform); + + sslConfigVo = platformService.getSslConfig(); + } + + if(StringUtils.isNotEmpty(socketParam.getOtherParameter())) { + String paramString = socketParam.getOtherParameter(); + + OtherParameterVo otherVo = JSON.parseObject(paramString, OtherParameterVo.class); + + parameterVo = otherVo; + } + + EmiaProChannelInitializer channelInitializer = + new EmiaProChannelInitializer(factory, getInstanceVo().getByteOrder(), sslConfigVo); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if(open) { + state = GatewayState.RUNNING; + logger.info("plc网关:{}启动成功 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + + shouldRun = new AtomicBoolean(true); + + Integer keepAlive = socketParam.getKeepAlive(); + + AliveCheckTask task = new AliveCheckTask(keepAlive, shouldRun, factory); + + Thread thread = new Thread(task, "EMIA-PRO-alive-check-thread-" + getInstanceVo().getRunId()); + thread.start(); + }else { + logger.info("plc网关:{}启动失败 参数:{}", instanceVo.getRunId(), getInstanceVo().getParameter()); + } + } + + @Override + public void shutdown() { + if(state == GatewayState.CLOSED) { + return; + } + + if(sessionFactory != null) { + for (IoSession ioSession : sessionFactory.getSessions()) { + EmiaProIoSession emiaProIoSession = (EmiaProIoSession)ioSession; + emiaProIoSession.close(); + } + } + + boolean close = ioService.close(); + if(close == true) { + sessionFactory = null; + state = GatewayState.CLOSED; + logger.info("plc网关:{}关闭成功", instanceVo.getRunId()); + }else { + logger.info("plc网关:{}关闭失败", instanceVo.getRunId()); + } + } + + @Override + public Set getActiveDeviceIds(){ + if(onlyOneDeviceId != null) { + Set set = new HashSet(); + set.add(onlyOneDeviceId); + return set; + }else { + return sessionFactory.getActiveDeviceIds(); + } + } +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/EMServerTest.java b/src/main/java/com/cisdi/data/emserver/gateway/EMServerTest.java new file mode 100644 index 0000000..a009f30 --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/EMServerTest.java @@ -0,0 +1,64 @@ +package com.cisdi.data.emserver.gateway; + +import com.cisdi.data.emserver.gateway.impl.AliveCheckTask; +import com.cisdi.data.emserver.gateway.impl.EmiaProChannelInitializer; +import com.cisdi.data.emserver.gateway.impl.EmiaProSessionFactory; +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.TcpIoService; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultTcpIoService; +import com.cisdi.data.sdk.vo.GatewayVo; +import io.netty.handler.ssl.OpenSsl; + +import java.io.UnsupportedEncodingException; +import java.util.concurrent.atomic.AtomicBoolean; + +public class EMServerTest { + private static TcpIoService ioService = null; + @SuppressWarnings("unused") + private static SessionFactory sessionFactory = null; + + + public static void main(String[] args) throws UnsupportedEncodingException { + String testmsg=(char)0x02+",T1234,S0123456789ABCDEFGHIJ,012.123456 ,N 0.987654 ,H 0.987654 ,W 0.987654 ,0 36,N 45,H 45,M3,H12:00,D01M12Y10,"+(char)0x0a+(char)0x0b+",A001196302303,N "+(char)0x0a+(char)0x0b+(char)0x03; + boolean available = OpenSsl.isAvailable(); + + DefaultTcpIoService defaultIoService = new DefaultTcpIoService(); + SessionFactory factory = new EmiaProSessionFactory("10000034"); + + factory.init(null, null); + + EmiaProChannelInitializer channelInitializer = + new EmiaProChannelInitializer(factory, ByteOrder.BIGENDIAN, null); + + GatewayVo instanceVo = new GatewayVo(); + instanceVo.setByteOrder(ByteOrder.BIGENDIAN); + instanceVo.setParameter("{\"listenIp\":\"10.76.48.66\",\"listenPort\":5002,\"threadSize\":50,\"timeout\":10}"); + + defaultIoService.init(instanceVo, factory, channelInitializer); + + ioService = defaultIoService; + sessionFactory = factory; + + boolean open = ioService.open(); + + if(open == true) { + + AtomicBoolean shouldRun = new AtomicBoolean(true); + AliveCheckTask task = new AliveCheckTask(10, shouldRun, factory); + + Thread thread = new Thread(task, "plc-alive-check-thread"); + thread.start(); + + System.out.println("plc网关启动成功"); + }else { + System.out.println("plc网关启动失败"); + } + + try { + Thread.sleep(10000000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/AliveCheckTask.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/AliveCheckTask.java new file mode 100644 index 0000000..a90b816 --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/AliveCheckTask.java @@ -0,0 +1,68 @@ +package com.cisdi.data.emserver.gateway.impl; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; + +/** + * 保活时间检测,如果超过指定时间,则关闭连接 + * @author cup + * + */ +public class AliveCheckTask implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(AliveCheckTask.class); + + private SessionFactory sessionFactory; + private AtomicBoolean shouldRun = null; + + /** + * 会话允许的保活时间,单位秒 + */ + private final long keepAlive; + + private static final double multiply = 1.5; + + private static final int sleepInternal = 1000; // 1秒 + + public AliveCheckTask(int keepAlive, AtomicBoolean shouldRun, SessionFactory sessionFactory) { + super(); + this.keepAlive = keepAlive; + this.shouldRun = shouldRun; + this.sessionFactory = sessionFactory; + } + + @Override + public void run() { + logger.info("启动plc保活超时检测线程"); + + while (shouldRun != null && shouldRun.get() == true) { + try { + long now = System.currentTimeMillis(); + + List sessions = sessionFactory.getSessions(); + + for (IoSession ioSession : sessions) { + EmiaProIoSession session = (EmiaProIoSession)ioSession; + + // 超出指定倍数保活时间, + if((now - session.getLastAliveTime()) > ( keepAlive * 1000)) { + session.close(); + logger.warn("{} 超出指定保活时间{},单位秒,关闭通道", + session.gwPrefix(), keepAlive); + } + } + + Thread.sleep(sleepInternal); + } catch (Exception e) { + logger.warn(e.getLocalizedMessage(), e); + } + } + + logger.info("结束plc保活超时检测线程"); + } +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProChannelInitializer.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProChannelInitializer.java new file mode 100644 index 0000000..f67f407 --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProChannelInitializer.java @@ -0,0 +1,75 @@ +package com.cisdi.data.emserver.gateway.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cisdi.data.sdk.enums.ByteOrder; +import com.cisdi.data.sdk.gateway.netty.SessionFactory; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractChannelInitializer; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractIoChannelHandler; +import com.cisdi.data.sdk.gateway.netty.impl.DefaultIoChannelHandler; +import com.cisdi.data.sdk.utils.NettySslUtils; +import com.cisdi.data.sdk.vo.SslConfigVo; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; + +public class EmiaProChannelInitializer extends AbstractChannelInitializer { + + private static final Logger logger = LoggerFactory.getLogger(EmiaProChannelInitializer.class); + + private SessionFactory factory; + private ByteOrder byteOrder; + private SslConfigVo sslConfigVo; + + /** + * 构造通道初始化器对象 + * @param factory 会话工厂 + * @param byteOrder 大小端 + * @param sslConfig ssl证书配置,如果不启用tls,传null + */ + public EmiaProChannelInitializer(SessionFactory factory, + ByteOrder byteOrder, + SslConfigVo sslConfig) { + this.factory = factory; + this.byteOrder = byteOrder; + this.sslConfigVo = sslConfig; + } + + @SuppressWarnings("unchecked") + @Override + protected AbstractIoChannelHandler getTailHandler() { + return new DefaultIoChannelHandler(factory); + } + + @Override + protected List getHandlers() { + List handlers = new ArrayList(); + handlers.add(new EmiaProFrameDecoder()); + return handlers; + } + + @Override + protected IdleStateHandler getIdleStateHandler() { + return new IdleStateHandler(15, 15, 30); + } + + @Override + protected ChannelHandler getSslChannelHandler(Channel socketChannel) { + ChannelHandler sslHandler = null; + try { + if(sslConfigVo != null) { + sslHandler = NettySslUtils.getServerSslHandler((SocketChannel)socketChannel, sslConfigVo); + } + } catch (Exception e) { + logger.warn(e.getLocalizedMessage(), e); + } + return sslHandler; + } + +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProConsts.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProConsts.java new file mode 100644 index 0000000..059bafd --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProConsts.java @@ -0,0 +1,13 @@ +package com.cisdi.data.emserver.gateway.impl; + +public class EmiaProConsts { + /** + * 心跳电文号 + */ + public static final String Heartbeat_Msgkey = "50"; + + /** + * 顺序号,凡是遇到此顺序号,则不处理重复电文 + */ + public static final Short Dummy_Sequence = 0; +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProFrameDecoder.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProFrameDecoder.java new file mode 100644 index 0000000..6c01e1b --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProFrameDecoder.java @@ -0,0 +1,61 @@ +package com.cisdi.data.emserver.gateway.impl; + +import java.util.List; + +import com.cisdi.data.common.exception.BusinessException; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.util.CharsetUtil; + +/** + * +*

Title: 工业物联网标准plc协议 FrameDecoder.java

+*

Description: 得到一个完整报文长度

+* @author lhp +* @date 2019年6月18日 +* @version 1.0 + */ +public class EmiaProFrameDecoder extends ByteToMessageDecoder { + + private static final Byte STARTF_LAG=0x02; + private static final Byte END_FLAG=0x03; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bufferIn, List out) throws Exception + { + boolean getStartFlag=false; + boolean getEndFlag=false; + int beginIndex = bufferIn.readerIndex(); + int endIndex=beginIndex; + for(int i=0;iTitle: StandardIoSession.java

+*

Description: 标准Socket协议会话实现

+* @author lhp +* @date 2019年2月12日 +* @version 1.0 + */ +public class EmiaProIoSession extends AbstractIoSession implements IoSession { + + private long lastAliveTime = System.currentTimeMillis(); + + private static Logger logger = LoggerFactory.getLogger(EmiaProIoSession.class); + + private EmiaProSessionFactory factory = null; + + private String deviceId = null; + + private Short prevSeq = EmiaProConsts.Dummy_Sequence; + + protected Set clientDeviceIds = Sets.newConcurrentHashSet(); + + /** + * 如果有值,代表所有会话都使用这一个设备id,且允许多会话共享此一个设备id,不T除旧会话 + */ + private String onlyOneDeviceId; + + public EmiaProIoSession(EmiaProSessionFactory factory, String onlyOneDeviceId) { + super(); + this.factory = factory; + this.onlyOneDeviceId = onlyOneDeviceId; + } + + @Override + public void onRead(Object message) { + + ByteOrder byteOrder = socketGateway == null ? ByteOrder.BIGENDIAN : socketGateway.getInstanceVo().getByteOrder(); + + int length = ((ByteBuf)message).readableBytes(); + ByteBuf inBuf = (ByteBuf)message; + String data=inBuf.toString(CharsetUtil.UTF_8); + //剔除分隔符, + data=data.replace(",",""); + Integer readerIndex = null; + + try { + // 预备处理透传 + if(socketGateway != null && Boolean.TRUE.equals(socketGateway.getInstanceVo().getNeedPassthrough())) { + readerIndex = inBuf.readerIndex(); + } + + if(socketGateway!= null && Boolean.TRUE.equals(socketGateway.getInstanceVo().getLogOpen())) { + logger.info("EMIA-PRO {} 报文长度:{} vo:{} ", getChannel(), length, data); + } + + + if(serviceProvider != null && socketGateway != null&& onlyOneDeviceId!=null) { + String runId = socketGateway.getInstanceVo().getRunId(); + RouteService routeService = (RouteService)serviceProvider.getByName(ServiceName.Route); + DeviceVo deviceVo = routeService.findDeviceByDeviceId(onlyOneDeviceId); + if(deviceVo == null || !Strings.equals(runId, deviceVo.getRunId())) { + logger.error("查找不到 deviceId={} 的设备或此设备和本连接未正确配置关联,关闭当前会话", onlyOneDeviceId); + close(); + } + } + + // 需要检查之前是否存在,必须为多设备支持才如此, 如果存在,则T掉之前的会话 + if(factory != null && onlyOneDeviceId != null) { + IoSession ioSession = factory.getSessionByDeviceId(onlyOneDeviceId); + // 发现了已有设备会话,T掉 + if(ioSession != null && ioSession != this) { + logger.error("发现设备:{}已存在会话:{},同一设备只能绑定一个会话,自动将旧会话关闭", + onlyOneDeviceId, ioSession.getChannel()); + EmiaProIoSession session = (EmiaProIoSession)ioSession; + session.close(); + } + factory.newDeviceConnect(onlyOneDeviceId, this); + } + + lastAliveTime = System.currentTimeMillis(); + + + if(StringUtils.isNotBlank(data) && serviceProvider != null && socketGateway != null) { + SendService service = (SendService)serviceProvider.getByName(ServiceName.Send); + + SocketMessage socketMessage = socketGateway.buildSocketMessage(); + socketMessage.setDeviceId(onlyOneDeviceId); + socketMessage.setData(data.getBytes()); + socketMessage.setMsgKey("default"); + socketMessage.getPropsMap().put("charset", Charset.forName("UTF-8")); + + service.sendMessage(socketMessage); + }else { + String hexString = HexUtils.toHexString(data.getBytes()); + logger.info("长度:{},报文:{}", data.length(), hexString); + } + } finally { + if(readerIndex != null) { // 需要透传 + inBuf.readerIndex(readerIndex); + passthrough(inBuf); + } + } + + } + + private void passthrough(ByteBuf passthroughBuf) { + try { + if(serviceProvider != null && socketGateway != null) { + PassthroughService service = (PassthroughService)serviceProvider.getByName(ServiceName.Passthrough); + service.transfer(passthroughBuf, socketGateway.getInstanceVo().getPassthroughAddress()); + } + }catch (Exception e) { + logger.error("tcp标准协议透传失败:" + e.getLocalizedMessage(), e); + } + } + + @Override + public void onOpen() { + super.onOpen(); + logger.info("建立连接,channel:{}", getChannel()); + } + + @Override + public void onClose() { + super.onClose(); + factory = null; + } + + public void close() { + if(getChannel() != null) { + try { + getChannel().close().sync(); + } catch (InterruptedException e) { + logger.warn(e.getLocalizedMessage(), e); + } + } + } + + private String gwPrefixCache = null; + + public String gwPrefix() { + if(gwPrefixCache != null) { + return gwPrefixCache; + } + + gwPrefixCache = ""; + if(socketGateway != null && socketGateway.getInstanceVo() != null) { + gwPrefixCache = "网关Id:" + socketGateway.getInstanceVo().getRunId() + "连接:" + getChannel() + " "; + }else { + gwPrefixCache = "连接:" + getChannel() + " "; + } + + return gwPrefixCache; + } + + public long getLastAliveTime() { + return lastAliveTime; + } + + @Override + public String[] getDeviceIds() { + String cpDeviceId = deviceId; + String[] result = new String[cpDeviceId == null ? 0 : 1]; + if(cpDeviceId != null) { + result[0] = cpDeviceId; + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProSessionFactory.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProSessionFactory.java new file mode 100644 index 0000000..bff7598 --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProSessionFactory.java @@ -0,0 +1,23 @@ +package com.cisdi.data.emserver.gateway.impl; + +import java.util.UUID; + +import com.cisdi.data.sdk.gateway.netty.IoSession; +import com.cisdi.data.sdk.gateway.netty.impl.AbstractSessionFactory; + +public class EmiaProSessionFactory extends AbstractSessionFactory { + + private String onlyOneDeviceId; + + public EmiaProSessionFactory(String onlyoneDeviceId) { + this.onlyOneDeviceId = onlyoneDeviceId; + } + + @Override + public IoSession newSession() { + IoSession session = new EmiaProIoSession(this, onlyOneDeviceId); + session.init(UUID.randomUUID().toString(), provider, socketGateway); + sessionSet.add(session); + return session; + } +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVo.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVo.java new file mode 100644 index 0000000..19775cf --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVo.java @@ -0,0 +1,26 @@ +//package com.cisdi.data.emia.gateway.impl; +// +//import java.io.Serializable; +// +//import com.alibaba.fastjson.JSON; +// +//public class EmiaProVo implements Serializable { +// private static final long serialVersionUID = 1251102466675497863L; +// +// private byte[] body; +// +// +// public byte[] getBody() { +// return body; +// } +// +// public void setBody(byte[] body) { +// this.body = body; +// } +// +// @Override +// public String toString() { +// String jsonString = JSON.toJSONString(this); +// return jsonString; +// } +//} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVoDecode.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVoDecode.java new file mode 100644 index 0000000..bc88c9f --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVoDecode.java @@ -0,0 +1,58 @@ +//package com.cisdi.data.emia.gateway.impl; +// +//import com.cisdi.data.sdk.enums.ByteOrder; +// +//import io.netty.buffer.ByteBuf; +// +///** +// * +//*

Title: StandardVoDecode.java

+//*

Description: 将报文做下一步分解

+//* @author lhp +//* @date 2019年3月22日 +//* @version 1.0 +// */ +//public class EmiaProVoDecode { +// +// public static EmiaProVo Decode(ByteBuf in, ByteOrder byteOrder) { +// +// EmiaProVo result = new EmiaProVo(); +// +// if(ByteOrder.LITTLEENDIAN.code().equals(byteOrder.code())) { +// short length = in.readShortLE(); +// short msgId = in.readShortLE(); +// short seq = in.readShortLE(); +// short reserved = in.readShortLE(); +// int deviceId = in.readIntLE(); +// int destDeviceId = in.readIntLE(); +// +// result.setLength(length); +// result.setMsgKey(String.valueOf(msgId)); +// result.setSeq(seq); +// result.setReserved(reserved); +// result.setDeviceId(String.valueOf(deviceId)); +// result.setDestDeviceId(String.valueOf(destDeviceId)); +// }else { +// short length = in.readShort(); +// short msgId = in.readShort(); +// short seq = in.readShort(); +// short reserved = in.readShort(); +// int deviceId = in.readInt(); +// int destDeviceId = in.readInt(); +// +// result.setLength(length); +// result.setMsgKey(String.valueOf(msgId)); +// result.setSeq(seq); +// result.setReserved(reserved); +// result.setDeviceId(String.valueOf(deviceId)); +// result.setDestDeviceId(String.valueOf(destDeviceId)); +// } +// +// byte[] bodyBytes = new byte[result.getLength() - 16]; +// in.readBytes(bodyBytes); +// +// result.setBody(bodyBytes); +// +// return result; +// } +//} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVoEncoder.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVoEncoder.java new file mode 100644 index 0000000..9f2432b --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/EmiaProVoEncoder.java @@ -0,0 +1,96 @@ +//package com.cisdi.data.emia.gateway.impl; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import com.cisdi.data.common.exception.BusinessException; +//import com.cisdi.data.sdk.enums.ByteOrder; +//import com.cisdi.data.sdk.procotol.message.SocketReturnMessage; +// +//import io.netty.buffer.ByteBuf; +//import io.netty.buffer.Unpooled; +//import io.netty.channel.ChannelHandlerContext; +//import io.netty.handler.codec.MessageToByteEncoder; +// +//public class EmiaProVoEncoder extends MessageToByteEncoder { +// private ByteOrder byteOrder = ByteOrder.BIGENDIAN; +// +// public EmiaProVoEncoder(ByteOrder byteOrder) { +// this.byteOrder = byteOrder; +// } +// +// private static final Logger logger = LoggerFactory.getLogger(EmiaProVoEncoder.class); +// +// private Object lockObj = new Object(); +// +// @Override +// protected void encode(ChannelHandlerContext ctx, EmiaProVo msg, ByteBuf out) throws Exception { +// try { +// if(msg == null) { +// throw new BusinessException("encode EmiaProVo is null"); +// } +// +// if(msg.getBody() == null) { +// throw new BusinessException("消息体为null"); +// } +// +// synchronized (lockObj) { +// if(ByteOrder.LITTLEENDIAN.code().equals(byteOrder.code())) { +// out.writeShortLE(16 + msg.getBody().length); +// out.writeShortLE(msg.getMsgKey() == null ? 0 : Short.valueOf(msg.getMsgKey())); +// out.writeShortLE(0); +// out.writeShortLE(0); +// out.writeIntLE(msg.getDeviceId() == null ? 0 : Integer.valueOf(msg.getDeviceId())); +// out.writeIntLE(msg.getDestDeviceId() == null ? 0 : Integer.valueOf(msg.getDestDeviceId())); +// out.writeBytes(msg.getBody()); +// }else { +// out.writeShort(16 + msg.getBody().length); +// out.writeShort(msg.getMsgKey() == null ? 0 : Short.valueOf(msg.getMsgKey())); +// out.writeShort(0); +// out.writeShort(0); +// out.writeInt(msg.getDeviceId() == null ? 0 : Integer.valueOf(msg.getDeviceId())); +// out.writeInt(msg.getDestDeviceId() == null ? 0 : Integer.valueOf(msg.getDestDeviceId())); +// out.writeBytes(msg.getBody()); +// } +// } +// } catch (Exception e) { +// logger.warn("编码消息发生异常" + e.getMessage(), e); +// throw e; +// } +// } +// +// public static byte[] encode(SocketReturnMessage msg, ByteOrder order) { +// ByteBuf out = null; +// byte[] result = null; +// +// try { +// out = Unpooled.buffer(); +// if(ByteOrder.LITTLEENDIAN.code().equals(order.code())) { +// out.writeShortLE(16 + msg.getData().length); +// out.writeShortLE(msg.getMsgKey() == null ? 0 : Short.valueOf(msg.getMsgKey())); +// out.writeShortLE(0); +// out.writeShortLE(0); +// out.writeIntLE(0); +// out.writeIntLE(msg.getDeviceId() == null ? 0 : Integer.valueOf(msg.getDeviceId())); +// out.writeBytes(msg.getData()); +// }else { +// out.writeShort(16 + msg.getData().length); +// out.writeShort(msg.getMsgKey() == null ? 0 : Short.valueOf(msg.getMsgKey())); +// out.writeShort(0); +// out.writeShort(0); +// out.writeInt(0); +// out.writeInt(msg.getDeviceId() == null ? 0 : Integer.valueOf(msg.getDeviceId())); +// out.writeBytes(msg.getData()); +// } +// +// result = new byte[out.readableBytes()]; +// out.readBytes(result); +// } finally { +// if(out != null) { +// out.release(); +// } +// } +// +// return result; +// } +//} \ No newline at end of file diff --git a/src/main/java/com/cisdi/data/emserver/gateway/impl/SocketClient.java b/src/main/java/com/cisdi/data/emserver/gateway/impl/SocketClient.java new file mode 100644 index 0000000..b6c7d06 --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/impl/SocketClient.java @@ -0,0 +1,100 @@ +package com.cisdi.data.emserver.gateway.impl; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cisdi.data.common.exception.BusinessException; + +public class SocketClient { + + private static Logger logger = LoggerFactory.getLogger(SocketClient.class); + + //连接服务器的IP + public String ip = null; + //连接服务器的端口 + public Integer port = null; + //套节字对象 + private Socket socket = null; + //超时时间,以毫秒为单位 + private Integer timeout = 2000; + + public SocketClient(String ip, Integer port) { + this.ip = ip; + this.port = port; + } + + /** + * 打开连接 + */ + public void open(){ + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(ip, port), timeout);//设置连接请求超时时间 + socket.setKeepAlive(true);//开启保持活动状态的套接字 + socket.setSoTimeout(timeout);//设置超时时间 + } catch (UnknownHostException e) { + logger.error(e.getMessage(),e); + } catch (IOException e) { + logger.error(e.getMessage(),e); + } + } + + /** + * 发送数据(同步返回) + * @param data + * @return + */ + public String send(byte[] data) { + + String resultString = null; + + DataOutputStream out = null; + try { + out = new DataOutputStream(socket.getOutputStream()); + + out.write(data); + out.flush(); + + } catch (Exception e) { + throw new BusinessException(e.getMessage(), e); + } finally { + if (out != null){ + try { + out.close(); + } catch (IOException e) { + logger.error("DataOutputStream out 关闭异常:", e); + } + } + } + + return resultString; + } + + public void close() { + if(socket != null) { + try { + socket.close(); + } catch (IOException e) { + logger.warn(e.getLocalizedMessage(), e); + } + } + } + + + public static void main(String[] args) throws UnsupportedEncodingException + { + String testmsg=(char)0x02+",T1234,S0123456789ABCDEFGHIJ,C12.345678 ,S12.345678 ,W 0.987654 ,C 36,S 45,M012,H12:00,D01M12Y10,"+(char)0x0a+(char)0x0b+",A001196302303,N "+(char)0x0a+(char)0x0b+(char)0x03; + + SocketClient socketClient=new SocketClient("10.76.48.66",5002); + socketClient.open(); + socketClient.send(testmsg.getBytes()); + socketClient.close(); + } +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/vo/OtherParameterVo.java b/src/main/java/com/cisdi/data/emserver/gateway/vo/OtherParameterVo.java new file mode 100644 index 0000000..0ac4d9c --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/vo/OtherParameterVo.java @@ -0,0 +1,27 @@ +package com.cisdi.data.emserver.gateway.vo; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.fastjson.JSON; + +public class OtherParameterVo implements Serializable { + private static final long serialVersionUID = 2247503397521281192L; + + private List routes = new ArrayList(); + + public List getRoutes() { + return routes; + } + + public void setRoutes(List routes) { + this.routes = routes; + } + + @Override + public String toString() { + String jsonString = JSON.toJSONString(this); + return jsonString; + } +} diff --git a/src/main/java/com/cisdi/data/emserver/gateway/vo/RouteVo.java b/src/main/java/com/cisdi/data/emserver/gateway/vo/RouteVo.java new file mode 100644 index 0000000..932c23e --- /dev/null +++ b/src/main/java/com/cisdi/data/emserver/gateway/vo/RouteVo.java @@ -0,0 +1,46 @@ +package com.cisdi.data.emserver.gateway.vo; + +import java.io.Serializable; + +import com.alibaba.fastjson.JSON; + +/** + * 下发到设备的电文路由规则 + * @author cup + * + */ +public class RouteVo implements Serializable { + private static final long serialVersionUID = -1745777213966420113L; + + /** + * 电文key + */ + private String msgKey; + + /** + * 形如 192.168.5.9:9002 的字符串 + */ + private String ipAndPort; + + public String getMsgKey() { + return msgKey; + } + + public void setMsgKey(String msgKey) { + this.msgKey = msgKey; + } + + public String getIpAndPort() { + return ipAndPort; + } + + public void setIpAndPort(String ipAndPort) { + this.ipAndPort = ipAndPort; + } + + @Override + public String toString() { + String jsonString = JSON.toJSONString(this); + return jsonString; + } +} diff --git a/src/main/java/com/cisdi/data/util/HexUtil.java b/src/main/java/com/cisdi/data/util/HexUtil.java new file mode 100644 index 0000000..471c9b4 --- /dev/null +++ b/src/main/java/com/cisdi/data/util/HexUtil.java @@ -0,0 +1,74 @@ +package com.cisdi.data.util; + +public class HexUtil { + /* + * Convert byte[] to hex string.这里我们可以将byte转换成int,然后利用Integer.toHexString(int)来转换成16进制字符串。 + * @param src byte[] data + * @return hex string + */ + public static String bytesToHexString(byte[] src){ + StringBuilder stringBuilder = new StringBuilder(""); + if (src == null || src.length <= 0) { + return null; + } + for (int i = 0; i < src.length; i++) { + int v = src[i] & 0xFF; + String hv = Integer.toHexString(v); + if (hv.length() < 2) { + stringBuilder.append(0); + } + stringBuilder.append(hv); + } + return stringBuilder.toString(); + } + + /** + * Convert hex string to byte[] + * @param hexString the hex string + * @return byte[] + */ + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + /** + * Convert char to byte + * @param c char + * @return byte + */ + private static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + + //将指定byte数组以16进制的形式打印到控制台 + public static void printHexString( byte[] b) { + for (int i = 0; i < b.length; i++) { + String hex = Integer.toHexString(b[i] & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + System.out.print(hex.toUpperCase() ); + } + } + + public static Byte Byte(int value) { + Byte result = (byte)(value & 0xff); + return result; + } + + public static Short Short(int value) { + Short result = (short)(value & 0xffff); + return result; + } +} diff --git a/src/test/java/com/cisdi/data/RFID/gateway/test/StartRfidTest.java b/src/test/java/com/cisdi/data/RFID/gateway/test/StartRfidTest.java new file mode 100644 index 0000000..a46b9bc --- /dev/null +++ b/src/test/java/com/cisdi/data/RFID/gateway/test/StartRfidTest.java @@ -0,0 +1,39 @@ +package com.cisdi.data.RFID.gateway.test; + +import com.cisdi.data.RFID.gateway.RfidClient; +import com.cisdi.data.RFID.gateway.RfidDaemonTask; +import com.cisdi.data.RFID.gateway.RfidTaskStartVo; +import com.cisdi.data.sdk.param.PullSocketParam; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @program: dacoo-data-sdk-DI-client + * @description: RFID测试 + * @author: Mo + * @created: 2021/06/02 14:46 + */ +public class StartRfidTest { + public static void main(String[] args) { + try { + RfidTaskStartVo taskStartVo = new RfidTaskStartVo(); + taskStartVo.setDeviceId("1000800"); + taskStartVo.setShouldRun(new AtomicBoolean(true)); + PullSocketParam socketParam = new PullSocketParam(); + socketParam.setPullInternal(2000); + socketParam.setPeerIp("127.0.0.1"); + socketParam.setPeerPort(2112); + socketParam.setTimeout(5000); + taskStartVo.setSocketParam(socketParam); + + RfidClient fireClient = new RfidClient(taskStartVo); + RfidDaemonTask daemonTask = new RfidDaemonTask(fireClient, taskStartVo); + Thread thread = new Thread(daemonTask, "Rfid-Daemon-Task" ); + thread.start(); + Thread.sleep(100000000); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} \ No newline at end of file