├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── csg
│ │ └── ioms
│ │ └── iec
│ │ ├── Iec104Application.java
│ │ ├── assemble
│ │ ├── ContinuousAddressBuilder.java
│ │ ├── UnContinuousAddressBuilder.java
│ │ └── VaribleLengthPacket.java
│ │ ├── common
│ │ ├── BalancedLinkCode.java
│ │ ├── BasicInstruction104.java
│ │ ├── Iec104Constant.java
│ │ ├── TransferReason.java
│ │ └── TypeIdentifier.java
│ │ ├── config
│ │ ├── DefaultIec104Config.java
│ │ └── Iec104Config.java
│ │ ├── core
│ │ ├── CachedThreadPool.java
│ │ ├── ControlManageUtil.java
│ │ ├── Decoder104.java
│ │ ├── Encoder104.java
│ │ ├── Iec104ThreadLocal.java
│ │ └── ScheduledTaskPool.java
│ │ ├── enums
│ │ ├── QualifiersEnum.java
│ │ ├── TypeIdentifierEnum.java
│ │ └── UControlEnum.java
│ │ ├── exception
│ │ ├── CustomException.java
│ │ ├── IllegalFormatException.java
│ │ ├── LengthException.java
│ │ ├── UnknownLinkCodeException.java
│ │ ├── UnknownTransferReasonException.java
│ │ └── UnknownTypeIdentifierException.java
│ │ ├── message
│ │ ├── MessageDetail.java
│ │ └── MessageInfo.java
│ │ ├── protocol
│ │ ├── ASDU.java
│ │ ├── Analysis.java
│ │ ├── ParamePreset104.java
│ │ ├── Telecontrol104.java
│ │ ├── Telemetry104.java
│ │ └── Telesignalling104.java
│ │ ├── server
│ │ ├── Iec104Master.java
│ │ ├── Iec104MasterFactory.java
│ │ ├── handler
│ │ │ ├── BytesEncoder.java
│ │ │ ├── ChannelHandler.java
│ │ │ ├── ChannelHandlerImpl.java
│ │ │ ├── Check104Handler.java
│ │ │ ├── DataDecoder.java
│ │ │ ├── DataEncoder.java
│ │ │ ├── DataHandler.java
│ │ │ ├── SysSframeHandler.java
│ │ │ └── Unpack104Handler.java
│ │ └── master
│ │ │ ├── Iec104ClientInitializer.java
│ │ │ ├── Iec104TcpClientMaster.java
│ │ │ └── handler
│ │ │ ├── Iec104ClientHandler.java
│ │ │ ├── SysDataHandler.java
│ │ │ └── SysUframeClientHandler.java
│ │ └── utils
│ │ ├── ByteUtil.java
│ │ ├── Iec104Util.java
│ │ └── Util.java
└── resources
│ └── application.properties
└── test
└── java
└── com
└── csg
└── iec
└── Iec104ApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2007-present the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import java.net.*;
17 | import java.io.*;
18 | import java.nio.channels.*;
19 | import java.util.Properties;
20 |
21 | public class MavenWrapperDownloader {
22 |
23 | private static final String WRAPPER_VERSION = "0.5.6";
24 | /**
25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
26 | */
27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
29 |
30 | /**
31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
32 | * use instead of the default one.
33 | */
34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
35 | ".mvn/wrapper/maven-wrapper.properties";
36 |
37 | /**
38 | * Path where the maven-wrapper.jar will be saved to.
39 | */
40 | private static final String MAVEN_WRAPPER_JAR_PATH =
41 | ".mvn/wrapper/maven-wrapper.jar";
42 |
43 | /**
44 | * Name of the property which should be used to override the default download url for the wrapper.
45 | */
46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
47 |
48 | public static void main(String args[]) {
49 | System.out.println("- Downloader started");
50 | File baseDirectory = new File(args[0]);
51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
52 |
53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
54 | // wrapperUrl parameter.
55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
56 | String url = DEFAULT_DOWNLOAD_URL;
57 | if(mavenWrapperPropertyFile.exists()) {
58 | FileInputStream mavenWrapperPropertyFileInputStream = null;
59 | try {
60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
61 | Properties mavenWrapperProperties = new Properties();
62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
64 | } catch (IOException e) {
65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
66 | } finally {
67 | try {
68 | if(mavenWrapperPropertyFileInputStream != null) {
69 | mavenWrapperPropertyFileInputStream.close();
70 | }
71 | } catch (IOException e) {
72 | // Ignore ...
73 | }
74 | }
75 | }
76 | System.out.println("- Downloading from: " + url);
77 |
78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
79 | if(!outputFile.getParentFile().exists()) {
80 | if(!outputFile.getParentFile().mkdirs()) {
81 | System.out.println(
82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
83 | }
84 | }
85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
86 | try {
87 | downloadFileFromURL(url, outputFile);
88 | System.out.println("Done");
89 | System.exit(0);
90 | } catch (Throwable e) {
91 | System.out.println("- Error downloading");
92 | e.printStackTrace();
93 | System.exit(1);
94 | }
95 | }
96 |
97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
99 | String username = System.getenv("MVNW_USERNAME");
100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
101 | Authenticator.setDefault(new Authenticator() {
102 | @Override
103 | protected PasswordAuthentication getPasswordAuthentication() {
104 | return new PasswordAuthentication(username, password);
105 | }
106 | });
107 | }
108 | URL website = new URL(urlString);
109 | ReadableByteChannel rbc;
110 | rbc = Channels.newChannel(website.openStream());
111 | FileOutputStream fos = new FileOutputStream(destination);
112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
113 | fos.close();
114 | rbc.close();
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunshibiao/iec104/edc847c7843346e28b043a9643c4273fc403b677/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iec104
2 | 电力104协议对接 netty服务
3 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.3.4.RELEASE
9 |
10 |
11 | com.csg
12 | iec104
13 | 1.0
14 | iec104
15 | Demo project for Spring Boot
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter
25 |
26 |
27 | org.slf4j
28 | slf4j-api
29 |
30 |
31 | junit
32 | junit
33 |
34 |
35 | io.netty
36 | netty-all
37 |
38 |
39 | org.slf4j
40 | slf4j-simple
41 | compile
42 |
43 |
44 | org.projectlombok
45 | lombok
46 |
47 |
48 | com.alibaba
49 | fastjson
50 | 1.2.72
51 |
52 |
53 | org.springframework.boot
54 | spring-boot-starter-test
55 | test
56 |
57 |
58 | org.junit.vintage
59 | junit-vintage-engine
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | org.springframework.boot
69 | spring-boot-maven-plugin
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/Iec104Application.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | import com.csg.ioms.iec.config.Iec104Config;
7 | import com.csg.ioms.iec.server.Iec104MasterFactory;
8 | import com.csg.ioms.iec.server.master.handler.SysDataHandler;
9 |
10 | @SpringBootApplication
11 | public class Iec104Application {
12 |
13 | public static void main(String[] args) {
14 | try {
15 | SpringApplication.run(Iec104Application.class, args);
16 | Iec104Config iec104Config = new Iec104Config();
17 | iec104Config.setFrameAmountMax((short) 1);
18 | iec104Config.setTerminnalAddress((short) 1);
19 | Iec104MasterFactory.createTcpClientMaster("192.168.6.112", 2404).setDataHandler(new SysDataHandler()).setConfig(iec104Config).run();
20 | } catch (Exception e) {
21 | e.printStackTrace();
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/assemble/ContinuousAddressBuilder.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.assemble;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.function.Function;
6 |
7 | import com.csg.ioms.iec.utils.Util;
8 |
9 | /**
10 | * 连续信息体的104报文
11 | *
12 | * @Author sun
13 | */
14 | public class ContinuousAddressBuilder extends VaribleLengthPacket {
15 | /**
16 | * 信息体元素的地址
17 | */
18 | private int info_address;
19 | /**
20 | * 信息元素的长度
21 | * 说明;遥信/遥控 1字节:遥测:2字节;参数设置:4字节:
22 | * 总召唤/时钟同步/复位进程/初始化无此项,所以应该填充位0
23 | */
24 | private int infoLength;
25 | /**
26 | * 信息体元素
27 | */
28 | private ArrayList infos;
29 |
30 | @Deprecated
31 | public ContinuousAddressBuilder() {
32 | this("00000000", 1, 0, 1, 0, 0);
33 | }
34 |
35 | public ContinuousAddressBuilder(String con, int TI, int asduAddress, int transferReason, int info_address, int infoLength) {
36 | this(con, TI, asduAddress, transferReason, info_address, infoLength, -1);
37 | }
38 |
39 | public ContinuousAddressBuilder(String con, int TI, int asduAddress, int transferReason, int info_address, int infoLength, int qualifier) {
40 | super(con, TI, 1, asduAddress, transferReason);
41 | this.info_address = info_address;
42 | this.infoLength = infoLength;
43 | this.qualifier = qualifier;
44 | }
45 |
46 |
47 | public ContinuousAddressBuilder buildCon(String con) {
48 | this.con = con;
49 | return this;
50 | }
51 |
52 | public ContinuousAddressBuilder buildTI(int Ti) {
53 | this.TI = Ti;
54 | return this;
55 | }
56 |
57 | public ContinuousAddressBuilder buildAsduAddress(int asduAddress) {
58 | this.asduAddress = asduAddress;
59 | return this;
60 | }
61 |
62 | public ContinuousAddressBuilder buildTransferReason(int transferReason) {
63 | this.transferReason = transferReason;
64 | return this;
65 | }
66 |
67 | public ContinuousAddressBuilder buildInfoAddress(int info_address) {
68 | this.info_address = info_address;
69 | return this;
70 | }
71 |
72 | public ContinuousAddressBuilder buildInfoLength(int infoLength) {
73 | this.infoLength = infoLength;
74 | return this;
75 | }
76 |
77 | public ContinuousAddressBuilder buildInfos(T info) {
78 | this.infos.add(info);
79 | return this;
80 | }
81 |
82 | public ContinuousAddressBuilder buildInfosBatch(T[] infos) {
83 | Arrays.stream(infos).forEach(x -> this.infos.add(x));
84 | return this;
85 | }
86 |
87 | public ContinuousAddressBuilder buildInfosBatch(ArrayList infos) {
88 | infos.stream().forEach(x -> this.infos.add(x));
89 | return this;
90 | }
91 |
92 | public String build() {
93 | return this.build(x -> Integer.parseInt(x.toString()));
94 | }
95 |
96 | public String build(Function fun) {
97 | StringBuilder builder = new StringBuilder();
98 | builder.append("68");
99 | int messageLen = 10 + 3;
100 | messageLen += (infos.size() * infoLength);
101 | if (this.qualifier != -1) messageLen++;
102 | if (this.dateTime != null) {
103 | messageLen += 7;
104 | }
105 | builder.append(String.format("%02X", messageLen));
106 | builder.append(this.con);
107 | builder.append(String.format("%02X", this.TI));
108 | builder.append(String.format("%02X", (this.SQ << 7) + this.infos.size()));
109 | builder.append(String.format("%04X", this.transferReason));
110 | builder.append(Util.getAddressStr(this.asduAddress));
111 | builder.append(Util.getAddressStr3(this.info_address));
112 | infos.stream().forEach(x -> builder.append(Util.getInfoStr(fun.apply(x), infoLength)));
113 | if (qualifier != -1) builder.append(String.format("%02X", qualifier));
114 | if (dateTime != null) builder.append(Util.date2HStr(this.dateTime));
115 | return builder.toString();
116 | }
117 |
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/assemble/UnContinuousAddressBuilder.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.assemble;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.function.Function;
6 |
7 | import com.csg.ioms.iec.utils.Util;
8 |
9 | /**
10 | * 不连续信息体的104报文
11 | *
12 | * @Author sun
13 | */
14 | public class UnContinuousAddressBuilder extends VaribleLengthPacket {
15 | /**
16 | * 信息体元素的地址
17 | */
18 | private ArrayList info_adds;
19 | /**
20 | * 信息元素的长度
21 | * 说明;遥信/遥控 1字节:遥测:2字节;参数设置:4字节:
22 | * 总召唤/时钟同步/复位进程/初始化无此项,所以应该填充位0
23 | */
24 | private int infoLength;
25 | /**
26 | * 信息体元素
27 | */
28 | private ArrayList infos;
29 |
30 |
31 | @Deprecated
32 | public UnContinuousAddressBuilder() {
33 | this("00000000", 1, 0, 1, 0, 0);
34 | }
35 |
36 | public UnContinuousAddressBuilder(String con, int TI, int asduAddress, int transferReason, int infoLength) {
37 | this(con, TI, asduAddress, transferReason, 0, infoLength, -1);
38 | }
39 |
40 | public UnContinuousAddressBuilder(String con, int TI, int asduAddress, int transferReason, int info_address, int infoLength) {
41 | this(con, TI, asduAddress, transferReason, info_address, infoLength, -1);
42 | }
43 |
44 | /**
45 | * @param con 控制域
46 | * @param TI 类型标识符
47 | * @param asduAddress 应用服务数据单元公共地址
48 | * @param transferReason 传送原因
49 | * @param info_address 信息体地址
50 | * @param infoLength 信息体长度
51 | * @param qualifier 限定词
52 | */
53 | public UnContinuousAddressBuilder(String con, int TI, int asduAddress, int transferReason, int info_address, int infoLength, int qualifier) {
54 | super(con, TI, 0, asduAddress, transferReason);
55 | this.info_adds = new ArrayList();
56 | this.info_adds.add(info_address);
57 | this.infos = new ArrayList();
58 | this.infoLength = infoLength;
59 | this.qualifier = qualifier;
60 | }
61 |
62 |
63 | public UnContinuousAddressBuilder buildCon(String con) {
64 | this.con = con;
65 | return this;
66 | }
67 |
68 | public UnContinuousAddressBuilder buildTI(int Ti) {
69 | this.TI = Ti;
70 | return this;
71 | }
72 |
73 | public UnContinuousAddressBuilder buildAsduAddress(int asduAddress) {
74 | this.asduAddress = asduAddress;
75 | return this;
76 | }
77 |
78 | public UnContinuousAddressBuilder buildTransferReason(int transferReason) {
79 | this.transferReason = transferReason;
80 | return this;
81 | }
82 |
83 | public UnContinuousAddressBuilder buildInfoAddress(int info_address) {
84 | this.info_adds.add(info_address);
85 | return this;
86 | }
87 |
88 | public UnContinuousAddressBuilder buildInfoLength(int infoLength) {
89 | this.infoLength = infoLength;
90 | return this;
91 | }
92 |
93 | public UnContinuousAddressBuilder buildInfos(T info) {
94 | this.infos.add(info);
95 | return this;
96 | }
97 |
98 | public UnContinuousAddressBuilder buildInfosBatch(T[] infos) {
99 | Arrays.stream(infos).forEach(x -> this.infos.add(x));
100 | return this;
101 | }
102 |
103 | public UnContinuousAddressBuilder buildInfosBatch(ArrayList infos) {
104 | infos.stream().forEach(x -> this.infos.add(x));
105 | return this;
106 | }
107 |
108 | public String build() {
109 | return this.build(x -> Integer.parseInt(x.toString()));
110 | }
111 |
112 | public String build(Function fun) {
113 | StringBuilder builder = new StringBuilder();
114 | builder.append("68");
115 | int messageLen = 10;
116 | messageLen += info_adds.size() * 3;
117 | messageLen += (infos.size() * infoLength);
118 | if (this.qualifier != -1) messageLen++;
119 | if (this.dateTime != null) {
120 | messageLen += 7;
121 | }
122 | builder.append(String.format("%02X", messageLen));
123 | builder.append(con);
124 | builder.append(String.format("%02X", this.TI));
125 | builder.append(String.format("%02X", (this.SQ << 7) + this.infos.size()==0?1:this.infos.size()));
126 | builder.append( Util.getAddressStr(this.transferReason));
127 | builder.append(Util.getAddressStr(this.asduAddress));
128 | if (this.infos.size() == 0) {
129 | builder.append(Util.getAddressStr3(this.info_adds.get(0)));
130 | } else {
131 | for (int i = 0; i < info_adds.size(); i++) {
132 | builder.append(Util.getAddressStr3(this.info_adds.get(i)));
133 | builder.append(Util.getInfoStr(fun.apply(this.infos.get(i)), infoLength));
134 | }
135 | }
136 | if (qualifier != -1) builder.append(String.format("%02X", qualifier));
137 | if (dateTime != null) builder.append(Util.date2HStr(this.dateTime));
138 | return builder.toString();
139 | }
140 |
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/assemble/VaribleLengthPacket.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.assemble;
2 |
3 |
4 | import java.util.Date;
5 |
6 | /**
7 | * @Author sun
8 | */
9 | public abstract class VaribleLengthPacket {
10 | /**
11 | * 控制域 4字节 三种格式
12 | */
13 | String con;
14 | /**
15 | * 类型标识TI。
16 | * 可以提供已定义的 TypeIdentifier的code值;
17 | */
18 | int TI;
19 | /**
20 | * SQ的值,表明这条报文的信息体元素的地址是否是连续的;
21 | * "0.地址不连续1.地址连续"
22 | */
23 | int SQ;
24 | /**
25 | * 应用服务数据单元公共地址.
26 | */
27 | int asduAddress;
28 | /**
29 | * 传输原因。
30 | */
31 | int transferReason;
32 | /**
33 | * 限定词(品质描述词)。限定词默认填充0,无限定词时请填充-1
34 | */
35 | int qualifier;
36 | /**
37 | * 时标.默认无时标
38 | */
39 | Date dateTime;
40 |
41 | public VaribleLengthPacket() {
42 | }
43 |
44 | public VaribleLengthPacket(int SQ) {
45 | this.SQ = SQ;
46 | }
47 |
48 | public VaribleLengthPacket(String con, int TI, int SQ, int asduAddress, int transferReason ) {
49 | this.con = con;
50 | this.TI = TI;
51 | this.SQ = SQ;
52 | this.asduAddress = asduAddress;
53 | this.transferReason = transferReason;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/common/BalancedLinkCode.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.common;
2 |
3 | import com.csg.ioms.iec.exception.UnknownLinkCodeException;
4 |
5 | /**
6 | * @Author zhangyu
7 | * @create 2019/5/27 16:09
8 | */
9 | public enum BalancedLinkCode {
10 | RESET_REMOTE_LINK(0, "复位远方链路"),
11 | RESET_USER_PROCESS(1, "复位用户进程"),
12 | CONFIRM_LINK_TESTING(2, "发送/确认链路测试功能 "),
13 | CONFIRM_USER_DATA(3, "发送/确认用户数据"),
14 | NO_ANSWERED_DATA(4, "发送/无回答用户数据"),
15 | REQUEST_REQUEST_LINK_STATUS(9, "请求/响应请求链路状态"),
16 | RESPONSE_LINK_STATE(11, "响应:链路状态");
17 |
18 | private int code;
19 | private String describe;
20 |
21 | BalancedLinkCode(int code, String describe) {
22 | this.code = code;
23 | this.describe = describe;
24 | }
25 |
26 | public static String getDescribe(int code) throws UnknownLinkCodeException {
27 | for (BalancedLinkCode balancedLinkCode : BalancedLinkCode.values()) {
28 | if (balancedLinkCode.code == code) return balancedLinkCode.describe;
29 | }
30 | throw new UnknownLinkCodeException();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/common/BasicInstruction104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.common;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.csg.ioms.iec.enums.QualifiersEnum;
7 | import com.csg.ioms.iec.enums.TypeIdentifierEnum;
8 | import com.csg.ioms.iec.enums.UControlEnum;
9 | import com.csg.ioms.iec.message.MessageDetail;
10 | import com.csg.ioms.iec.message.MessageInfo;
11 | import com.csg.ioms.iec.utils.ByteUtil;
12 | import com.csg.ioms.iec.utils.Iec104Util;
13 |
14 | /**
15 | * 104 规约的基本指令封装
16 | * @ClassName: BasicInstruction104
17 | * @Description: 返回指定的指令
18 | * @author sun
19 | */
20 |
21 | public class BasicInstruction104 {
22 | // 68040B 00 00 00
23 | /**
24 | * 初始确认指令
25 | */
26 | public static final byte[] STARTDT_YES = new byte[] {0x68, 0x04, 0x0B, 0x00, 0x00, 0x00};
27 |
28 | /**
29 | * 链路启动指令
30 | */
31 | public static final byte[] STARTDT = new byte[] {0x68, 0x04, 0x07, 0x00, 0x00, 0x00};
32 |
33 |
34 | /**
35 | * 测试确认
36 | */
37 | public static final byte[] TESTFR_YES = new byte[] {0x68, 0x04, (byte) 0x83, 0x00, 0x00, 0x00};
38 |
39 | /**
40 | * 测试命令指令
41 | */
42 | public static final byte[] TESTFR = new byte[] {0x68, 0x04, (byte) 0x43, 0x00, 0x00, 0x00};
43 |
44 |
45 | /**
46 | * 停止确认
47 | */
48 | public static final byte[] STOPDT_YES = new byte[] {0x68, 0x04, 0x23, 0x00, 0x00, 0x00};
49 |
50 |
51 |
52 |
53 |
54 | /**
55 | *
56 | * @Title: getGeneralCallRuleDetail104
57 | * @Description: 总召唤指令
58 | * @param @return
59 | * @param @throws IOException
60 | * @return MessageDetail
61 | * @throws
62 | */
63 | public static MessageDetail getGeneralCallRuleDetail104() {
64 | TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
65 | int sq = 0;
66 | boolean isContinuous = sq == 0 ? false : true;
67 | // 接收序号
68 | short accept = 0;
69 | // 发送序号
70 | short send = 0;
71 | byte[] control = Iec104Util.getIcontrol(accept, send);
72 | // 传输原因
73 | short transferReason = 6;
74 | boolean isTest = false;
75 | boolean isPn = true;
76 | // 终端地址 实际发生的时候会被替换
77 | short terminalAddress = 1;
78 | // 消息地址 总召唤地址为0
79 | int messageAddress = 0;
80 | QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
81 | List messages = new ArrayList<>();
82 | MessageInfo message = new MessageInfo();
83 | message.setQualifiers(qualifiers);
84 | message.setMessageInfos(new byte[] {});
85 | messages.add(message);
86 | MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPn, transferReason,
87 | terminalAddress, messageAddress, messages, null, null);
88 | return ruleDetail104;
89 | }
90 |
91 |
92 | /**
93 | *
94 | * @Title: getYesGeneralCallRuleDetail104
95 | * @Description: 总召唤确认指令
96 | * @return
97 | * @return MessageDetail
98 | * @throws
99 | */
100 | public static MessageDetail getYesGeneralCallRuleDetail104() {
101 | TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
102 | //SQ=0 length =1
103 | int sq = 0;
104 | boolean isContinuous = sq == 0 ? false : true;
105 | // 接收序号
106 | short accept = 0;
107 | // 发送序号
108 | short send = 0;
109 | byte[] control = Iec104Util.getIcontrol(accept, send);
110 | // 传输原因
111 | short transferReason = 7;
112 | // true:1 ; false : 0
113 | boolean isTest = false;
114 | // true:0 false;1
115 | boolean isPN = true;
116 |
117 | short terminalAddress = 1;
118 | // 消息地址 总召唤地址为0
119 | int messageAddress = 0;
120 |
121 | QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
122 | List messages = new ArrayList<>();
123 | MessageInfo message = new MessageInfo();
124 | message.setQualifiers(qualifiers);
125 | message.setMessageInfos(new byte[] {});
126 |
127 | messages.add(message);
128 | MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPN, transferReason,
129 | terminalAddress, messageAddress, messages, null, null);
130 | return ruleDetail104;
131 | }
132 |
133 |
134 | /**
135 | *
136 | * @Title: getEndGeneralCallRuleDetail104
137 | * @Description: 总召唤结束指令
138 | * @return
139 | * @return MessageDetail
140 | * @throws
141 | */
142 | public static MessageDetail getEndGeneralCallRuleDetail104() {
143 | TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
144 | //SQ=0 length =1
145 | int sq = 0;
146 | boolean isContinuous = sq == 0 ? false : true;
147 | // 接收序号
148 | short accept = 1;
149 | // 发送序号
150 | short send = 4;
151 | byte[] control = Iec104Util.getIcontrol(accept, send);
152 | // 传输原因
153 | short transferReason = 0x0A;
154 | // true:1 ; false : 0
155 | boolean isTest = false;
156 | // true:0 false;1
157 | boolean isPN = true;
158 |
159 | short terminalAddress = 1;
160 | // 消息地址 总召唤地址为0
161 | int messageAddress = 0;
162 | // 老板限定词
163 | QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
164 | List messages = new ArrayList<>();
165 | MessageInfo message = new MessageInfo();
166 | message.setQualifiers(qualifiers);
167 | message.setMessageInfos(new byte[] {});
168 |
169 | messages.add(message);
170 | MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPN, transferReason,
171 | terminalAddress, messageAddress, messages, null, null);
172 | return ruleDetail104;
173 | }
174 |
175 | public static MessageDetail getInitRuleDetail104() {
176 | byte[] control = ByteUtil.intToByteArray(UControlEnum.STARTDT.getValue());
177 | MessageDetail ruleDetail104 = new MessageDetail(control);
178 | return ruleDetail104;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/common/Iec104Constant.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.common;
2 |
3 | /**
4 | *
5 | * @ClassName: Iec104Constant
6 | * @Description: TODO
7 | * @author sun
8 | */
9 | public class Iec104Constant {
10 |
11 | /**
12 | * 开始字符
13 | */
14 | public static final byte HEAD_DATA = 0x68;
15 |
16 | /**
17 | * 控制域长度
18 | */
19 | public static final byte CPNTROL_LENGTH = 0x04;
20 |
21 | /**
22 | * APCI 长度
23 | */
24 | public static final byte APCI_LENGTH = 0x06;
25 |
26 | /**
27 | * APCI 中 发送序号低位坐标
28 | */
29 | public static final int ACCEPT_LOW_INDEX = 2;
30 |
31 | /**
32 | * APCI 中 发送序号高位坐标
33 | */
34 | public static final int ACCEPT_HIGH_INDEX = 3;
35 |
36 |
37 | /**
38 | *最大接收序号
39 | */
40 | public static final Short SEND_MAX = 32767;
41 |
42 | /**
43 | * 最小接收序号
44 | */
45 | public static final Short SEND_MIN = 0;
46 |
47 |
48 | // /**
49 | // *
50 | // */
51 | // public static final short FRAME_SUM_MAX = 1;
52 | //
53 | // /**
54 | // * 终端地址
55 | // */
56 | // public static final short TERMINNAL_ADDRESS = 1;
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/common/TransferReason.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.common;
2 |
3 | import com.csg.ioms.iec.exception.UnknownTransferReasonException;
4 |
5 | /**
6 | * 解析传送原因
7 | *
8 | * @author sun
9 | */
10 | public enum TransferReason {
11 | UNUSED(0, "未用"),
12 | PERIOD(1, "周期、循环 "),
13 | BG_SCANNER(2, "背景扫描"),
14 | OUTBURST(3, "突发(自发)"),
15 | INITIALIZATION_COMPLETE(4, "初始化完成"),
16 | REQUEST(5, "请求或者被请求"),
17 | ACTIVATE(6, "激活"),
18 | ACTIVATE_CONFIRMATION(7, "激活确认"),
19 | STOP_ACTIVATION(8, " 停止激活 "),
20 | STOP_ACTIVATION_CONFIRMATION(9, "停止激活确认"),
21 | ACTIVATE_TERMINATION(10, "激活终止 "),
22 | FILE_TRANSFER(13, "文件传输 "),
23 | ANSWER_STATION_CALL(20, "响应站召唤(总召唤)"),
24 | UNKNOWN_TYPE(44, "未知的类型标识"),
25 | UNKNOWN_REASON_TRANSMISSION(45, "未知的传送原因"),
26 | UNKNOWN_ASDU(46, "未知的应用服务数据单元公共地址"),
27 | UNKNOWN_ADDRESS(47, "未知的信息对象地址"),
28 | RC_STATUS_ERROR(48, "遥控执行软压板状态错误"),
29 | RC_TIME_ERROR(49, "遥控执行时间戳错误"),
30 | RC_SIGNATURE_ERROR(50, "遥控执行数字签名认证错误");
31 | private int code;
32 | private String describe;
33 |
34 | TransferReason(int code, String describe) {
35 | this.code = code;
36 | this.describe = describe;
37 | }
38 |
39 | public static String getDdescribe(int code) throws UnknownTransferReasonException {
40 | for (TransferReason value : TransferReason.values()) {
41 | if (value.code == code) return value.describe;
42 | }
43 | throw new UnknownTransferReasonException();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/common/TypeIdentifier.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.common;
2 |
3 |
4 | import com.csg.ioms.iec.exception.UnknownTypeIdentifierException;
5 |
6 | /**
7 | * 归一化值
8 | *
9 | * @Author sun
10 | */
11 | public enum TypeIdentifier {
12 | SINGLE_POINT(1, "不带时标的单点信息"),
13 | TWO_POINT(3, "不带时标的双点信息"),
14 | NORMALIZED(9, "测量值,归一化值"),
15 | SCALE(11, "测量值,标度化值"),
16 | SHORT_FLOAT(13, "测量值,短浮点数"),
17 | SINGLE_POINT_TIME(30, "带CP56Time2a时标的单点信息"),
18 | TWO_POINT_TIME(31, "带CP56Time2a时标的双点信息"),
19 | SINGLE_COMMAND(45, "单命令(遥控)"),
20 | TWO_COMMAND(46, "双命令(遥控)"),
21 | PRESETS_SINGLE_PARAmeter(48, "预置/激活单个参数命令"),
22 | READ_SINGLE_PARAMETer(102, "读单个参数命令"),
23 | END_OF_INITIALIZATIon(70, "初始化结束"),
24 | CALL_COMMAND(100, "召唤命令"),
25 | CLOCK_SYNCHRONIZATIon(103, "时钟同步/读取命令"),
26 | TEST_COMMAND(104, "测试命令"),
27 | RESET_PROCESS(105, "复位进程命令"),
28 | READ_MULTIPLE_PARAMETERS(132, "读多个参数命令"),
29 | PRESETS_MULTIPLE_PARAMETERS(136, "预置/激活多个参数命令");
30 |
31 |
32 | private int code;
33 | private String describe;
34 |
35 | TypeIdentifier(int code, String describe) {
36 | this.code = code;
37 | this.describe = describe;
38 | }
39 |
40 | public static String getDescribe(int code) throws UnknownTypeIdentifierException {
41 | for (TypeIdentifier value : TypeIdentifier.values()) {
42 | if (value.code == code) return value.describe;
43 | }
44 |
45 | throw new UnknownTypeIdentifierException();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/config/DefaultIec104Config.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.config;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 默认的配置
7 | */
8 | @Data
9 | public class DefaultIec104Config extends Iec104Config {
10 |
11 | public DefaultIec104Config() {
12 | setFrameAmountMax((short) 1);
13 | setTerminnalAddress((short) 1);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/config/Iec104Config.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.config;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * 104规约的配置
7 | */
8 | @Data
9 | public class Iec104Config {
10 |
11 | /**
12 | * 接收到帧的数量到该值就要发一个确认帧
13 | */
14 | private short frameAmountMax;
15 |
16 |
17 | /**
18 | * 终端地址
19 | */
20 | private short terminnalAddress;
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/core/CachedThreadPool.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.core;
2 |
3 |
4 | import java.util.concurrent.ExecutorService;
5 | import java.util.concurrent.Executors;
6 |
7 | /**
8 | * 线程池
9 | */
10 | public final class CachedThreadPool {
11 |
12 | private static CachedThreadPool cachedThreadPool = new CachedThreadPool();
13 | private ExecutorService executorService;
14 |
15 |
16 | private CachedThreadPool() {
17 | executorService = Executors.newCachedThreadPool();
18 | }
19 |
20 |
21 | public static CachedThreadPool getCachedThreadPool() {
22 | return cachedThreadPool;
23 | }
24 |
25 | public void execute(Runnable runnable) {
26 | executorService.execute(runnable);
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/core/ControlManageUtil.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.core;
2 |
3 | import com.csg.ioms.iec.common.Iec104Constant;
4 | import com.csg.ioms.iec.message.MessageDetail;
5 | import com.csg.ioms.iec.utils.Iec104Util;
6 |
7 | import io.netty.channel.ChannelHandlerContext;
8 |
9 | /**
10 | * 控制域的管理工具
11 | */
12 | public class ControlManageUtil {
13 |
14 | /**
15 | * 发送序号
16 | */
17 | private Short send;
18 |
19 | /**
20 | * 接收序号
21 | */
22 | private Short accept;
23 |
24 | /**
25 | * 接收到帧的数量
26 | */
27 | private Short frameAmount;
28 |
29 | /**
30 | * 发送S帧的锁
31 | */
32 | private Boolean sendSframeLock;
33 |
34 | /**
35 | * 接收到帧的数量最大阀值
36 | */
37 |
38 | private short frameAmountMax;
39 |
40 | /**
41 | * 发送消息句柄
42 | */
43 | private ChannelHandlerContext ctx;
44 |
45 |
46 |
47 | public ControlManageUtil(ChannelHandlerContext ctx) {
48 | send = 0;
49 | accept = 0;
50 | frameAmount = 0;
51 | sendSframeLock = true;
52 | frameAmountMax = 1;
53 | this.ctx = ctx;
54 | }
55 |
56 | /**
57 | * 启动S发送S确认帧 的任务
58 | */
59 | public void startSendFrameTask() {
60 | Runnable runnable = () -> {
61 | while (true) {
62 | try {
63 | synchronized (sendSframeLock) {
64 | if (frameAmount >= frameAmountMax) {
65 | // 查过最大帧 的数量就要发送一个确认帧出去
66 | byte[] control = Iec104Util.getScontrol(accept);
67 | MessageDetail ruleDetail104 = new MessageDetail(control);
68 | ctx.channel().writeAndFlush(Encoder104.encoder(ruleDetail104));
69 | frameAmount = 0;
70 | }
71 | sendSframeLock.wait();
72 | }
73 | } catch (Exception e) {
74 | e.printStackTrace();
75 | }
76 | }
77 | };
78 | CachedThreadPool.getCachedThreadPool().execute(runnable);
79 | }
80 |
81 |
82 | /**
83 | * 返回当前的发送序号
84 | */
85 | public short getSend() {
86 | synchronized (send) {
87 | short sendRule = this.send;
88 | this.send++;
89 | if (send > Iec104Constant.SEND_MAX) {
90 | send = Iec104Constant.SEND_MIN;
91 | }
92 | return sendRule;
93 | }
94 | }
95 | public short getAccept() {
96 | return accept;
97 | }
98 |
99 | /**
100 | *
101 | * @Title: setAccept
102 | * @Description: 设置接收序号
103 | * @param lastAccept
104 | */
105 | public void setAccept(short lastAccept) {
106 | synchronized (sendSframeLock) {
107 | this.accept = lastAccept;
108 | frameAmount++;
109 | if (frameAmount >= frameAmountMax) {
110 | this.accept = lastAccept;
111 | sendSframeLock.notifyAll();
112 | }
113 | }
114 | }
115 |
116 |
117 | public ControlManageUtil setFrameAmountMax(short frameAmountMax) {
118 | this.frameAmount = frameAmountMax;
119 | return this;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/core/Decoder104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.core;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.csg.ioms.iec.enums.QualifiersEnum;
7 | import com.csg.ioms.iec.enums.TypeIdentifierEnum;
8 | import com.csg.ioms.iec.message.MessageDetail;
9 | import com.csg.ioms.iec.message.MessageInfo;
10 | import com.csg.ioms.iec.utils.ByteUtil;
11 | import com.csg.ioms.iec.utils.Iec104Util;
12 |
13 | /**
14 | *
15 | * @ClassName: Decoder104
16 | * @Description: 104 协议转码工具
17 | * @author sun
18 | */
19 | public class Decoder104 {
20 |
21 | /**
22 | * 将bytes 转换成指定的数据结构
23 | * @param bytes
24 | * @return
25 | */
26 | public static MessageDetail encoder(byte[] bytes) {
27 | MessageDetail ruleDetail104 = new MessageDetail();
28 | int index = 0;
29 | ruleDetail104.setStart(bytes[index++]);
30 | ruleDetail104.setApuuLength(bytes[index++] & 0xFF);
31 | ruleDetail104.setControl(ByteUtil.getByte(bytes, index, 4));
32 | index += 4;
33 | if (ruleDetail104.getApuuLength() <= 4) {
34 | ruleDetail104.setMessages(new ArrayList<>());
35 | // 如果只有APCI 就此返回
36 | return ruleDetail104;
37 | }
38 | // 下面是返回ASDU的结构
39 | ruleDetail104.setTypeIdentifier(TypeIdentifierEnum.getTypeIdentifierEnum(bytes[index++]));
40 | // 添加可变结构限定词
41 | Iec104Util.setChanged(ruleDetail104, bytes[index++]);
42 | ruleDetail104.setTransferReason(ByteUtil.byteArrayToShort(ByteUtil.getByte(bytes, index, 2)));
43 | index += 2;
44 | //
45 | ruleDetail104.setTerminalAddress(Iec104Util.getTerminalAddressShort(ByteUtil.getByte(bytes, index, 2)));
46 | index += 2;
47 | Iec104Util.setMeaageAttribute(ruleDetail104);
48 | setMessage(ruleDetail104, bytes, index);
49 | return ruleDetail104;
50 | }
51 |
52 | /**
53 | *
54 | * @Title: setMessage
55 | * @Description: 对消息进行编码
56 | * @param @param ruleDetail104
57 | * @param @param bytes
58 | * @param @param index
59 | * @return void
60 | * @throws
61 | */
62 | public static void setMessage(MessageDetail ruleDetail104, byte[] bytes, int index) {
63 | int mesageIndex = index;
64 | if (ruleDetail104.isContinuous()) {
65 | setContinuoustMessage(ruleDetail104, bytes, mesageIndex);
66 | } else {
67 | setNoContinuoustMessage(ruleDetail104, bytes, mesageIndex);
68 | }
69 | }
70 |
71 |
72 | /**
73 | *
74 | * @Title: setContinuoustMessage
75 | * @Description: 设置连续地址的消息
76 | * @param ruleDetail104
77 | * @param bytes
78 | * @param index
79 | * @return void
80 | * @throws
81 | */
82 | public static void setContinuoustMessage(MessageDetail ruleDetail104, byte[] bytes, int index) {
83 | List messages = new ArrayList<>();
84 | int mesageIndex = index;
85 | // 连续的 前三个字节是地址
86 | // 如果是地址联系的只需要设置一个初始地址就可以了
87 | // TODO 此处不处理地址
88 | int messageAddress = Iec104Util.messageAddressToInt(ByteUtil.getByte(bytes, mesageIndex, 3));
89 | ruleDetail104.setMessageAddress(messageAddress);
90 | mesageIndex += 3;
91 | if (ruleDetail104.isMessage()) {
92 | // 获取每个消息的长度
93 | int messageLength = getMessageLength(ruleDetail104);
94 | int messageSize = 0;
95 | while (messageSize < ruleDetail104.getMeasgLength()) {
96 | MessageInfo messageObj = new MessageInfo();
97 | messageObj.setMessageAddress(messageAddress);
98 | byte[] messageInfos = ByteUtil.getByte(bytes, mesageIndex, messageLength);
99 | mesageIndex += messageLength;
100 | messageObj.setMessageInfos(messageInfos);
101 | //对数据的值进行解析
102 | setMessageValue(ruleDetail104, messageObj);
103 | if (ruleDetail104.isQualifiers()) {
104 | // 判断是否有限定词
105 | messageObj.setQualifiers(QualifiersEnum.getQualifiersEnum(ruleDetail104.getTypeIdentifier(), bytes[mesageIndex++]));
106 | }
107 | if (ruleDetail104.isTimeScaleExit()) {
108 | ruleDetail104.setTimeScale(ByteUtil.byte2Hdate(ByteUtil.getByte(bytes, mesageIndex, 7)));
109 | }
110 | messageSize++;
111 | messageAddress++;
112 | messages.add(messageObj);
113 | }
114 | }
115 | ruleDetail104.setMessages(messages);
116 | }
117 |
118 |
119 | /**
120 | *
121 | * @Title: setNoContinuoustMessage
122 | * @Description: 设置不连续地址的消息
123 | * @param ruleDetail104
124 | * @param bytes
125 | * @param index
126 | * @return void
127 | * @throws
128 | */
129 | public static void setNoContinuoustMessage(MessageDetail ruleDetail104, byte[] bytes, int index) {
130 | List messages = new ArrayList<>();
131 | int mesageIndex = index;
132 | // 获取每个消息的长度
133 | int messageLength = getMessageLength(ruleDetail104);
134 | int messageSize = 0;
135 | while (messageSize < ruleDetail104.getMeasgLength()) {
136 | MessageInfo messageObj = new MessageInfo();
137 | // 消息地址
138 | messageObj.setMessageAddress(Iec104Util.messageAddressToInt(ByteUtil.getByte(bytes, mesageIndex, 3)));
139 | mesageIndex += 3;
140 |
141 | if (ruleDetail104.isMessage()) {
142 | // 消息集合
143 | byte[] messageInfos = ByteUtil.getByte(bytes, mesageIndex, messageLength);
144 | mesageIndex += messageLength;
145 | messageObj.setMessageInfos(messageInfos);
146 | //对数据的值进行解析
147 | setMessageValue(ruleDetail104, messageObj);
148 | } else {
149 | messageObj.setMessageInfos(new byte[] {});
150 | }
151 | if (ruleDetail104.isQualifiers()) {
152 | // 判断是否有限定词
153 | messageObj.setQualifiers(QualifiersEnum.getQualifiersEnum(ruleDetail104.getTypeIdentifier(), bytes[mesageIndex++]));
154 | }
155 | if (ruleDetail104.isTimeScaleExit()) {
156 | messageObj.setTimeScale(ByteUtil.byte2Hdate(ByteUtil.getByte(bytes, mesageIndex, 7)));
157 | }
158 | messageSize++;
159 | messages.add(messageObj);
160 | }
161 | ruleDetail104.setMessages(messages);
162 | }
163 |
164 | /**
165 | * 根据类型对数据的值进行解析
166 | * */
167 | private static void setMessageValue(MessageDetail ruleDetail104, MessageInfo messageObj) {
168 | switch (ruleDetail104.getTypeIdentifier().getValue()) {
169 | case 0x09:
170 | // 遥测 测量值 归一化值 遥测
171 | break;
172 | case 0x0B:
173 | // 遥测 测量值 标度化值 遥测
174 | break;
175 | case 0x66:
176 | // 读单个参数
177 | break;
178 | case (byte) 0x84:
179 | // 读多个参数
180 | break;
181 | case 0x30:
182 | // 预置单个参数命令
183 | break;
184 | case (byte) 0x88:
185 | // 预置多个个参数
186 | break;
187 | default :
188 | }
189 | }
190 |
191 |
192 | /**
193 | * 根据类型标识返回消息长度
194 | */
195 | private static int getMessageLength(MessageDetail ruleDetail104) {
196 | int messageLength = 0;
197 | switch (ruleDetail104.getTypeIdentifier().getValue()) {
198 | case 0x01:
199 | // 单点遥信
200 | messageLength = 1;
201 | break;
202 | case 0x09:
203 | // 遥测 测量值 归一化值 遥测
204 | messageLength = 2;
205 | break;
206 | case 0x0B:
207 | // 遥测 测量值 标度化值 遥测
208 | messageLength = 2;
209 | break;
210 | case 0x0D:
211 | // 测量值 短浮点数 遥测
212 | messageLength = 4;
213 | break;
214 | case 0x0F:
215 | // 测量值 短浮点数 遥脉
216 | messageLength = 4;
217 | break;
218 | case 0x66:
219 | // 读单个参数
220 | messageLength = 4;
221 | break;
222 | case (byte) 0x84:
223 | // 读多个参数
224 | messageLength = 4;
225 | break;
226 | case 0x30:
227 | // 预置单个参数命令
228 | messageLength = 4;
229 | break;
230 | case (byte) 0x88:
231 | // 预置多个个参数
232 | messageLength = 4;
233 | break;
234 | case (byte) 0xCE:
235 | // 预置多个个参数
236 | messageLength = 4;
237 | break;
238 | default :
239 | messageLength = 0;
240 | }
241 | return messageLength;
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/core/Encoder104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.core;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 |
6 | import com.csg.ioms.iec.message.MessageDetail;
7 | import com.csg.ioms.iec.message.MessageInfo;
8 | import com.csg.ioms.iec.utils.ByteUtil;
9 | import com.csg.ioms.iec.utils.Iec104Util;
10 |
11 | /**
12 | *
13 | * @author sun
14 | *
15 | */
16 | public class Encoder104 {
17 |
18 |
19 | public static byte[] encoder(MessageDetail ruleDetail104) throws IOException {
20 | Iec104Util.setMeaageAttribute(ruleDetail104);
21 | ByteArrayOutputStream bytes = new ByteArrayOutputStream();
22 | bytes.write(ruleDetail104.getStart());
23 | byte[] apduBytes = getApduBytes(ruleDetail104);
24 | int messageLen = apduBytes.length;
25 | ruleDetail104.setApuuLength(messageLen);
26 | bytes.write((byte) messageLen);
27 | bytes.write(apduBytes);
28 | return bytes.toByteArray();
29 | }
30 |
31 | private static byte[] getApduBytes(MessageDetail ruleDetail104) throws IOException {
32 | ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
33 | // 控制域
34 | bOutput.write(ruleDetail104.getControl());
35 | if (ruleDetail104.getTypeIdentifier() == null) {
36 | // U帧或者S帧
37 | return bOutput.toByteArray();
38 | }
39 | // 类型标识
40 | bOutput.write((byte) ruleDetail104.getTypeIdentifier().getValue());
41 | // 可变结构限定词
42 | bOutput.write(Iec104Util.getChangedQualifiers(ruleDetail104));
43 | // 传输原因
44 | bOutput.write(ByteUtil.shortToByteArray(ruleDetail104.getTransferReason()));
45 | // 终端地址
46 | bOutput.write((Iec104Util.getTerminalAddressByte(ruleDetail104.getTerminalAddress())));
47 | // 如果是是连续的则数据地址 只需要在开头写以后的数据单元就不需要再写了
48 | if (ruleDetail104.isContinuous()) {
49 | bOutput.write(Iec104Util.intToMessageAddress(ruleDetail104.getMessageAddress()));
50 | // 地址只取三个字节
51 | if (ruleDetail104.isMessage()) {
52 | for (MessageInfo ruleDetail104Message : ruleDetail104.getMessages()) {
53 | bOutput.write(ruleDetail104Message.getMessageInfos());
54 | }
55 | }
56 | if (ruleDetail104.isQualifiers()) {
57 | bOutput.write(ruleDetail104.getQualifiers().getValue());
58 | }
59 | if (ruleDetail104.isTimeScaleExit()) {
60 | bOutput.write(ByteUtil.date2Hbyte(ruleDetail104.getTimeScale()));
61 | }
62 | } else {
63 | for (MessageInfo ruleDetail104Message : ruleDetail104.getMessages()) {
64 | bOutput.write(Iec104Util.intToMessageAddress(ruleDetail104Message.getMessageAddress()));
65 | if (ruleDetail104.isMessage()) {
66 | bOutput.write(ruleDetail104Message.getMessageInfos());
67 | }
68 | if (ruleDetail104.isQualifiers()) {
69 | bOutput.write(ruleDetail104Message.getQualifiers().getValue());
70 | }
71 | if (ruleDetail104.isTimeScaleExit()) {
72 | bOutput.write(ByteUtil.date2Hbyte(ruleDetail104Message.getTimeScale()));
73 | }
74 | }
75 | }
76 | return bOutput.toByteArray();
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/core/Iec104ThreadLocal.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.core;
2 |
3 |
4 | import com.csg.ioms.iec.config.Iec104Config;
5 |
6 | /**
7 | * @ClassName: Iec104ThreadLocal
8 | * @Description: 线程变量管理
9 | * @author: sun
10 | */
11 | public class Iec104ThreadLocal {
12 |
13 | /**
14 | * 定时发送启动链指令 测试链指令
15 | */
16 | private static ThreadLocal scheduledTaskPoolThreadLocal = new ThreadLocal<>();
17 |
18 | /**
19 | * 返回 发送序号 和接收序号 定时发送S帧
20 | */
21 | private static ThreadLocal controlPoolThreadLocal = new ThreadLocal<>();
22 |
23 | /**
24 | * 存放相关配置文件
25 | */
26 | private static ThreadLocal iec104ConfigThreadLocal = new ThreadLocal<>();
27 |
28 |
29 |
30 |
31 | public static void setScheduledTaskPool(ScheduledTaskPool scheduledTaskPool) {
32 | scheduledTaskPoolThreadLocal.set(scheduledTaskPool);
33 | }
34 |
35 | public static ScheduledTaskPool getScheduledTaskPool() {
36 | return scheduledTaskPoolThreadLocal.get();
37 | }
38 |
39 | public static void setControlPool(ControlManageUtil controlPool) {
40 | controlPoolThreadLocal.set(controlPool);
41 | }
42 |
43 | public static ControlManageUtil getControlPool() {
44 | return controlPoolThreadLocal.get();
45 | }
46 |
47 |
48 | public static Iec104Config getIec104Conig() {
49 | return iec104ConfigThreadLocal.get();
50 | }
51 |
52 |
53 | public static void setIec104Config(Iec104Config iec104Confiig) {
54 | iec104ConfigThreadLocal.set(iec104Confiig);
55 | }
56 |
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/core/ScheduledTaskPool.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.core;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import com.csg.ioms.iec.common.BasicInstruction104;
7 |
8 | import io.netty.channel.ChannelHandlerContext;
9 | import io.netty.channel.ChannelInboundHandlerAdapter;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 |
13 | /**
14 | * 这是一个定时任务管理池
15 | * @ClassName: ScheduledTaskPool
16 | * @Description:
17 | * @author: sun
18 | */
19 | @Slf4j
20 | public class ScheduledTaskPool {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
23 |
24 | /**
25 | * 发送指令
26 | */
27 | private ChannelHandlerContext ctx;
28 | /**
29 | * 发送启动指令的线程
30 | */
31 | private Thread sendStatrThread;
32 | /**
33 | * 循环发送启动指令线程的状态
34 | */
35 | private Boolean sendStatrStatus = false;
36 | /**
37 | * 发送测试指令的线程 类似心跳
38 | */
39 | private Thread sendTestThread;
40 | /**
41 | * 发送测试指令线程的状态
42 | */
43 | private Boolean sendTestStatus = false;
44 | /**
45 | * 发送总召唤指令状态
46 | */
47 | private Boolean senGeneralCallStatus = false;
48 | /**
49 | * 启动指令收到确认后固定时间内发送总召唤指令
50 | */
51 | private Thread generalCallTThread;
52 |
53 | public ScheduledTaskPool(ChannelHandlerContext ctx) {
54 | this.ctx = ctx;
55 | }
56 |
57 | /**
58 | *
59 | * @Title: sendStatrFrame
60 | * @Description: 发送启动帧
61 | */
62 | public void sendStatrFrame() {
63 | synchronized (sendStatrStatus) {
64 | if (sendStatrThread != null) {
65 | sendStatrStatus = true;
66 | sendStatrThread.start();
67 | } else if (sendStatrThread == null) {
68 | sendStatrStatus = true;
69 | sendStatrThread = new Thread(new Runnable() {
70 | @Override
71 | public void run() {
72 | while (sendStatrStatus) {
73 | try {
74 | ctx.channel().writeAndFlush(BasicInstruction104.STARTDT);
75 | LOGGER.info("发送启动链路指令");
76 | Thread.sleep(5000);
77 | } catch (Exception e) {
78 | e.printStackTrace();
79 | }
80 | }
81 | }
82 | });
83 | sendStatrThread.start();
84 | }
85 | }
86 | }
87 |
88 | /**
89 | *
90 | * @Title: stopSendStatrFrame
91 | * @Description: 停止发送确认帧
92 | */
93 | public void stopSendStatrFrame() {
94 | if (sendStatrThread != null) {
95 | sendStatrStatus = false;
96 | }
97 | }
98 |
99 |
100 | /**
101 | *
102 | * @Title: sendTestFrame
103 | * @Description: 发送测试帧
104 | */
105 | public void sendTestFrame() {
106 | synchronized (sendTestStatus) {
107 | if (sendTestThread != null && sendTestThread.getState() == Thread.State.TERMINATED) {
108 | sendTestStatus = true;
109 | sendTestThread.start();
110 | } else if (sendTestThread == null) {
111 | sendTestStatus = true;
112 | sendTestThread = new Thread(new Runnable() {
113 | @Override
114 | public void run() {
115 | while (sendTestStatus) {
116 | try {
117 | LOGGER.info("发送测试链路指令");
118 | ctx.channel().writeAndFlush(BasicInstruction104.TESTFR);
119 | Thread.sleep(5000);
120 | } catch (Exception e) {
121 | e.printStackTrace();
122 | }
123 | }
124 | }
125 | });
126 | sendTestThread.start();
127 | }
128 | }
129 | }
130 |
131 | /**
132 | *
133 | * @Title: stopSendTestFrame
134 | * @Description: 停止发送测试帧
135 | */
136 | public void stopSendTestFrame() {
137 | if (sendTestThread != null) {
138 | sendTestStatus = false;
139 | }
140 | }
141 |
142 | /**
143 | *
144 | * @Title: sendGeneralCall
145 | * @Description: 发送总召唤
146 | */
147 | public void sendGeneralCall() {
148 | synchronized (senGeneralCallStatus) {
149 | if (generalCallTThread != null && generalCallTThread.getState() == Thread.State.TERMINATED) {
150 | senGeneralCallStatus = true;
151 | generalCallTThread.start();
152 | } else if (generalCallTThread == null) {
153 | senGeneralCallStatus = true;
154 | generalCallTThread = new Thread(new Runnable() {
155 | @Override
156 | public void run() {
157 | while (sendTestStatus) {
158 | try {
159 | LOGGER.info("发送总召唤指令");
160 | ctx.channel().writeAndFlush(BasicInstruction104.getGeneralCallRuleDetail104());
161 | Thread.sleep(1000 * 60);
162 | } catch (Exception e) {
163 | e.printStackTrace();
164 | }
165 | }
166 | }
167 | });
168 | generalCallTThread.start();
169 | }
170 | }
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/enums/QualifiersEnum.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.enums;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | *
7 | * @ClassName: 限定词
8 | * @author sun
9 | */
10 | public enum QualifiersEnum {
11 |
12 | /**
13 | * 总召唤限定词
14 | */
15 | generalCallQualifiers(TypeIdentifierEnum.generalCall, 0x20),
16 |
17 | /**
18 | * 总召唤限定词 支持 老版的分组
19 | */
20 | generalCallGroupingQualifiers(TypeIdentifierEnum.generalCall, 0x14),
21 | /**
22 | * 复位进程限定词
23 | */
24 | resetPprocessQualifiers(TypeIdentifierEnum.resetPprocess, 0x01),
25 | /**
26 | * 初始化原因 当地电源合上
27 | */
28 | localCloseUpQualifiers(TypeIdentifierEnum.initEnd, 0x00),
29 | /**
30 | * 初始化原因 当地手动复位
31 | */
32 | localMmanualResetQualifiers(TypeIdentifierEnum.initEnd, 0x01),
33 | /**
34 | * 远方复位
35 | */
36 | distanceResetQualifiers(TypeIdentifierEnum.initEnd, 0x02),
37 | /**
38 | * 品质描述词 遥测
39 | */
40 | qualityQualifiers(null, 0x00),
41 | /**
42 | * 设置命令限定词 选择预置参数 1000 0000
43 | */
44 | prefabParameterQualifiers(null, 0x40),
45 | /**
46 | * 设置命令限定词 执行激活参数
47 | */
48 | activationParameterQualifiers(null, 0x0F);
49 |
50 | @Getter
51 | private byte value;
52 | @Getter
53 | private TypeIdentifierEnum typeIdentifier;
54 |
55 | QualifiersEnum(TypeIdentifierEnum typeIdentifier, int value) {
56 | this.value = (byte) value;
57 | this.typeIdentifier = typeIdentifier;
58 | }
59 |
60 |
61 | /**
62 | * 根据传输类型和 限定词的关系返回 限定词的类型
63 | * @param typeIdentifier
64 | * @param value
65 | * @return
66 | */
67 | public static QualifiersEnum getQualifiersEnum(TypeIdentifierEnum typeIdentifier, byte value) {
68 | for (QualifiersEnum type : QualifiersEnum.values()) {
69 | if (type.getValue() == value && type.getTypeIdentifier() == typeIdentifier) {
70 | return type;
71 | }
72 | }
73 | // 品质描述词和设置参数 限定词对应多个值 所以需要做特殊处理
74 | QualifiersEnum qualifiersEnum = null;
75 | if ((TypeIdentifierEnum.normalizedTelemetry.equals(typeIdentifier)
76 | || TypeIdentifierEnum.scaledTelemetry.equals(typeIdentifier)
77 | || TypeIdentifierEnum.shortFloatingPointTelemetry.equals(typeIdentifier))
78 | && qualityQualifiers.getValue() == value) {
79 | qualifiersEnum = qualityQualifiers;
80 | }
81 | if ((TypeIdentifierEnum.readOneParameter.equals(typeIdentifier)
82 | || TypeIdentifierEnum.readMultipleParameter.equals(typeIdentifier)
83 | || TypeIdentifierEnum.prefabActivationOneParameter.equals(typeIdentifier)
84 | || TypeIdentifierEnum.prefabActivationMultipleParameter.equals(typeIdentifier))
85 | && qualityQualifiers.getValue() == value) {
86 | qualifiersEnum = qualityQualifiers;
87 | }
88 | return qualifiersEnum;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/enums/TypeIdentifierEnum.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.enums;
2 |
3 | /**
4 | *
5 | * @ClassName: TypeIdentifierEnum 类型标识
6 | * @author sun
7 | */
8 | public enum TypeIdentifierEnum {
9 |
10 | /**
11 | * 单点摇信
12 | */
13 | onePointTeleindication(0x01),
14 | /**
15 | * 双点摇信
16 | */
17 | twoPointTeleindication(0x03),
18 | /**
19 | * 测量值 归一化值 遥测
20 | */
21 | normalizedTelemetry(0x09),
22 | /**
23 | * 测量值 标度化值 遥测
24 | */
25 | scaledTelemetry(0x0B),
26 | /**
27 | * 测量值 短浮点数 遥测 Short floating point
28 | */
29 | shortFloatingPointTelemetry(0x0D),
30 | /**
31 | * 摇信带时标 单点
32 | */
33 | onePointTimeTeleindication(0x1E),
34 | /**
35 | * 摇信带时标 双点
36 | */
37 | twoPointTimeTeleindication(0x1F),
38 | /**
39 | * 单命令 遥控
40 | */
41 | onePointTelecontrol(0x2D),
42 | /**
43 | * 双命令遥控
44 | */
45 | twoPointTelecontrol(0x2E),
46 | /**
47 | * 读单个参数
48 | */
49 | readOneParameter(0x66),
50 | /**
51 | * 读多个参数
52 | */
53 | readMultipleParameter(0x84),
54 | /**
55 | * 预置单个参数命令
56 | */
57 | prefabActivationOneParameter(0x30),
58 | /**
59 | * 预置多个个参数
60 | */
61 | prefabActivationMultipleParameter(0x88),
62 | /**
63 | * 初始化结束
64 | */
65 | initEnd(0x46),
66 | /**
67 | * 召唤命令
68 | */
69 | generalCall(0x64),
70 | /**
71 | * 时钟同步
72 | */
73 | timeSynchronization(0x67),
74 | /**
75 | * 复位进程
76 | */
77 | resetPprocess(0x69);
78 |
79 | private byte value;
80 | TypeIdentifierEnum(int value) {
81 | this.value = (byte) value;
82 | }
83 | public byte getValue() {
84 | return value;
85 | }
86 |
87 | public static TypeIdentifierEnum getTypeIdentifierEnum(byte value) {
88 | for (TypeIdentifierEnum type : TypeIdentifierEnum.values()) {
89 | if (type.getValue() == value) {
90 | return type;
91 | }
92 | }
93 | return null;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/enums/UControlEnum.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.enums;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | * U帧 基本指令
7 | *
8 | */
9 | public enum UControlEnum {
10 | /**
11 | * 测试命令
12 | */
13 | TESTFR(0x43000000),
14 | /**
15 | * 测试确认指令
16 | */
17 | TESTFR_YES(0x83000000),
18 | /**
19 | * 停止指令
20 | */
21 | STOPDT(0x13000000),
22 | /**
23 | * 停止确认
24 | */
25 | STOPDT_YES(0x23000000),
26 | /**
27 | * 启动命令
28 | */
29 | STARTDT(0x07000000),
30 | /**
31 | * 启动确认命令
32 | */
33 | STARTDT_YES(0x0B000000);
34 |
35 | @Getter
36 | private int value;
37 |
38 | UControlEnum(int value) {
39 | this.value = value;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/exception/CustomException.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.exception;
2 |
3 | /**
4 | * @Author sun
5 | */
6 | public class CustomException extends Exception {
7 | private String content;
8 |
9 | public CustomException(String content) {
10 | this.content = content;
11 | }
12 |
13 | @Override
14 | public String toString() {
15 | return this.content;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/exception/IllegalFormatException.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.exception;
2 |
3 | /**
4 | * @Author sun
5 | */
6 | public class IllegalFormatException extends Exception {
7 | /**
8 | *
9 | */
10 | private static final long serialVersionUID = 3986445004211566058L;
11 |
12 | public IllegalFormatException() {
13 | super();
14 | }
15 |
16 | public String toString() {
17 | return "非法报文。报文应为16进制数字组成,并且由0x68或者0x10为报文头,0x16为报文尾(104规约无)。";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/exception/LengthException.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.exception;
2 |
3 | /**
4 | * @Author sun
5 | */
6 | public class LengthException extends Exception {
7 |
8 | private long desired;//期望的长度
9 | private long reality;//实际的长度
10 |
11 | public LengthException(long desired, long reality) {
12 | this.desired = desired;
13 | this.reality = reality;
14 | }
15 |
16 | @Override
17 | public String toString() {
18 | return "报文长度错误,期望报文长度为" + desired +
19 | "字节。而真实的报文长度为" + reality +
20 | "字节";
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/exception/UnknownLinkCodeException.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.exception;
2 |
3 | /**
4 | * @Author sun
5 | */
6 | public class UnknownLinkCodeException extends Exception {
7 |
8 | /**
9 | *
10 | */
11 | private static final long serialVersionUID = -8260695109983771420L;
12 |
13 | public UnknownLinkCodeException() {
14 | super();
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return "未知类型的链路功能码(平衡式)";
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/exception/UnknownTransferReasonException.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.exception;
2 |
3 | /**
4 | * @Author sun
5 | */
6 | public class UnknownTransferReasonException extends Exception {
7 |
8 | /**
9 | *
10 | */
11 | private static final long serialVersionUID = 4977251357848627763L;
12 |
13 | public UnknownTransferReasonException() {
14 | super();
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return "未定义传送原因。";
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/exception/UnknownTypeIdentifierException.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.exception;
2 |
3 | /**
4 | * @Author sun
5 | */
6 | public class UnknownTypeIdentifierException extends Exception {
7 | public UnknownTypeIdentifierException() {
8 | super();
9 | }
10 |
11 | @Override
12 | public String toString() {
13 | return "未知类型标识符";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/message/MessageDetail.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.message;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Date;
5 | import java.util.List;
6 |
7 | import com.csg.ioms.iec.enums.QualifiersEnum;
8 | import com.csg.ioms.iec.enums.TypeIdentifierEnum;
9 | import com.csg.ioms.iec.utils.Iec104Util;
10 |
11 | import lombok.Data;
12 |
13 | /**
14 | * 一条报文对应的消息体
15 | * @author sun
16 | */
17 | @Data
18 | public class MessageDetail {
19 |
20 | /**
21 | * 启动字符 固定 一个字节
22 | */
23 | private byte start = 0x68;
24 |
25 | /**
26 | * APUU 长度1个字节
27 | */
28 | private int apuuLength = 0;
29 |
30 | /**
31 | * 控制域 四个字节
32 | */
33 | private byte[] control;
34 |
35 |
36 | /**
37 | * 类型标识 1字节
38 | */
39 | private TypeIdentifierEnum typeIdentifier;
40 |
41 |
42 | /**
43 | * 可变结构限定词 1个字节
44 | * true SQ = 0 true 数目number 是 信息对象的数目
45 | * false SQ = 1 单个对象的信息元素或者信息元素的集合的数目
46 | */
47 |
48 | private boolean isContinuous;
49 |
50 | /**
51 | * 消息长度
52 | */
53 | private int measgLength;
54 | /**
55 | * 传输原因 两个字节
56 | */
57 | private short transferReason;
58 |
59 | /**
60 | * 终端地址 也就是应用服务数据单元公共地址
61 | */
62 | private short terminalAddress;
63 |
64 | /**
65 | * 消息地址 字节
66 | */
67 | private int messageAddress;
68 |
69 | /**
70 | * 消息结构
71 | */
72 | private List messages;
73 |
74 |
75 | /**
76 | * 判断是否有消息元素
77 | */
78 | private boolean isMessage;
79 | /**
80 | * 判断是否有限定词
81 | */
82 | private boolean isQualifiers;
83 | /**
84 | * 判断是否有时标
85 | */
86 | private boolean isTimeScaleExit;
87 |
88 | private QualifiersEnum qualifiers;
89 | /**
90 | *
91 | * 时标
92 | */
93 | private Date timeScale;
94 |
95 | /**
96 | * 十六进制 字符串
97 | */
98 | private String hexString;
99 |
100 | public MessageDetail() {
101 | }
102 |
103 | /**
104 | *
105 | * @param control 控制域
106 | * @param typeIdentifierEnum 类型标识
107 | * @param sq 0 地址不连续 1 地址连续
108 | * @param isTest 传输原因 0 未试验 1 试验
109 | * @param isPn 肯定确认 和否定确认
110 | * @param transferReason 传输原因 后六个比特位
111 | * @param terminalAddress 服务地址
112 | * @param messageAddress 消息地址
113 | * @param messages 消息列表
114 | * @param timeScale 时间
115 | * @param qualifiers 限定词
116 | * @return
117 | */
118 | public MessageDetail(byte[] control, TypeIdentifierEnum typeIdentifierEnum, boolean sq,
119 | boolean isTest, boolean isPn, short transferReason, short terminalAddress, int messageAddress,
120 | List messages, Date timeScale, QualifiersEnum qualifiers) {
121 | this.control = control;
122 | this.typeIdentifier = typeIdentifierEnum;
123 | this.isContinuous = sq;
124 | this.measgLength = messages.size();
125 | this.transferReason = Iec104Util.getTransferReasonShort(isTest, isPn, transferReason);
126 | this.messages = messages;
127 | this.terminalAddress = terminalAddress;
128 | this.timeScale = timeScale;
129 | if (timeScale != null) {
130 | this.isTimeScaleExit = true;
131 | }
132 | this.qualifiers = qualifiers;
133 | }
134 |
135 |
136 | /**
137 | * U 帧或者S帧
138 | * @param control 控制域
139 | */
140 | public MessageDetail(byte[] control) {
141 | this.control = control;
142 | this.messages = new ArrayList<>();
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/message/MessageInfo.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.message;
2 |
3 | import java.util.Date;
4 |
5 | import com.csg.ioms.iec.enums.QualifiersEnum;
6 |
7 | import lombok.Data;
8 |
9 | /**
10 | * 报文中 的消息部分
11 | */
12 | @Data
13 | public class MessageInfo {
14 | /**
15 | * 消息地址 字节
16 | */
17 | private int messageAddress;
18 |
19 | /**
20 | * 信息元素集合 1 2 4 个字节
21 | */
22 | private byte[] messageInfos;
23 |
24 | /**
25 | * 限定词
26 | */
27 | private QualifiersEnum qualifiers;
28 | /**
29 | *
30 | * 时标
31 | */
32 | private Date timeScale;
33 |
34 | /**
35 | * 消息详情
36 | */
37 | private int messageInfoLength;
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/protocol/ASDU.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.protocol;
2 |
3 |
4 | import com.csg.ioms.iec.common.TransferReason;
5 | import com.csg.ioms.iec.common.TypeIdentifier;
6 | import com.csg.ioms.iec.exception.UnknownTransferReasonException;
7 | import com.csg.ioms.iec.exception.UnknownTypeIdentifierException;
8 | import com.csg.ioms.iec.utils.Util;
9 |
10 | public class ASDU {
11 |
12 |
13 | public static String ASDU_analysis(int[] asdu) throws UnknownTypeIdentifierException, UnknownTransferReasonException {
14 | StringBuilder builder = new StringBuilder();
15 | builder.append("类属性标识符[7 byte]:").append(TypeIdentifier.getDescribe(asdu[0])).append("\n");
16 | builder.append("可变结构限定词[8 byte]:").append(VariTureDete(asdu[1])).append("\n");
17 | builder.append("传送原因[9 byte - 10 byte]:").append(CotAnalysis(asdu[2], asdu[3])).append("\n");
18 |
19 | builder.append("应用服务数据单元公共地址[11 byte - 12 byte]:").append(Util.address(asdu[4], asdu[5]));
20 | int info[] = new int[asdu.length - 6];
21 | // 将[信息元素+限定词+(时标)]装入数组info
22 | for (int i = 0; i < info.length; i++) {
23 | info[i] = asdu[6 + i];
24 | }
25 | builder.append(Infoanalysis(info, asdu[0],
26 | ((asdu[1] & 0x80) >> 7), (asdu[1] & 0x7f)));
27 | return builder.toString();
28 | }
29 |
30 | /**
31 | * @param info 信息地址+信息元素+限定词+(时标)
32 | * @param i 类型标识
33 | * @param j SQ
34 | * @param k 信息体个数
35 | * @return
36 | * @throws Exception
37 | */
38 | private static String Infoanalysis(int[] info, int i, int j, int k) {
39 | String s = "";
40 | switch (i) {
41 | case 1:
42 | s += new Telemetry104().NoTime_Point(info, k, i, j);
43 | break;
44 | case 3:
45 | s += new Telemetry104().NoTime_Point(info, k, i, j);
46 | break;
47 | case 9:// 测量值,归一化值(遥测)*
48 | s += new Telesignalling104().normalization(info, k, i, j);
49 | break;
50 | case 11:// 测量值,标度化值(遥测)
51 | s += new Telesignalling104().standardization(info, k, i, j);
52 | break;
53 | case 13:// 测量值,短浮点数(遥测)
54 | s += new Telesignalling104().short_float(info, k, i, j);
55 | break;
56 | case 30:
57 | s += new Telemetry104().Time_Point(info, k, i, j);
58 | break;
59 | case 31:
60 | s += new Telemetry104().Time_Point(info, k, i, j);
61 | break;
62 | case 45:// 单命令(遥控)
63 | s = new Telecontrol104().Single_command(info, k, i);
64 | break;
65 | case 46:// 双命令(遥控)
66 | s = new Telecontrol104().Double_command(info, k, i);
67 | break;
68 | case 48:
69 | s += new ParamePreset104().activate_single_parmeter(info, k, i, j);
70 | break;
71 | case 70:
72 | s += "信息对象地址:";
73 | s += InfoAddress(info[0], info[1], info[2]);
74 | s += "\n";
75 | s += "初始化原因:" + (info[3]) + " ";
76 | s += (info[3] == 0) ? "当地电源合上" : (info[3] == 1) ? "当地手动复位"
77 | : (info[3] == 2) ? "远方复位" : "使用保留";
78 | break;
79 | case 100:
80 | s += "信息对象地址:";
81 | s += InfoAddress(info[0], info[1], info[2]);
82 | s += "\n";
83 | if (info[3] == 20) {
84 | s += "召唤限定词QOI:20";
85 | } else {
86 | s += "召唤限定词出错!当前值为0x"
87 | + ((info[3] < 10) ? "0" + Integer.toString(info[3], 16)
88 | : Integer.toString(info[3], 16))
89 | + ",正确值应为0x14!";
90 | }
91 |
92 | break;
93 | case 102:
94 | s += new ParamePreset104().single_parmeter(info, k, i, j);
95 | break;
96 | case 103:
97 | s += "信息对象地址:";
98 | s += InfoAddress(info[0], info[1], info[2]);
99 | s += "\n";
100 | int[] time = new int[7];
101 | for (int l = 0; l < time.length; l++) {
102 | time[l] = info[3 + l];
103 | }
104 | s += TimeScale(time);
105 | break;
106 |
107 | case 105:
108 | s += "信息对象地址:";
109 | s += InfoAddress(info[0], info[1], info[2]);
110 | s += "\n";
111 | if (info[3] == 1) {
112 | s += "复位进程限定词:1";
113 | } else {
114 | s += "复位进程限定词出错!当前值为0x"
115 | + ((info[3] < 10) ? "0" + Integer.toString(info[3], 16)
116 | : Integer.toString(info[3], 16))
117 | + ",正确值应为0x01!";
118 | }
119 | break;
120 | case 132:
121 | s += new ParamePreset104().multi_parmeter(info, k, i, j);
122 | break;
123 | case 136:
124 | s += new ParamePreset104().activate_multi_parmeter(info, k, i, j);
125 | break;
126 | default:
127 | s = "类型标识出错,无法解析信息对象!";
128 | break;
129 | }
130 |
131 | return s;
132 | }
133 |
134 | /**
135 | * 信息对象地址转换
136 | *
137 | * @param i bit0 ~bit7
138 | * @param j bit8 ~bit15
139 | * @param k bit16 ~bit23
140 | * @return 十进制地址(十六进制地址)
141 | */
142 | public static String InfoAddress(int i, int j, int k) {
143 |
144 | int add;
145 | add = (k << 16) + (j << 8) + i;
146 |
147 | return add
148 | + "("
149 | + ((k < 10) ? "0" + Integer.toString(k, 16) : Integer.toString(
150 | k, 16))
151 | + ((j < 10) ? "0" + Integer.toString(j, 16) : Integer.toString(
152 | j, 16))
153 | + ((i < 10) ? "0" + Integer.toString(i, 16) : Integer.toString(
154 | i, 16)) + "H)" + "";
155 | }
156 |
157 | /**
158 | * 解析传送原因
159 | *
160 | * @param i
161 | * @param j
162 | * @return
163 | */
164 | public static String CotAnalysis(int i, int j) throws UnknownTransferReasonException {
165 | StringBuilder builder = new StringBuilder();
166 | if ((i & 0x80) == 128) {
167 | builder.append("[T(test) bit7:1 实验]");
168 | } else if ((i & 0x80) == 0) {
169 | builder.append("[T(test) bit7:0 未实验]");
170 | }
171 | if ((i & 0x40) == 64) {
172 | builder.append("[P/N bit6:1 否定确认]");
173 | } else if ((i & 0x40) == 0) {
174 | builder.append("[P/N bit6:0 肯定确认]");
175 | }
176 | builder.append("[原因 bit5~bit0:").append(TransferReason.getDdescribe(i & 0x3F)).append("]");
177 | return builder.toString();
178 | }
179 |
180 | /**
181 | * 解析可变结构限定词
182 | *
183 | * @param b
184 | * @return
185 | */
186 | private static String VariTureDete(int b) {
187 | String vvString = "可变结构限定词:";
188 | vvString += Util.toHexString(b);
189 | vvString += " ";
190 | if ((b & 0x80) == 128) {
191 | vvString += "SQ=1 信息元素地址顺序 ";
192 | } else if ((b & 0x80) == 0) {
193 | vvString += "SQ=0 信息元素地址非顺序 ";
194 | }
195 | vvString += "信息元素个数:";
196 | vvString += b & 0x7f;
197 | return vvString;
198 | }
199 |
200 | private static String TimeScale(int b[]) {
201 |
202 | StringBuilder result = new StringBuilder();
203 |
204 | int year = b[6] & 0x7F;
205 | int month = b[5] & 0x0F;
206 | int day = b[4] & 0x1F;
207 | int week = (b[4] & 0xE0) / 32;
208 | int hour = b[3] & 0x1F;
209 | int minute = b[2] & 0x3F;
210 | int second = (b[1] << 8) + b[0];
211 |
212 | result.append("时标CP56Time2a:20");
213 | result.append(year).append("-");
214 | result.append(String.format("%02d", month)).append("-");
215 | result.append(String.format("%02d", day)).append(",");
216 | result.append(hour).append(":").append(minute).append(":");
217 | result.append(second / 1000 + "." + second % 1000).append("\n");
218 |
219 | return result.toString();
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/protocol/Analysis.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.protocol;
2 |
3 |
4 | import com.csg.ioms.iec.exception.IllegalFormatException;
5 | import com.csg.ioms.iec.exception.LengthException;
6 | import com.csg.ioms.iec.exception.UnknownTransferReasonException;
7 | import com.csg.ioms.iec.exception.UnknownTypeIdentifierException;
8 | import com.csg.ioms.iec.utils.Util;
9 |
10 | /**
11 | * 104解析
12 | *
13 | * @author sun
14 | */
15 | public class Analysis {
16 |
17 | public static String analysis(String message) throws IllegalFormatException, LengthException, UnknownTransferReasonException, UnknownTypeIdentifierException {
18 |
19 | StringBuilder contentbuilder = new StringBuilder();
20 |
21 | String mes = message.replaceAll(" ", "");
22 | if ((mes.length() == 0) || (mes.length() % 2) == 1) {
23 | //104 报文没有结束字符,所以最好判断一下报文串的长度是否是偶数
24 | throw new IllegalFormatException();
25 | }
26 | // 将报文转化成int数组
27 | int msgArray[] = Util.hexStringToIntArray(mes);
28 | int length = msgArray.length;// 记录报文的长度
29 | if (msgArray[0] == 0x68 && length >= 2) {
30 | contentbuilder.append("*APCI应用规约控制信息*").append("\n");
31 | contentbuilder.append("启动字符[1 byte]: 0x68 ").append("\n");
32 | } else {
33 | throw new IllegalFormatException();
34 | }
35 | if (length != msgArray[1] + 2) {
36 | throw new LengthException(msgArray[1] + 2, length);
37 | }
38 | contentbuilder.append("应用规约数据单元(APDU)长度[2 byte]:").append(msgArray[1]).append("字节").append("\n");
39 | contentbuilder.append("控制域[3 byte - 6 byte]:").append("\n").append(Control(new int[]{msgArray[2], msgArray[3], msgArray[4], msgArray[5]}));
40 |
41 | if ((msgArray[2] & 0x03) != 3 && (msgArray[2] & 0x03) != 1) {
42 | //解析ASDU
43 | contentbuilder.append("*ASDU应用服务数据单元*\n");
44 | int asdu[] = new int[length - 6];
45 | for (int j = 0; j < length - 6; j++) {
46 | asdu[j] = msgArray[6 + j];
47 | }
48 | contentbuilder.append(ASDU.ASDU_analysis(asdu));
49 | }
50 | return contentbuilder.toString();
51 |
52 | }
53 |
54 | /**
55 | * 解析104规约的控制域
56 | *
57 | * @param con
58 | * @return
59 | */
60 | private static String Control(int[] con) {
61 | StringBuilder conBuilder = new StringBuilder();
62 | switch (con[0] & 0x03) {
63 | case 1:
64 | conBuilder.append("\t(S格式控制域标志)\n");
65 | conBuilder.append("\t接受序列号:").append(((con[3] << 8) + con[2]) >> 1);
66 | break;
67 | case 3:
68 | conBuilder.append("\t(U格式控制域标志)\n");
69 | if ((con[0] & 0xC0) == 128) {
70 | conBuilder.append("\t链路测试TESTFR:确认\n");
71 | } else if ((con[0] & 0xC0) == 64) {
72 | conBuilder.append("\t链路测试TESTFR:命令\n");
73 | }
74 | if ((con[0] & 0x30) == 32) {
75 | conBuilder.append("\t断开数据传输STOPDT:确认\n");
76 | } else if ((con[0] & 0x30) == 16) {
77 | conBuilder.append("\t断开数据传输STOPDT:命令\n");
78 | }
79 | if ((con[0] & 0x0C) == 8) {
80 | conBuilder.append("\t启动数据传输STARTDT:确认\n");
81 | } else if ((con[0] & 0x0C) == 4) {
82 | conBuilder.append("\t启动数据传输STARTDT:命令\n");
83 | }
84 | break;
85 | default:
86 | conBuilder.append("\t(I格式控制域标志)\n");
87 | conBuilder.append("\t发送序列号:").append(((con[1] << 8) + con[0]) >> 1).append("\n");
88 | conBuilder.append("\t接受序列号:").append(((con[3] << 8) + con[2]) >> 1).append("\n");
89 | break;
90 | }
91 | return conBuilder.toString();
92 | }
93 |
94 |
95 | public static String analysis(byte[] data) throws IllegalFormatException, LengthException, UnknownTransferReasonException, UnknownTypeIdentifierException {
96 |
97 | StringBuilder contentbuilder = new StringBuilder();
98 | // 将报文转化成int数组
99 | byte[] msgArray = data;
100 | int length = msgArray.length;// 记录报文的长度
101 | if (msgArray[0] == 0x68 && length >= 2) {
102 | contentbuilder.append("*APCI应用规约控制信息*").append("\n");
103 | contentbuilder.append("启动字符[1 byte]: 0x68 ").append("\n");
104 | } else {
105 | throw new IllegalFormatException();
106 | }
107 | if (length != msgArray[1] + 2) {
108 | throw new LengthException(msgArray[1] + 2, length);
109 | }
110 | contentbuilder.append("应用规约数据单元(APDU)长度[2 byte]:").append(msgArray[1]).append("字节").append("\n");
111 | contentbuilder.append("控制域[3 byte - 6 byte]:").append("\n").append(Control(new int[]{msgArray[2], msgArray[3], msgArray[4], msgArray[5]}));
112 |
113 | if ((msgArray[2] & 0x03) != 3 && (msgArray[2] & 0x03) != 1) {
114 | //解析ASDU
115 | contentbuilder.append("*ASDU应用服务数据单元*\n");
116 | int asdu[] = new int[length - 6];
117 | for (int j = 0; j < length - 6; j++) {
118 | asdu[j] = msgArray[6 + j];
119 | }
120 | contentbuilder.append(ASDU.ASDU_analysis(asdu));
121 | }
122 | return contentbuilder.toString();
123 |
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/protocol/ParamePreset104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.protocol;
2 |
3 | import com.csg.ioms.iec.utils.Util;
4 |
5 | /**
6 | * @author sun
7 | *
8 | */
9 | public class ParamePreset104 {
10 | /**
11 | * 读单个参数命令(参数设置)
12 | *
13 | * @param infoElement
14 | * @param num
15 | * @param tI
16 | * @param sQ
17 | * @return
18 | */
19 | public String single_parmeter(int[] infoElement, int num, int tI, int sQ) {
20 | String string = "";
21 |
22 | string += "信息对象地址:";
23 | string += new ASDU().InfoAddress(infoElement[0], infoElement[1],
24 | infoElement[2]);
25 | string += "\n";
26 | string += "参数设置对象信息值:";
27 | string +=Util.toHexString(infoElement[3]);
28 | string += " ";
29 | string += Util.toHexString(infoElement[4]);
30 | string += " ";
31 | string += Util.toHexString(infoElement[5]);
32 | string += " ";
33 | string += Util.toHexString(infoElement[6]);
34 | string += "\n";
35 |
36 | return string;
37 | }
38 |
39 | /**
40 | * 读多个参数命令(参数设置)
41 | *
42 | * @param infoElement
43 | * @param num
44 | * @param tI
45 | * @param sQ
46 | * @return
47 | */
48 | public String multi_parmeter(int[] infoElement, int num, int tI, int sQ) {
49 | String string = "";
50 | for (int i = 0; i < num; i++) {
51 | string += "信息对象";
52 | string += (i + 1);
53 | string += "地址:";
54 | string += new ASDU().InfoAddress(infoElement[i * 7],
55 | infoElement[i * 7 + 1], infoElement[i * 7 + 2]);
56 | string += "\n";
57 | string += "参数设置对象信息值:";
58 | string += Util.toHexString(infoElement[i * 7 + 3]);
59 | string += " ";
60 | string += Util.toHexString(infoElement[i * 7 + 4]);
61 | string += " ";
62 | string += Util.toHexString(infoElement[i * 7 + 5]);
63 | string += " ";
64 | string += Util.toHexString(infoElement[i * 7 + 6]);
65 | string += "\n";
66 | }
67 | return string;
68 | }
69 |
70 | /**
71 | * 预置/激活多个参数命令(参数设置)
72 | *
73 | * @param infoElement
74 | * @param num
75 | * @param tI
76 | * @param sQ
77 | * @return
78 | */
79 | public String activate_multi_parmeter(int[] infoElement, int num, int tI,
80 | int sQ) {
81 | String string = "";
82 | for (int i = 0; i < num; i++) {
83 | string += "信息对象";
84 | string += (i + 1);
85 | string += "地址:";
86 | string += new ASDU().InfoAddress(infoElement[i * 7],
87 | infoElement[i * 7 + 1], infoElement[i * 7 + 2]);
88 | string += "\n";
89 | string += "预置/激活参数命令对象信息值:";
90 | string += Util.toHexString(infoElement[i * 7 + 3]);
91 | string += " ";
92 | string += Util.toHexString(infoElement[i * 7 + 4]);
93 | string += " ";
94 | string += Util.toHexString(infoElement[i * 7 + 5]);
95 | string += " ";
96 | string += Util.toHexString(infoElement[i * 7 + 6]);
97 | string += "\n";
98 | }
99 | string += "设定命令限定词QOS:";
100 |
101 | string += Util
102 | .toHexString(infoElement[infoElement.length - 1]);
103 | string += " ";
104 | if (infoElement[infoElement.length - 1] == 0x80) {
105 | string += "预置参数";
106 | } else if (infoElement[infoElement.length - 1] == 0x00) {
107 | string += "激活参数";
108 |
109 | }
110 | string += "\n";
111 | return string;
112 | }
113 |
114 | /**
115 | * 预置/激活单个参数命令(参数设置)
116 | *
117 | * @param infoElement
118 | * @param num
119 | * @param tI
120 | * @param sQ
121 | * @return
122 | */
123 | public String activate_single_parmeter(int[] infoElement, int num, int tI,
124 | int sQ) {
125 | String string = "";
126 |
127 | string += "信息对象地址:";
128 | string += new ASDU().InfoAddress(infoElement[0], infoElement[1],
129 | infoElement[2]);
130 | string += "\n";
131 | string += "预置/激活参数命令对象信息值:";
132 | string += Util.toHexString(infoElement[3]);
133 | string += " ";
134 | string += Util.toHexString(infoElement[4]);
135 | string += " ";
136 | string += Util.toHexString(infoElement[5]);
137 | string += " ";
138 | string += Util.toHexString(infoElement[6]);
139 | string += "\n";
140 | string += "设定命令限定词QOS:";
141 |
142 | string += Util.toHexString(infoElement[7]);
143 | string += " ";
144 | if (infoElement[7] == 0x80) {
145 | string += "预置参数";
146 | } else if (infoElement[7] == 0x00) {
147 | string += "激活参数";
148 |
149 | }
150 | string += "\n";
151 | return string;
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/protocol/Telecontrol104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.protocol;
2 |
3 | import com.csg.ioms.iec.utils.Util;
4 |
5 | /**
6 | * 遥控信息解析
7 | *
8 | * @author sun
9 | */
10 | public class Telecontrol104 {
11 |
12 | /**
13 | * 单命令信息解析
14 | *
15 | * @param infoElement 信息元素集报文
16 | * @param num 信息元素的个数
17 | * @param tI 类型标识
18 | * @return
19 | */
20 | public String Single_command(int[] infoElement, int num, int tI) {
21 | StringBuilder builder = new StringBuilder();
22 | builder.append("信息元素地址:");
23 | builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
24 | infoElement[2])).append("\n");
25 | builder.append("单命令 SCO:").append("\t");
26 | builder.append(Util.toHexString(infoElement[3])).append("\n");
27 | if ((infoElement[3] & 0x80) == 128) {
28 | builder.append("遥控选择命令\t");
29 | }
30 | if ((infoElement[3] & 0x80) == 0) {
31 | builder.append("遥控执行命令\t");
32 | }
33 | if ((infoElement[3] & 0x01) == 1) {
34 | builder.append("开关合\t");
35 | }
36 | if ((infoElement[3] & 0x01) == 0) {
37 | builder.append("开关分\t");
38 | }
39 | return builder.toString();
40 | }
41 |
42 | /**
43 | * 双命令信息解析
44 | *
45 | * @param infoElement 信息元素集报文
46 | * @param num 信息元素的个数
47 | * @param tI 类型标识
48 | * @return
49 | */
50 | public String Double_command(int[] infoElement, int num, int tI) {
51 | StringBuilder builder = new StringBuilder();
52 | builder.append("信息元素地址:");
53 | builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
54 | infoElement[2])).append("\n");
55 | builder.append("双命令 DCO:\t");
56 | builder.append(Util.toHexString(infoElement[3])).append("\n");
57 | if ((infoElement[3] & 0x80) == 128) {
58 | builder.append("遥控选择命令\t");
59 | }
60 | if ((infoElement[3] & 0x80) == 0) {
61 | builder.append("遥控执行命令\t");
62 | }
63 | if ((infoElement[2] & 0x03) == 0) {
64 | builder.append("不允许,有错误\t");
65 | }
66 | if ((infoElement[3] & 0x03) == 1) {
67 | builder.append("开关分\t");
68 | }
69 | if ((infoElement[3] & 0x03) == 2) {
70 | builder.append("开关合\t");
71 | }
72 | if ((infoElement[3] & 0x03) == 3) {
73 | builder.append("不允许,有错误\t");
74 | }
75 | return builder.toString();
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/protocol/Telemetry104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.protocol;
2 |
3 | import com.csg.ioms.iec.utils.Util;
4 |
5 | /**
6 | * 遥信信息解析
7 | *
8 | * @author sun
9 | */
10 | public class Telemetry104 {
11 | /**
12 | * 不带时标的单双点信息(遥信)
13 | *
14 | * @param infoElement
15 | * @param num
16 | * @param tI
17 | * @param sQ
18 | * @return
19 | */
20 | public String NoTime_Point(int[] infoElement, int num, int tI, int sQ) {
21 | StringBuilder builder = new StringBuilder();
22 | if (sQ == 1) {
23 | builder.append("信息对象地址: ");
24 | builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
25 | infoElement[2])).append("\n");
26 | for (int i = 0; i < num; i++) {
27 | builder.append("信息元素");
28 | builder.append(i + 1);
29 | builder.append("的信息元素值:");
30 | builder.append(Util.toHexString(infoElement[i + 3]));
31 | builder.append("\n");
32 | builder.append(this.point(infoElement[i + 3], tI));
33 | builder.append("\n");
34 | }
35 |
36 | } else {
37 | for (int i = 1; i <= num; i++) {
38 | builder.append("信息元素");
39 | builder.append(i);
40 | builder.append("的内容如下:\n");
41 | builder.append("信息对象地址: ");
42 | builder.append(new ASDU().InfoAddress(infoElement[(i - 1) * 4],
43 | infoElement[(i - 1) * 4 + 1],
44 | infoElement[(i - 1) * 4 + 2]));
45 | builder.append("\n");
46 | builder.append("信息元素值:");
47 | builder.append(Util.toHexString(infoElement[i * 4 - 1]));
48 | builder.append("\n");
49 | builder.append(this.point(infoElement[i * 4 - 1], tI));
50 | builder.append("\n");
51 | }
52 |
53 | }
54 | return builder.toString();
55 | }
56 |
57 | private String point(int i, int tI) {
58 | StringBuilder builder = new StringBuilder();
59 | if (tI == 1 || tI == 30) {
60 | if ((i & 0x80) == 128) {
61 | builder.append("无效/");
62 | }
63 | if ((i & 0x80) == 0) {
64 | builder.append("有效/");
65 | }
66 | if ((i & 0x40) == 64) {
67 | builder.append("非当前值/");
68 | }
69 | if ((i & 0x40) == 0) {
70 | builder.append("当前值/");
71 | }
72 | if ((i & 0x20) == 32) {
73 | builder.append("被取代/");
74 | }
75 | if ((i & 0x20) == 0) {
76 | builder.append("未被取代/");
77 | }
78 | if ((i & 0x10) == 16) {
79 | builder.append("被闭锁/");
80 | }
81 | if ((i & 0x10) == 0) {
82 | builder.append("未被闭锁/");
83 | }
84 | if ((i & 0x01) == 1) {
85 | builder.append("开关合 ");
86 | }
87 | if ((i & 0x01) == 0) {
88 | builder.append("开关分 ");
89 | }
90 | }
91 | if (tI == 3 || tI == 31) {
92 | if ((i & 0x80) == 128) {
93 | builder.append("无效/");
94 | }
95 | if ((i & 0x80) == 0) {
96 | builder.append("有效/");
97 | }
98 | if ((i & 0x40) == 64) {
99 | builder.append("非当前值/");
100 | }
101 | if ((i & 0x40) == 0) {
102 | builder.append("当前值/");
103 | }
104 | if ((i & 0x20) == 32) {
105 | builder.append("被取代/");
106 | }
107 | if ((i & 0x20) == 0) {
108 | builder.append("未被取代/");
109 | }
110 | if ((i & 0x10) == 16) {
111 | builder.append("被闭锁/");
112 | }
113 | if ((i & 0x10) == 0) {
114 | builder.append("未被闭锁/");
115 | }
116 | if ((i & 0x03) == 0) {
117 | builder.append("不确定或中间状态 ");
118 | }
119 | if ((i & 0x03) == 1) {
120 | builder.append("确定开关分 ");
121 | }
122 | if ((i & 0x03) == 2) {
123 | builder.append("确定开关合 ");
124 | }
125 | if ((i & 0x03) == 3) {
126 | builder.append("不确定 ");
127 | }
128 |
129 | }
130 |
131 | return builder.toString();
132 | }
133 |
134 | /**
135 | * 带CP56Time2a时标的单双点信息(遥信)
136 | *
137 | * @param infoElement
138 | * @param num
139 | * @param tI
140 | * @param sQ
141 | * @return
142 | */
143 | public String Time_Point(int[] infoElement, int num, int tI, int sQ) {
144 |
145 | StringBuilder builder = new StringBuilder();
146 | if (sQ == 0) {
147 | for (int i = 1; i <= num; i++) {
148 | builder.append("信息元素");
149 | builder.append(i);
150 | builder.append("的内容如下:\n");
151 | builder.append("信息对象地址:");
152 | builder.append(new ASDU().InfoAddress(infoElement[(i - 1) * 11],
153 | infoElement[(i - 1) * 11 + 1],
154 | infoElement[(i - 1) * 11 + 2])).append("\n");
155 | builder.append("信息元素值:");
156 | builder.append(Util.toHexString(infoElement[(i - 1) * 11 + 3])).append("\n");
157 |
158 | builder.append(this.point(infoElement[(i - 1) * 11 + 3], tI)).append("\n");
159 |
160 | int time[] = new int[7];
161 | for (int j = 0; j < 7; j++) {
162 | time[j] = infoElement[(i - 1) * 11 + 4 + j];
163 | }
164 | builder.append(Util.TimeScale(time));
165 | }
166 | } else {
167 | builder.append("按照DL/T 634.5101-2002规定,带长时标的单/双点信息遥信报文并不存在信息元素序列(SQ=1)的情况。");
168 | }
169 | return builder.toString();
170 |
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/protocol/Telesignalling104.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.protocol;
2 |
3 | import com.csg.ioms.iec.utils.Util;
4 |
5 | /**
6 | * 遥测信息解析
7 | *
8 | * @author sun
9 | *
10 | */
11 | public class Telesignalling104 {
12 | /**
13 | * 测量值,归一化值
14 | *
15 | * @param infoElement
16 | * @param num
17 | * @param tI
18 | * @param sQ
19 | * @return
20 | */
21 | public String normalization(int[] infoElement, int num, int tI, int sQ) {
22 | StringBuilder builder = new StringBuilder();
23 | if (sQ == 1) {
24 |
25 | builder.append("遥测信息对象地址");
26 | builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
27 | infoElement[2]));
28 | builder.append("\n");
29 | for (int i = 0; i < num; i++) {
30 | builder.append("信息对象");
31 | builder.append(i + 1);
32 | builder.append("归一化值NVA:");
33 | builder.append(Util.toHexString(infoElement[i * 3 + 3]));
34 | builder.append("\t");
35 | builder.append(Util.toHexString(infoElement[i * 3 + 4]));
36 | builder.append("\n");
37 | builder.append("品质描述词QDS:");
38 | builder.append(Util.toHexString(infoElement[i * 3 + 5]));
39 | builder.append("\n");
40 | }
41 | } else {
42 | for (int i = 0; i < num; i++) {
43 | builder.append("信息对象");
44 | builder.append((i + 1));
45 | builder.append("的地址:");
46 | builder.append(new ASDU().InfoAddress(infoElement[i * 6],
47 | infoElement[i * 6 + 1], infoElement[i * 6 + 2]));
48 | builder.append("\n");
49 | builder.append("归一化值NVA:");
50 | builder.append(Util.toHexString(infoElement[i * 6 + 3]));
51 | builder.append("\t");
52 | builder.append(Util.toHexString(infoElement[i * 6 + 4]));
53 | builder.append("\n");
54 | builder.append("品质描述词QDS:");
55 | builder.append(Util.toHexString(infoElement[i * 6 + 5]));
56 | builder.append("\n");
57 | }
58 | }
59 |
60 | return builder.toString();
61 | }
62 |
63 | /**
64 | * 测量值,标度化值
65 | *
66 | * @param infoElement
67 | * @param num
68 | * @param tI
69 | * @param sQ
70 | * @return
71 | */
72 | public String standardization(int[] infoElement, int num, int tI, int sQ) {
73 | String string = "";
74 |
75 | return string;
76 | }
77 |
78 | /**
79 | * 测量值,短浮点数
80 | *
81 | * @param infoElement
82 | * @param num
83 | * @param tI
84 | * @param sQ
85 | * @return
86 | */
87 | public String short_float(int[] infoElement, int num, int tI, int sQ) {
88 | StringBuilder builder = new StringBuilder();
89 | if (sQ == 1) {
90 |
91 | builder.append("遥测信息对象地址");
92 | builder.append(new ASDU().InfoAddress(infoElement[0], infoElement[1],
93 | infoElement[2])).append("\n");
94 | for (int i = 0; i < num; i++) {
95 | builder.append("遥测");
96 | builder.append((i + 1));
97 | builder.append("IEEE STD745短浮点数:");
98 | String fl=Util.toHexString(infoElement[i * 5 + 6])+Util.toHexString(infoElement[i * 5 + 5])+Util.toHexString(infoElement[i * 5 + 4])+Util.toHexString(infoElement[i * 5 + 3]);
99 | fl=fl.replaceAll("0x", "");
100 | int ieee754Int = Integer.parseInt(fl, 16);
101 | float realValue = Float.intBitsToFloat(ieee754Int);
102 | builder.append(realValue);
103 | // builder.append(Util.toHexString(infoElement[i * 5 + 3]));
104 | // builder.append("\t");
105 | // builder.append(Util.toHexString(infoElement[i * 5 + 4]));
106 | // builder.append("\t");
107 | // builder.append(Util.toHexString(infoElement[i * 5 + 5]));
108 | // builder.append("\t");
109 | // builder.append(Util.toHexString(infoElement[i * 5 + 6]));
110 | builder.append("\n");
111 | builder.append("品质描述词QDS:");
112 | builder.append(Util.toHexString(infoElement[i * 5 + 7]));
113 | builder.append("\n");
114 | }
115 | } else {
116 | for (int i = 0; i < num; i++) {
117 | builder.append("信息对象");
118 | builder.append((i + 1));
119 | builder.append("的地址:");
120 | builder.append(new ASDU().InfoAddress(infoElement[i * 8],
121 | infoElement[i * 8 + 1], infoElement[i * 8 + 2]));
122 | builder.append("\n");
123 | builder.append("IEEE STD745短浮点数:");
124 | String fl=Util.toHexString(infoElement[i * 8 + 6])+Util.toHexString(infoElement[i * 8 + 5])+Util.toHexString(infoElement[i * 8 + 4])+Util.toHexString(infoElement[i * 8 + 3]);
125 | fl=fl.replaceAll("0x", "");
126 | int ieee754Int = Integer.parseInt(fl, 16);
127 | float realValue = Float.intBitsToFloat(ieee754Int);
128 | builder.append(realValue);
129 | // builder.append(Util.toHexString(infoElement[i * 8 + 3]));
130 | // builder.append("\t");
131 | // builder.append(Util.toHexString(infoElement[i * 8 + 4]));
132 | // builder.append("\t");
133 | // builder.append(Util.toHexString(infoElement[i * 8 + 5]));
134 | // builder.append("\t");
135 | // builder.append(Util.toHexString(infoElement[i * 8 + 6]));
136 | // builder.append("\t");
137 |
138 | builder.append("\n");
139 | builder.append("品质描述词QDS:");
140 | builder.append(Util.toHexString(infoElement[i * 8 + 7])).append("\n");
141 | }
142 | }
143 |
144 | return builder.toString();
145 | }
146 |
147 | public static void main(String[] args) {
148 | String str = "3F58A925";
149 | int ieee754Int = Integer.parseInt(str, 16);
150 | float realValue = Float.intBitsToFloat(ieee754Int);
151 | System.out.println(realValue);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/Iec104Master.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server;
2 |
3 | import com.csg.ioms.iec.config.Iec104Config;
4 | import com.csg.ioms.iec.server.handler.DataHandler;
5 |
6 | /**
7 | * 主站抽象类
8 | */
9 | public interface Iec104Master {
10 |
11 | /**
12 | * 服务启动方法
13 | * @throws Exception
14 | */
15 | void run() throws Exception;
16 |
17 | /**
18 | *
19 | * @Title: setDataHandler
20 | * @Description: 设置数据处理类
21 | * @param dataHandler
22 | */
23 | Iec104Master setDataHandler(DataHandler dataHandler);
24 |
25 | /**
26 | * 设置配置文件
27 | * @param iec104Confiig
28 | * @return
29 | */
30 | Iec104Master setConfig(Iec104Config iec104Confiig);
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/Iec104MasterFactory.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server;
2 |
3 | import com.csg.ioms.iec.server.master.Iec104TcpClientMaster;
4 |
5 | /**
6 | * 主站 工厂类
7 | * @ClassName: Iec104MasterFactory
8 | * @Description: IEC104规约主站
9 | * @author: sun
10 | */
11 | public class Iec104MasterFactory {
12 |
13 |
14 |
15 | /**
16 | * @Title: createTcpClientMaster
17 | * @Description: 创建一个TCM客户端的104主站
18 | * @param host 从机地址
19 | * @param port 端口
20 | * @return
21 | */
22 | public static Iec104Master createTcpClientMaster(String host, int port) {
23 | return new Iec104TcpClientMaster(host, port);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/handler/BytesEncoder.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server.handler;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.handler.codec.MessageToByteEncoder;
6 |
7 |
8 | /**
9 | * 数组编码器
10 | * @author sun
11 | *
12 | */
13 | public class BytesEncoder extends MessageToByteEncoder {
14 |
15 |
16 | @Override
17 | protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception {
18 | out.writeBytes(msg);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/handler/ChannelHandler.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server.handler;
2 |
3 | import com.csg.ioms.iec.message.MessageDetail;
4 |
5 | /**
6 | *
7 | * @ClassName: ChannelHandler
8 | * @Description: 处理数据
9 | * @author: YDL
10 | * @date: 2020年5月19日 上午11:41:58
11 | */
12 | public interface ChannelHandler {
13 | void writeAndFlush(MessageDetail ruleDetail104);
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/handler/ChannelHandlerImpl.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server.handler;
2 |
3 | import com.csg.ioms.iec.message.MessageDetail;
4 |
5 | import io.netty.channel.ChannelHandlerContext;
6 |
7 | /**
8 | *
9 | * @ClassName: ChannelHandlerImpl
10 | * @Description: 实现一个自定义发现消息的类
11 | * @author: sun
12 | */
13 | public class ChannelHandlerImpl implements ChannelHandler {
14 |
15 | private ChannelHandlerContext ctx;
16 |
17 | public ChannelHandlerImpl(ChannelHandlerContext ctx) {
18 | this.ctx = ctx;
19 | }
20 |
21 | @Override
22 | public void writeAndFlush(MessageDetail ruleDetail104) {
23 | ctx.channel().writeAndFlush(ruleDetail104);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/handler/Check104Handler.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server.handler;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import com.csg.ioms.iec.common.Iec104Constant;
7 | import com.csg.ioms.iec.utils.ByteUtil;
8 |
9 | import io.netty.buffer.ByteBuf;
10 | import io.netty.channel.ChannelHandlerContext;
11 | import io.netty.channel.ChannelInboundHandlerAdapter;
12 | import io.netty.util.ReferenceCountUtil;
13 |
14 | /**
15 | *
16 | * @ClassName: Check104Handler
17 | * @Description: 检查104报文
18 | * @author sun
19 | */
20 | public class Check104Handler extends ChannelInboundHandlerAdapter {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(Check104Handler.class);
23 |
24 | /**
25 | * 拦截系统消息
26 | */
27 | @Override
28 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
29 | ByteBuf result = (ByteBuf) msg;
30 | byte[] bytes = new byte[result.readableBytes()];
31 | result.readBytes(bytes);
32 | LOGGER.info("接收到的报文: " + ByteUtil.byteArrayToHexString(bytes));
33 | if (bytes.length < Iec104Constant.APCI_LENGTH || bytes[0] != Iec104Constant.HEAD_DATA) {
34 | LOGGER.error("报文无效");
35 | ReferenceCountUtil.release(result);
36 | } else {
37 | result.writeBytes(bytes);
38 | ctx.fireChannelRead(msg);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/csg/ioms/iec/server/handler/DataDecoder.java:
--------------------------------------------------------------------------------
1 | package com.csg.ioms.iec.server.handler;
2 |
3 | import java.util.List;
4 |
5 | import com.csg.ioms.iec.core.Decoder104;
6 | import com.csg.ioms.iec.core.Iec104ThreadLocal;
7 | import com.csg.ioms.iec.message.MessageDetail;
8 | import com.csg.ioms.iec.protocol.Analysis;
9 | import com.csg.ioms.iec.utils.ByteUtil;
10 | import com.csg.ioms.iec.utils.Iec104Util;
11 |
12 | import io.netty.buffer.ByteBuf;
13 | import io.netty.channel.ChannelHandlerContext;
14 | import io.netty.handler.codec.ByteToMessageDecoder;
15 |
16 |
17 | /**
18 | * 解码器
19 | * @author sun
20 | *
21 | */
22 | public class DataDecoder extends ByteToMessageDecoder {
23 |
24 | @Override
25 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List