辅控项目代码整合初版

This commit is contained in:
weitang 2025-04-23 17:56:18 +08:00
parent fcf11fe63c
commit 1248e5543b
80 changed files with 5411 additions and 2 deletions

View File

@ -0,0 +1,89 @@
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.yfd.platform.component.iec104.client;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.core.CachedThreadPool;
import com.yfd.platform.component.iec104.server.Iec104Master;
import com.yfd.platform.component.iec104.server.Iec104MasterFactory;
import com.yfd.platform.modules.auxcontrol.domain.GatewayDevice;
import com.yfd.platform.modules.auxcontrol.mapper.GatewayDeviceMapper;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author
* @date 2019-01-07
*/
@Component
@RequiredArgsConstructor
public class IEC104ClientRunner implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(IEC104ClientRunner.class);
private final GatewayDeviceMapper gatewayDeviceMapper;
/**
* 项目启动时激活启用的IEC104 与各子站的连接任务
*
* @param applicationArguments /
*/
@Override
@Async
public void run(ApplicationArguments applicationArguments) throws Exception {
LambdaQueryWrapper<GatewayDevice> queryWrapper = new LambdaQueryWrapper<>();
//没有停用
queryWrapper.ne(GatewayDevice::getStatus, "00");
queryWrapper.eq(GatewayDevice::getDeviceType, "30");
queryWrapper.eq(GatewayDevice::getProtocol, "IEC104");
queryWrapper.select(GatewayDevice::getId, GatewayDevice::getDeviceCode, GatewayDevice::getDeviceName,
GatewayDevice::getIpAddr, GatewayDevice::getIecAddr);
queryWrapper.orderByAsc(GatewayDevice::getDeviceCode);
List<GatewayDevice> list = gatewayDeviceMapper.selectList(queryWrapper);
if (list.size() > 0) {
for (GatewayDevice gatewayDevice : list) {
Iec104Config iec104Config = new Iec104Config();
iec104Config.setFrameAmountMax((short) 10);
short terminnalAddress = gatewayDevice.getIecAddr().shortValue();
//通讯网关机地址
iec104Config.setTerminnalAddress(terminnalAddress);
iec104Config.setSlaveCode(gatewayDevice.getDeviceCode());
iec104Config.setSlaveIP(gatewayDevice.getIpAddr());
Runnable runnable = () -> {
Iec104Master iec104server = Iec104MasterFactory.createTcpClientMaster(iec104Config.getSlaveIP(),
2404);
try {
iec104server.setDataHandler(new MasterSysDataHandler()).setConfig(iec104Config).run();
} catch (Exception e) {
log.error(String.format("%s-对应的终端连接失败!", iec104Config.getSlaveIP()));
}
};
CachedThreadPool.getCachedThreadPool().execute(runnable);
}
}
}
}

View File

@ -0,0 +1,91 @@
package com.yfd.platform.component.iec104.client;
import cn.hutool.core.util.ObjUtil;
import com.ydl.iec.util.ByteUtil;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.message.MessageInfo;
import com.yfd.platform.component.iec104.server.handler.ChannelHandler;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannel;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannelCache;
import com.yfd.platform.modules.auxcontrol.service.IDeviceAlarmRecordService;
import com.yfd.platform.modules.auxcontrol.service.IDeviceSignalService;
import com.yfd.platform.modules.auxcontrol.service.IDeviceWorkDataService;
import com.yfd.platform.modules.auxcontrol.service.IGatewayDeviceService;
import com.yfd.platform.utils.SpringContextHolder;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* @author Admin
*/
@io.netty.channel.ChannelHandler.Sharable
public class MasterSysDataHandler implements DataHandler {
private static final Logger log = LoggerFactory.getLogger(MasterSysDataHandler.class);
IDeviceSignalService signalService = SpringContextHolder.getBean(IDeviceSignalService.class);
IDeviceWorkDataService workDataService = SpringContextHolder.getBean(IDeviceWorkDataService.class);
IGatewayDeviceService gatewayDeviceService = SpringContextHolder.getBean(IGatewayDeviceService.class);
IDeviceAlarmRecordService alarmRecordService = SpringContextHolder.getBean(IDeviceAlarmRecordService.class);
@Override
public void handlerAdded(ChannelHandler ctx) throws Exception {
ChannelHandlerContext currentctx = ctx.getCtx();
BootNettyClientChannel channel = BootNettyClientChannelCache.get(currentctx.channel().id().asShortText());
String slaveIp = channel.getCode();
log.info(String.format("%s-对应的终端连接成功!", slaveIp));
}
@Override
public void channelRead(ChannelHandler ctx, MessageDetail ruleDetail104) throws Exception {
ChannelHandlerContext currentctx = ctx.getCtx();
//------------从通道中获取当前连接的IP-----------------//
BootNettyClientChannel channel = BootNettyClientChannelCache.get(currentctx.channel().id().asShortText());
if (ObjUtil.isEmpty(channel)) {
//通道已关闭
} else {
String slaveIp = channel.getCode();
String hexString = ByteUtil.byteArrayToHexString(ruleDetail104.getControl());
if ("83000000".equals(hexString)) {
//测试确认更新心跳时间
gatewayDeviceService.updateKeepLiveTime(slaveIp);
return;
}
int type = ruleDetail104.getTypeIdentifier().getValue();
if (type == 1 || type == 3 || type == 30 || type == 31) {
//遥信值
List<MessageInfo> list = ruleDetail104.getMessages();
for (MessageInfo messageInfo : list) {
String address = String.valueOf(messageInfo.getMessageAddress());
String status = String.valueOf(messageInfo.getStatus());
String dateTime = messageInfo.getTimeScale();
signalService.updateDeviceSignalValue(slaveIp, address, "yx", status, dateTime);
//生成设备自身报警记录status=1
alarmRecordService.doAlaramRecord("IEC104", "yx", slaveIp, address, status);
}
}
if (type == 9 || type == 11 || type == 13 || type == 34 || type == 35 || type == 36) {
//遥测值
List<MessageInfo> list = ruleDetail104.getMessages();
for (MessageInfo messageInfo : list) {
String address = String.valueOf(messageInfo.getMessageAddress() - 16384);
//从设备上去的地址减去
// 4000H对应的偏差值16384
String value = String.valueOf(messageInfo.getValue());
String dateTime = messageInfo.getTimeScale();
workDataService.insertData("IEC104", slaveIp, address, value, dateTime);
signalService.updateDeviceSignalValue(slaveIp, address, "yc", value, dateTime);
//生成阈值越限报警记录
alarmRecordService.doAlaramRecord("IEC104", "yc", slaveIp, address, value);
}
}
}
}
}

View File

@ -0,0 +1,230 @@
package com.yfd.platform.component.iec104.common;
import cn.hutool.core.util.TypeUtil;
import com.yfd.platform.component.iec104.core.Encoder104;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.message.MessageInfo;
import com.yfd.platform.component.iec104.enums.QualifiersEnum;
import com.yfd.platform.component.iec104.enums.TypeIdentifierEnum;
import com.yfd.platform.component.iec104.enums.UControlEnum;
import com.ydl.iec.util.ByteUtil;
import com.ydl.iec.util.Iec104Util;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* 104 规约的基本指令封装
* @ClassName: BasicInstruction104
* @Description: 返回指定的指令
* @author YDL
*/
public class BasicInstruction104 {
// 68040B 00 00 00
/**
* 链路启动指令
*/
public static final byte[] STARTDT = new byte[] {0x68, 0x04, 0x07, 0x00, 0x00, 0x00};
/**
* 链路启动确认指令
*/
public static final byte[] STARTDT_YES = new byte[] {0x68, 0x04, 0x0B, 0x00, 0x00, 0x00};
/**
* 测试命令指令
*/
public static final byte[] TESTFR = new byte[] {0x68, 0x04, (byte) 0x43, 0x00, 0x00, 0x00};
/**
* 测试回复确认
*/
public static final byte[] TESTFR_YES = new byte[] {0x68, 0x04, (byte) 0x83, 0x00, 0x00, 0x00};
/**
* 停止确认
*/
public static final byte[] STOPDT_YES = new byte[] {0x68, 0x04, 0x23, 0x00, 0x00, 0x00};
/**
*
* @Title: getGeneralCallRuleDetail104
* @Description: 总召唤指令
* @param @return
* @param @throws IOException
* @return MessageDetail
* @throws
*/
public static MessageDetail getGeneralCallRuleDetail104() throws IOException {
TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
int sq = 0;
boolean isContinuous = sq == 0 ? false : true;
// 接收序号
short accept = 0;
// 发送序号
short send = 0;
byte[] control = Iec104Util.getIcontrol(accept, send);
// 传输原因
short transferReason = 6;
boolean isTest = false;
boolean isPn = true;
// 终端地址 实际发生的时候会被替换
short terminalAddress = 1;
// 消息地址 总召唤地址为0
int messageAddress = 0;
QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
List<MessageInfo> messages = new ArrayList<>();
MessageInfo messageInfo=new MessageInfo();
byte[] message=new byte[]{0x00, 0x00, 0x00, 0x00};
messageInfo.setMessageInfos(message);
messages.add(messageInfo);
MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPn, transferReason,
terminalAddress, messageAddress, messages, null, qualifiers);
byte[] bytes = Encoder104.encoder(ruleDetail104);
ruleDetail104.setHexString(ByteUtil.byteArrayToHexString(bytes));
return ruleDetail104;
}
/**
*
* @Title: getYesGeneralCallRuleDetail104
* @Description: 总召唤确认指令
* @return
* @return MessageDetail
* @throws
*/
public static MessageDetail getYesGeneralCallRuleDetail104() {
TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
//SQ=0 length =1
int sq = 0;
boolean isContinuous = sq == 0 ? false : true;
// 接收序号
short accept = 0;
// 发送序号
short send = 0;
byte[] control = Iec104Util.getIcontrol(accept, send);
// 传输原因
short transferReason = 7;
// true1 ; false 0
boolean isTest = false;
// true:0 false;1
boolean isPN = true;
short terminalAddress = 1;
// 消息地址 总召唤地址为0
int messageAddress = 0;
QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
List<MessageInfo> messages = new ArrayList<>();
MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPN, transferReason,
terminalAddress, messageAddress, messages, null, qualifiers);
return ruleDetail104;
}
/**
*
* @Title: getEndGeneralCallRuleDetail104
* @Description: 总召唤结束指令
* @return
* @return MessageDetail
* @throws
*/
public static MessageDetail getEndGeneralCallRuleDetail104() {
TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.generalCall;
//SQ=0 length =1
int sq = 0;
boolean isContinuous = sq == 0 ? false : true;
// 接收序号
short accept = 1;
// 发送序号
short send = 4;
byte[] control = Iec104Util.getIcontrol(accept, send);
// 传输原因
short transferReason = 0x0A;
// true1 ; false 0
boolean isTest = false;
// true:0 false;1
boolean isPN = true;
short terminalAddress = 1;
// 消息地址 总召唤地址为0
int messageAddress = 0;
// 老板限定词
QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
List<MessageInfo> messages = new ArrayList<>();
MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPN, transferReason,
terminalAddress, messageAddress, messages, null, qualifiers);
return ruleDetail104;
}
public static MessageDetail getInitRuleDetail104() {
byte[] control = ByteUtil.intToByteArray(UControlEnum.STARTDT.getValue());
MessageDetail ruleDetail104 = new MessageDetail(control);
return ruleDetail104;
}
/**
*
* @Title: gettTimeScale104
* @Description: 对时
* @param @return
* @param @throws IOException
* @return MessageDetail
* @throws
*/
public static MessageDetail getTimeScale104() {
// 接收序号
short accept = 0;
// 发送序号
short send = 0;
//控制欲
byte[] control = Iec104Util.getIcontrol(accept, send);
// 消息地址 总召唤地址为0
int messageAddress = 0;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Calendar calendar = Calendar.getInstance();
Date timeScale = calendar.getTime();
//对时没有限定词
// QualifiersEnum qualifiers = QualifiersEnum.generalCallGroupingQualifiers;
List<MessageInfo> messages = new ArrayList<>();
MessageInfo message = new MessageInfo();
message.setMessageInfos(new byte[] {0x01});
//类型标识 对时同步
TypeIdentifierEnum typeIdentifierEnum = TypeIdentifierEnum.timeSynchronization;
int sq = 0;
//可变结构限定词 01
boolean isContinuous = sq == 0 ? false : true;
// 传输原因
short transferReason = 6;
// 终端地址 实际发生的时候会被替换 公共地址
short terminalAddress = 1;
boolean isTest = false;
boolean isPn = true;
message.setMessageAddress(messageAddress);
messages.add(message);
MessageDetail ruleDetail104 = new MessageDetail(control, typeIdentifierEnum, isContinuous, isTest, isPn, transferReason,
terminalAddress, messageAddress, messages, timeScale,null);
return ruleDetail104;
}
}

View File

@ -0,0 +1,49 @@
package com.yfd.platform.component.iec104.common;
/**
*
* @ClassName: Iec104Constant
* @Description: TODO
* @author YDL
*/
public class Iec104Constant {
/**
* 开始字符
*/
public static final byte HEAD_DATA = 0x68;
/**
* 控制域长度
*/
public static final byte CPNTROL_LENGTH = 0x04;
/**
* APCI 长度
*/
public static final byte APCI_LENGTH = 0x06;
/**
* APCI 发送序号低位坐标
*/
public static final int ACCEPT_LOW_INDEX = 2;
/**
* APCI 发送序号高位坐标
*/
public static final int ACCEPT_HIGH_INDEX = 3;
/**
*最大接收序号
*/
public static final Short SEND_MAX = 32767;
/**
* 最小接收序号
*/
public static final Short SEND_MIN = 0;
}

View File

@ -0,0 +1,17 @@
package com.yfd.platform.component.iec104.config;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 默认的配置
*/
@Data
@EqualsAndHashCode(callSuper=false)
public class DefaultIec104Config extends Iec104Config {
public DefaultIec104Config() {
setFrameAmountMax((short) 1);
setTerminnalAddress((short) 1);
}
}

View File

@ -0,0 +1,33 @@
package com.yfd.platform.component.iec104.config;
import lombok.Data;
/**
* 104规约的配置
*/
@Data
public class Iec104Config {
/**
* 接收到帧的数量到该值就要发一个确认帧
*/
private short frameAmountMax;
/**
* 终端地址
*/
private short terminnalAddress;
/**
* 终端IP
*/
private String slaveIP;
/**
* 终端编号
*/
private String slaveCode;
}

View File

@ -0,0 +1,26 @@
package com.yfd.platform.component.iec104.core;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池
*/
public final class CachedThreadPool {
private static CachedThreadPool cachedThreadPool = new CachedThreadPool();
private ExecutorService executorService;
private CachedThreadPool() {
executorService = Executors.newCachedThreadPool();
}
public static CachedThreadPool getCachedThreadPool() {
return cachedThreadPool;
}
public void execute(Runnable runnable) {
executorService.execute(runnable);
}
}

View File

@ -0,0 +1,123 @@
package com.yfd.platform.component.iec104.core;
import com.yfd.platform.component.iec104.common.Iec104Constant;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.ydl.iec.util.Iec104Util;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* 控制域的管理工具
*/
public class ControlManageUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
/**
* 发送序号
*/
private Short send;
/**
* 接收序号
*/
private Short accept;
/**
* 接收到帧的数量
*/
private Short frameAmount;
/**
* 发送S帧的锁
*/
private Boolean sendSframeLock;
/**
* 接收到帧的数量最大阀值
*/
private short frameAmountMax;
/**
* 发送消息句柄
*/
private ChannelHandlerContext ctx;
public ControlManageUtil(ChannelHandlerContext ctx) {
send = 0;
accept = 0;
frameAmount = 0;
sendSframeLock = true;
frameAmountMax = 1;
this.ctx = ctx;
}
/**
* 启动S发送S确认帧 的任务
*/
public void startSendFrameTask() {
Runnable runnable = () -> {
while (true) {
try {
synchronized (sendSframeLock) {
if (frameAmount >= frameAmountMax) {
// 查过最大帧 的数量就要发送一个确认帧出去
byte[] control = Iec104Util.getScontrol(accept);
MessageDetail ruleDetail104 = new MessageDetail(control);
ctx.channel().writeAndFlush(Encoder104.encoder(ruleDetail104));
frameAmount = 0;
}
sendSframeLock.wait();
}
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
}
};
CachedThreadPool.getCachedThreadPool().execute(runnable);
}
/**
* 返回当前的发送序号
*/
public short getSend() {
synchronized (send) {
short sendRule = this.send;
this.send++;
if (send > Iec104Constant.SEND_MAX) {
send = Iec104Constant.SEND_MIN;
}
return sendRule;
}
}
public short getAccept() {
return accept;
}
/**
*
* @Title: setAccept
* @Description: 设置接收序号
* @param lastAccept
*/
public void setAccept(short lastAccept) {
synchronized (sendSframeLock) {
this.accept = lastAccept;
frameAmount++;
if (frameAmount >= frameAmountMax) {
this.accept = lastAccept;
sendSframeLock.notifyAll();
}
}
}
public ControlManageUtil setFrameAmountMax(short frameAmountMax) {
this.frameAmountMax = frameAmountMax;
return this;
}
}

View File

@ -0,0 +1,305 @@
package com.yfd.platform.component.iec104.core;
import com.ydl.iec.util.Iec104Util;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.message.MessageInfo;
import com.yfd.platform.component.iec104.enums.QualifiersEnum;
import com.yfd.platform.component.iec104.enums.TypeIdentifierEnum;
import com.ydl.iec.util.ByteUtil;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
*
* @ClassName: Decoder104
* @Description: 104 协议转码工具
* @author YDL
* @date 2020年5月13日
*/
public class Decoder104 {
/**
* 将bytes 转换成指定的数据结构
* @param bytes
* @return
*/
public static MessageDetail encoder(byte[] bytes) {
MessageDetail ruleDetail104 = new MessageDetail();
int index = 0;
ruleDetail104.setStart(bytes[index++]);
ruleDetail104.setApuuLength(bytes[index++] & 0xFF);
ruleDetail104.setControl(ByteUtil.getByte(bytes, index, 4));
index += 4;
if (ruleDetail104.getApuuLength() <= 4) {
ruleDetail104.setMessages(new ArrayList<>());
// 如果只有APCI 就此返回
return ruleDetail104;
}
// 下面是返回ASDU的结构
ruleDetail104.setTypeIdentifier(TypeIdentifierEnum.getTypeIdentifierEnum(bytes[index++]));
// 添加可变结构限定词
Iec104Util.setChanged(ruleDetail104, bytes[index++]);
ruleDetail104.setTransferReason(ByteUtil.byteArrayToShort(ByteUtil.getByte(bytes, index, 2)));
index += 2;
//
ruleDetail104.setTerminalAddress(Iec104Util.getTerminalAddressShort(ByteUtil.getByte(bytes, index, 2)));
index += 2;
Iec104Util.setMeaageAttribute(ruleDetail104);
setMessage(ruleDetail104, bytes, index);
return ruleDetail104;
}
/**
*
* @Title: setMessage
* @Description: 对消息进行编码
* @param @param ruleDetail104
* @param @param bytes
* @param @param index
* @return void
* @throws
*/
public static void setMessage(MessageDetail ruleDetail104, byte[] bytes, int index) {
int mesageIndex = index;
if (ruleDetail104.isContinuous()) {
setContinuoustMessage(ruleDetail104, bytes, mesageIndex);
} else {
setNoContinuoustMessage(ruleDetail104, bytes, mesageIndex);
}
}
/**
*
* @Title: setContinuoustMessage
* @Description: 设置连续地址的消息
* @param ruleDetail104
* @param bytes
* @param index
* @return void
* @throws
*/
public static void setContinuoustMessage(MessageDetail ruleDetail104, byte[] bytes, int index) {
List<MessageInfo> messages = new ArrayList<>();
int mesageIndex = index;
// 连续的 前三个字节是地址
// 如果是地址联系的只需要设置一个初始地址就可以了
// TODO 此处不处理地址
int messageAddress = Iec104Util.messageAddressToInt(ByteUtil.getByte(bytes, mesageIndex, 3));
ruleDetail104.setMessageAddress(messageAddress);
mesageIndex += 3;
if (ruleDetail104.isMessage()) {
// 获取每个消息的长度
int messageLength = getMessageLength(ruleDetail104);
int messageSize = 0;
while (messageSize < ruleDetail104.getMeasgLength()) {
MessageInfo messageObj = new MessageInfo();
messageObj.setMessageAddress(messageAddress);
byte[] messageInfos = ByteUtil.getByte(bytes, mesageIndex, messageLength);
mesageIndex += messageLength;
messageObj.setMessageInfos(messageInfos);
//对数据的值进行解析
setMessageValue(ruleDetail104, messageObj);
if (ruleDetail104.isQualifiers() && TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
// 判断是否有限定词
// 0每个信息元素后缀1个字节
ruleDetail104.setQualifiersType(QualifiersEnum.getQualifiersEnum(ruleDetail104.getTypeIdentifier(), bytes[mesageIndex++]));
}
messageSize++;
messageAddress++;
messages.add(messageObj);
}
}
if (ruleDetail104.isQualifiers() && !TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
// 判断是否有限定词
ruleDetail104.setQualifiersType(QualifiersEnum.getQualifiersEnum(ruleDetail104.getTypeIdentifier(), bytes[mesageIndex++]));
}
if (ruleDetail104.isTimeScaleExit()) {
ruleDetail104.setTimeScale(ByteUtil.byte2Hdate(ByteUtil.getByte(bytes, mesageIndex, 7)));
// 创建一个 SimpleDateFormat 对象指定日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// 使用 SimpleDateFormat 对象将 Date 对象格式化为字符串
String dateString = sdf.format(ruleDetail104.getTimeScale());
for (MessageInfo message : messages) {
message.setTimeScale(dateString);
}
}
ruleDetail104.setMessages(messages);
}
/**
*
* @Title: setNoContinuoustMessage
* @Description: 设置不连续地址的消息
* @param ruleDetail104
* @param bytes
* @param index
* @return void
* @throws
*/
public static void setNoContinuoustMessage(MessageDetail ruleDetail104, byte[] bytes, int index) {
List<MessageInfo> messages = new ArrayList<>();
int mesageIndex = index;
// 获取每个消息的长度
int messageLength = getMessageLength(ruleDetail104);
int messageSize = 0;
while (messageSize < ruleDetail104.getMeasgLength()) {
MessageInfo messageObj = new MessageInfo();
// 消息地址
messageObj.setMessageAddress(Iec104Util.messageAddressToInt(ByteUtil.getByte(bytes, mesageIndex, 3)));
mesageIndex += 3;
if (ruleDetail104.isMessage()) {
// 消息集合
byte[] messageInfos = ByteUtil.getByte(bytes, mesageIndex, messageLength);
mesageIndex += messageLength;
messageObj.setMessageInfos(messageInfos);
//对数据的值进行解析
setMessageValue(ruleDetail104, messageObj);
} else {
messageObj.setMessageInfos(new byte[] {});
}
if (ruleDetail104.isQualifiers() && TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
// 判断是否有限定词
// 0每个信息元素后缀1个字节
ruleDetail104.setQualifiersType(QualifiersEnum.getQualifiersEnum(ruleDetail104.getTypeIdentifier(), bytes[mesageIndex++]));
}
messageSize++;
messages.add(messageObj);
}
if (ruleDetail104.isQualifiers() && !TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
// 判断是否有限定词
ruleDetail104.setQualifiersType(QualifiersEnum.getQualifiersEnum(ruleDetail104.getTypeIdentifier(), bytes[mesageIndex++]));
}
if (ruleDetail104.isTimeScaleExit()) {
ruleDetail104.setTimeScale(ByteUtil.byte2Hdate(ByteUtil.getByte(bytes, mesageIndex, 7)));
// 创建一个 SimpleDateFormat 对象指定日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// 使用 SimpleDateFormat 对象将 Date 对象格式化为字符串
String dateString = sdf.format(ruleDetail104.getTimeScale());
for (MessageInfo message : messages) {
message.setTimeScale(dateString);
}
}
ruleDetail104.setMessages(messages);
}
/**
* 根据类型对数据的值进行解析
* */
private static void setMessageValue(MessageDetail ruleDetail104, MessageInfo messageObj) {
switch (ruleDetail104.getTypeIdentifier().getValue()) {
case 0x01:
messageObj.setStatus(messageObj.getMessageInfos()[0] & 0xFF);
break;
case 0x03:
int value3=messageObj.getMessageInfos()[0] & 0xFF;
if(value3==2)
{
messageObj.setStatus(1);
}
else if(value3==1){
messageObj.setStatus(0);
}
else{
messageObj.setStatus(9);//未确定
}
break;
case 0x09:
// 遥测 测量值 归一化值 遥测
byte[] newbyte9=new byte[2];
newbyte9[0]=messageObj.getMessageInfos()[1];
newbyte9[1]=messageObj.getMessageInfos()[0];
float value9 =ByteUtil.byteArrayToShort(newbyte9);
messageObj.setValue(value9);
break;
case 0x0B:
// 遥测 测量值 标度化值 遥测
byte[] newbyte11=new byte[2];
newbyte11[0]=messageObj.getMessageInfos()[1];
newbyte11[1]=messageObj.getMessageInfos()[0];
float value11 = ByteUtil.byteArrayToShort(newbyte11);
messageObj.setValue(value11);
break;
case 0x0D:
// 遥测 测量值 短浮点数 遥测
float value13 = Float.intBitsToFloat((messageObj.getMessageInfos()[0] & 0xff) | ((messageObj.getMessageInfos()[1] & 0xff) << 8) | ((messageObj.getMessageInfos()[2] & 0xff) << 16) | ((messageObj.getMessageInfos()[3] & 0xff) << 24));
messageObj.setValue(value13);
break;
case 0x1E:
messageObj.setStatus(messageObj.getMessageInfos()[0] & 0xFF);
break;
case 0x1F:
int value31=messageObj.getMessageInfos()[0] & 0xFF;
if(value31==2)
{
messageObj.setStatus(1);
}
else if(value31==1){
messageObj.setStatus(0);
}
else{
messageObj.setStatus(9);//未确定
}
break;
case 0x22:
// 遥测 测量值 归一化值 带时标
byte[] newbyte34=new byte[2];
newbyte34[0]=messageObj.getMessageInfos()[1];
newbyte34[1]=messageObj.getMessageInfos()[0];
messageObj.setValue(ByteUtil.byteArrayToShort(newbyte34));
break;
case 0x23:
// 遥测 测量值 标度化值 带时标
byte[] newbyte35=new byte[2];
newbyte35[0]=messageObj.getMessageInfos()[1];
newbyte35[1]=messageObj.getMessageInfos()[0];
messageObj.setValue(ByteUtil.byteArrayToShort(newbyte35));
break;
case 0x24:
// 遥测 测量值 短浮点数 遥测
float value36 = Float.intBitsToFloat((messageObj.getMessageInfos()[0] & 0xff) | ((messageObj.getMessageInfos()[1] & 0xff) << 8) | ((messageObj.getMessageInfos()[2] & 0xff) << 16) | ((messageObj.getMessageInfos()[3] & 0xff) << 24));
messageObj.setValue(value36);
break;
case 0x66:
// 读单个参数
break;
case (byte) 0x84:
// 读多个参数
break;
case 0x30:
// 预置单个参数命令
break;
case (byte) 0x88:
// 预置多个个参数
break;
default :
}
}
/**
* 根据类型标识返回消息长度
*/
private static int getMessageLength(MessageDetail ruleDetail104) {
return ruleDetail104.getTypeIdentifier().getMessageLength();
}
}

View File

@ -0,0 +1,94 @@
package com.yfd.platform.component.iec104.core;
import com.yfd.platform.component.iec104.enums.QualifiersEnum;
import com.yfd.platform.component.iec104.enums.TypeIdentifierEnum;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.message.MessageInfo;
import com.ydl.iec.util.ByteUtil;
import com.ydl.iec.util.Iec104Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
*
* @author Admin
*
*/
public class Encoder104 {
public static byte[] encoder(MessageDetail ruleDetail104) throws IOException {
Iec104Util.setMeaageAttribute(ruleDetail104);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bytes.write(ruleDetail104.getStart());
byte[] apduBytes = getApduBytes(ruleDetail104);
int messageLen = apduBytes.length;
ruleDetail104.setApuuLength(messageLen);
bytes.write((byte) messageLen);
bytes.write(apduBytes);
return bytes.toByteArray();
}
private static byte[] getApduBytes(MessageDetail ruleDetail104) throws IOException {
ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
// 控制域
bOutput.write(ruleDetail104.getControl());
if (ruleDetail104.getTypeIdentifier() == null) {
// U帧或者S帧
return bOutput.toByteArray();
}
// 类型标识
bOutput.write((byte) ruleDetail104.getTypeIdentifier().getValue());
// 可变结构限定词
bOutput.write(Iec104Util.getChangedQualifiers(ruleDetail104));
// 传输原因
bOutput.write(ByteUtil.shortToByteArray(ruleDetail104.getTransferReason()));
// 终端地址
bOutput.write((Iec104Util.getTerminalAddressByte(ruleDetail104.getTerminalAddress())));
// 如果是是连续的则数据地址 只需要在开头写以后的数据单元就不需要再写了
if (ruleDetail104.isContinuous()) {
bOutput.write(Iec104Util.intToMessageAddress(ruleDetail104.getMessageAddress()));
// 地址只取三个字节
if (ruleDetail104.isMessage()) {
for (MessageInfo ruleDetail104Message : ruleDetail104.getMessages()) {
bOutput.write(ruleDetail104Message.getMessageInfos());
if (ruleDetail104.isQualifiers() && TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
// 0每个信息元素后缀1个字节
bOutput.write(ruleDetail104.getQualifiersType().getValue());
}
}
}
if (ruleDetail104.isQualifiers() && !TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
bOutput.write(ruleDetail104.getQualifiersType().getValue());
}
if (ruleDetail104.isTimeScaleExit()) {
bOutput.write(ByteUtil.date2Hbyte(ruleDetail104.getTimeScale()));
}
} else {
for (MessageInfo ruleDetail104Message : ruleDetail104.getMessages()) {
bOutput.write(Iec104Util.intToMessageAddress(ruleDetail104Message.getMessageAddress()));
if (ruleDetail104.isMessage()) {
bOutput.write(ruleDetail104Message.getMessageInfos());
}
if (ruleDetail104.isQualifiers() && TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
// 0每个信息元素后缀1个字节
bOutput.write(ruleDetail104.getQualifiersType().getValue());
}
}
if (ruleDetail104.isQualifiers() && !TypeIdentifierEnum.isTelemetry(ruleDetail104.getTypeIdentifier())) {
bOutput.write(ruleDetail104.getQualifiersType().getValue());
}
if (ruleDetail104.isTimeScaleExit()) {
bOutput.write(ByteUtil.date2Hbyte(ruleDetail104.getTimeScale()));
}
}
return bOutput.toByteArray();
}
}

View File

@ -0,0 +1,59 @@
package com.yfd.platform.component.iec104.core;
import com.yfd.platform.component.iec104.config.Iec104Config;
/**
* @ClassName: Iec104ThreadLocal
* @Description: 线程变量管理
* @author: YDL
* @date: 2020年5月19日 上午10:48:57
*/
public class Iec104ThreadLocal {
/**
* 定时发送启动链指令 测试链指令
*/
private static ThreadLocal<ScheduledTaskPool> scheduledTaskPoolThreadLocal = new ThreadLocal<>();
/**
* 返回 发送序号 和接收序号 定时发送S帧
*/
private static ThreadLocal<ControlManageUtil> controlPoolThreadLocal = new ThreadLocal<>();
/**
* 存放相关配置文件
*/
private static ThreadLocal<Iec104Config> iec104ConfigThreadLocal = new ThreadLocal<>();
public static void setScheduledTaskPool(ScheduledTaskPool scheduledTaskPool) {
scheduledTaskPoolThreadLocal.set(scheduledTaskPool);
}
public static ScheduledTaskPool getScheduledTaskPool() {
return scheduledTaskPoolThreadLocal.get();
}
public static void setControlPool(ControlManageUtil controlPool) {
controlPoolThreadLocal.set(controlPool);
}
public static ControlManageUtil getControlPool() {
return controlPoolThreadLocal.get();
}
public static Iec104Config getIec104Conig() {
return iec104ConfigThreadLocal.get();
}
public static void setIec104Config(Iec104Config iec104Confiig) {
iec104ConfigThreadLocal.set(iec104Confiig);
}
}

View File

@ -0,0 +1,131 @@
package com.yfd.platform.component.iec104.core;
import cn.hutool.core.util.ObjUtil;
import com.yfd.platform.component.iec104.common.BasicInstruction104;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannel;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannelCache;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 这是一个定时任务管理池
* @ClassName: ScheduledTaskPool
* @Description:
* @author: YDL
* @date: 2020年5月19日 上午10:47:15
*/
@Slf4j
public class ScheduledTaskPool {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
/**
* 发送指令
*/
private ChannelHandlerContext ctx;
public ScheduledTaskPool(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
/**
*
* @Title: sendStatrFrame
* @Description: 发送启动帧
*/
public void sendStartFrame() {
try {
BootNettyClientChannel Channel= BootNettyClientChannelCache.get(ctx.channel().id().asShortText());
if(ObjUtil.isNotEmpty(Channel)){
ctx.channel().writeAndFlush(BasicInstruction104.STARTDT);
String slave_ip= Channel.getCode();
LOGGER.info(String.format("向从站[%s]发送启动链路指令!",slave_ip));
}
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
}
/**
*
* @Title: startTestLinkTask
* @Description: 启动发送测试帧任务
*/
public void startTestLinkTask() {
scheduler.scheduleAtFixedRate(this::sendTestFrame, 0, 30, TimeUnit.SECONDS);
}
/**
*
* @Title: sendTestFrame
* @Description: 发送测试帧
*/
private void sendTestFrame() {
Runnable runnable = () -> {
try {
BootNettyClientChannel Channel= BootNettyClientChannelCache.get(ctx.channel().id().asShortText());
if(ObjUtil.isNotEmpty(Channel)){
String slave_ip= Channel.getCode();
LOGGER.info(String.format("向从站[%s]发送测试链路指令!",slave_ip));
ctx.channel().writeAndFlush(BasicInstruction104.TESTFR);
//对时指令
ctx.channel().writeAndFlush(BasicInstruction104.getTimeScale104());
}
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
};
CachedThreadPool.getCachedThreadPool().execute(runnable);
}
/**
*
* @Title: startGeneralCallTask
* @Description: 启动总召唤任务
*/
public void startGeneralCallTask() {
scheduler.scheduleAtFixedRate(this::sendGeneralCall, 15, 45, TimeUnit.SECONDS);
}
private void sendGeneralCall() {
Runnable runnable = () -> {
try {
BootNettyClientChannel channel = BootNettyClientChannelCache.get(ctx.channel().id().asShortText());
if (ObjUtil.isNotEmpty(channel)) {
String slaveIp = channel.getCode();
MessageDetail messageDetail = BasicInstruction104.getGeneralCallRuleDetail104();
LOGGER.info(String.format("向从站[%s]发送总召唤指令[%s]", slaveIp, messageDetail.getHexString()));
ctx.channel().writeAndFlush(messageDetail);
}
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
};
CachedThreadPool.getCachedThreadPool().execute(runnable);
}
public void stopSendCommandTask() {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt(); // 保持中断状态
}
}
}

View File

@ -0,0 +1,89 @@
package com.yfd.platform.component.iec104.enums;
import lombok.Getter;
/**
*
* @ClassName: 限定词
* @author YDL
*/
public enum QualifiersEnum {
/**
* 总召唤限定词
*/
generalCallQualifiers(TypeIdentifierEnum.generalCall, 0x20),
/**
* 总召唤限定词 支持 老版的分组
*/
generalCallGroupingQualifiers(TypeIdentifierEnum.generalCall, 0x14),
/**
* 复位进程限定词
*/
resetPprocessQualifiers(TypeIdentifierEnum.resetPprocess, 0x01),
/**
* 初始化原因 当地电源合上
*/
localCloseUpQualifiers(TypeIdentifierEnum.initEnd, 0x00),
/**
* 初始化原因 当地手动复位
*/
localMmanualResetQualifiers(TypeIdentifierEnum.initEnd, 0x01),
/**
* 远方复位
*/
distanceResetQualifiers(TypeIdentifierEnum.initEnd, 0x02),
/**
* 品质描述词 遥测
*/
qualityQualifiers(null, 0x00),
/**
* 设置命令限定词 选择预置参数 1000 0000
*/
prefabParameterQualifiers(null, 0x40),
/**
* 设置命令限定词 执行激活参数
*/
activationParameterQualifiers(null, 0x0F);
@Getter
private byte value;
@Getter
private TypeIdentifierEnum typeIdentifier;
QualifiersEnum(TypeIdentifierEnum typeIdentifier, int value) {
this.value = (byte) value;
this.typeIdentifier = typeIdentifier;
}
/**
* 根据传输类型和 限定词的关系返回 限定词的类型
* @param typeIdentifier
* @param value
* @return
*/
public static QualifiersEnum getQualifiersEnum(TypeIdentifierEnum typeIdentifier, byte value) {
for (QualifiersEnum type : QualifiersEnum.values()) {
if (type.getValue() == value && type.getTypeIdentifier() == typeIdentifier) {
return type;
}
}
// 品质描述词和设置参数 限定词对应多个值 所以需要做特殊处理
QualifiersEnum qualifiersEnum = null;
if ((TypeIdentifierEnum.normalizedTelemetry.equals(typeIdentifier)
|| TypeIdentifierEnum.scaledTelemetry.equals(typeIdentifier)
|| TypeIdentifierEnum.shortFloatingPointTelemetry.equals(typeIdentifier))
&& qualityQualifiers.getValue() == value) {
qualifiersEnum = qualityQualifiers;
}
if ((TypeIdentifierEnum.readOneParameter.equals(typeIdentifier)
|| TypeIdentifierEnum.readMultipleParameter.equals(typeIdentifier)
|| TypeIdentifierEnum.prefabActivationMultipleParameter.equals(typeIdentifier))
&& qualityQualifiers.getValue() == value) {
qualifiersEnum = qualityQualifiers;
}
return qualifiersEnum;
}
}

View File

@ -0,0 +1,133 @@
package com.yfd.platform.component.iec104.enums;
import lombok.Getter;
/**
*
* @ClassName: TypeIdentifierEnum 类型标识
* @author YDL
* @date 2020年5月13日
*/
public enum TypeIdentifierEnum {
/**
* 单点 摇信 1
*/
onePointTeleindication(0x01, 1),
/**
* 双点 摇信 2
*/
twoPointTeleindication(0x03, 1),
/**
* 测量值 归一化值 遥测 9
*/
normalizedTelemetry(0x09,2),
/**
* 测量值 标度化值 遥测 11
*/
scaledTelemetry(0x0B, 2),
/**
* 测量值 短浮点数 遥测 Short floating point 13
*/
shortFloatingPointTelemetry(0x0D, 4),
/**
* 摇信带时标 单点 30
*/
onePointTimeTeleindication(0x1E, 1),
/**
* 摇信带时标 双点 31
*/
twoPointTimeTeleindication(0x1F, 1),
/**
* 测量值 归一化值 带时标 遥测 9
*/
normalizedTimeTelemetry(0x22,2),
/**
* 测量值 标度化值 带时标 遥测 11
*/
scaledTimeTelemetry(0x23, 2),
/**
* 测量值 短浮点数 带时标 遥测 Short floating point 13
*/
shortFloatingPointTimeTelemetry(0x24, 4),
/**
* 单命令 遥控 45
*/
onePointTelecontrol(0x2D, 1),
/**
* 双命令遥控 46
*/
twoPointTelecontrol(0x2E, 1),
/**
* 归一化值 遥调 48
*
*/
normalizedTeleadjustment(0x30,2),
/**
* 标度化值 遥调 49
*/
scaledTeleadjustment(0x31, 2),
/**
* 短浮点数 遥调 50
*/
shortFloatingPointTeleadjustment(0x32, 4),
/**
* 读单个参数
*/
readOneParameter(0x66, 4),
/**
* 读多个参数
*/
readMultipleParameter(0x84, 4),
/**
* 预置多个个参数
*/
prefabActivationMultipleParameter(0x88, 4),
/**
* 初始化结束
*/
initEnd(0x46, 0),
/**
* 召唤命令
*/
generalCall(0x64, 0),
/**
* 时钟同步
*/
timeSynchronization(0x67, 0),
/**
* 复位进程
*/
resetPprocess(0x69, 0);
@Getter
private byte value;
@Getter
private int messageLength;
TypeIdentifierEnum(int value, int messageLength) {
this.value = (byte) value;
this.messageLength = messageLength;
}
public static TypeIdentifierEnum getTypeIdentifierEnum(byte value) {
for (TypeIdentifierEnum type : TypeIdentifierEnum.values()) {
if (type.getValue() == value) {
return type;
}
}
return null;
}
public static boolean isTelemetry(TypeIdentifierEnum typeIdentifierEnum) {
return TypeIdentifierEnum.normalizedTelemetry == typeIdentifierEnum
||TypeIdentifierEnum.scaledTelemetry == typeIdentifierEnum
||TypeIdentifierEnum.shortFloatingPointTelemetry == typeIdentifierEnum
||TypeIdentifierEnum.normalizedTimeTelemetry == typeIdentifierEnum
||TypeIdentifierEnum.scaledTimeTelemetry == typeIdentifierEnum
||TypeIdentifierEnum.shortFloatingPointTimeTelemetry == typeIdentifierEnum;
}
}

View File

@ -0,0 +1,43 @@
package com.yfd.platform.component.iec104.enums;
import lombok.Getter;
/**
* U帧 基本指令
*
*/
public enum UControlEnum {
/**
* 测试命令
*/
TESTFR(0x43000000),
/**
* 测试确认指令
*/
TESTFR_YES(0x83000000),
/**
* 停止指令
*/
STOPDT(0x13000000),
/**
* 停止确认
*/
STOPDT_YES(0x23000000),
/**
* 启动命令
*/
STARTDT(0x07000000),
/**
* 启动确认命令
*/
STARTDT_YES(0x0B000000);
@Getter
private int value;
UControlEnum(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,148 @@
package com.yfd.platform.component.iec104.message;
import com.ydl.iec.util.Iec104Util;
import com.yfd.platform.component.iec104.enums.QualifiersEnum;
import com.yfd.platform.component.iec104.enums.TypeIdentifierEnum;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 一条报文对应的消息体
* @author YDL
*/
@Data
public class MessageDetail {
/**
* 启动字符 固定 一个字节
*/
private byte start = 0x68;
/**
* APUU 长度1个字节
*/
private int apuuLength = 0;
/**
* 控制域 四个字节
*/
private byte[] control;
/**
* 类型标识 1字节
*/
private TypeIdentifierEnum typeIdentifier;
/**
* 可变结构限定词 1个字节
* true SQ = 0 true 数目number 信息对象的数目
* false SQ = 1 单个对象的信息元素或者信息元素的集合的数目
*/
private boolean isContinuous;
/**
* 消息长度
*/
private int measgLength;
/**
* 传输原因 两个字节
*/
private short transferReason;
/**
* 终端地址 也就是应用服务数据单元公共地址
*/
private short terminalAddress;
/**
* 消息地址 字节
*/
private int messageAddress;
/**
* 消息结构
*/
private List<MessageInfo> messages;
/**
* 判断是否有消息元素
*/
private boolean isMessage;
/**
* 判断是否有限定词
*/
private boolean isQualifiers;
/**
* 判断是否有时标
*/
private boolean isTimeScaleExit;
private QualifiersEnum qualifiersType;
/**
*
* 时标
*/
private Date timeScale;
/**
* 十六进制 字符串
*/
private String hexString;
public MessageDetail() {
}
/**
*
* @param control 控制域
* @param typeIdentifierEnum 类型标识
* @param sq 0 地址不连续 1 地址连续
* @param isTest 传输原因 0 未试验 1 试验
* @param isPn 肯定确认 和否定确认
* @param transferReason 传输原因 后六个比特位
* @param terminalAddress 服务地址
* @param messageAddress 消息地址
* @param messages 消息列表
* @param timeScale 时间
* @param qualifiers 限定词
* @return
*/
public MessageDetail(byte[] control, TypeIdentifierEnum typeIdentifierEnum, boolean sq,
boolean isTest, boolean isPn, short transferReason, short terminalAddress, int messageAddress,
List<MessageInfo> messages, Date timeScale, QualifiersEnum qualifiers) {
this.control = control;
this.typeIdentifier = typeIdentifierEnum;
this.isContinuous = sq;
this.measgLength = messages.size();
this.transferReason = Iec104Util.getTransferReasonShort(isTest, isPn, transferReason);
this.messages = messages;
this.terminalAddress = terminalAddress;
this.timeScale = timeScale;
if (isContinuous) {
// 只有连续地址才会在次设置地址
this.messageAddress = messageAddress;
}
if (timeScale != null) {
this.isTimeScaleExit = true;
}
this.qualifiersType = qualifiers;
}
/**
* U 帧或者S帧
* @param control 控制域
*/
public MessageDetail(byte[] control) {
this.control = control;
this.messages = new ArrayList<>();
}
}

View File

@ -0,0 +1,48 @@
package com.yfd.platform.component.iec104.message;
import com.yfd.platform.component.iec104.enums.QualifiersEnum;
import lombok.Data;
import java.util.Date;
/**
* 报文中 的消息部分
*/
@Data
public class MessageInfo {
/**
* 消息地址 字节
*/
private int messageAddress;
/**
* 信息元素集合 1 2 4 个字节
*/
private byte[] messageInfos;
/**
* 限定词
*/
private QualifiersEnum qualifiersType;
// /**
// *
// * 时标
// */
private String timeScale;
/**
* 消息详情
*/
private int messageInfoLength;
/**
* 遥信状态值
*/
private int status;
/**
* 遥测值
*/
private float value;
}

View File

@ -0,0 +1,33 @@
package com.yfd.platform.component.iec104.server;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
/**
* 主站抽象类
*/
public interface Iec104Master {
/**
* 服务启动方法
* @throws Exception
*/
void run() throws Exception;
/**
*
* @Title: setDataHandler
* @Description: 设置数据处理类
* @param dataHandler
*/
Iec104Master setDataHandler(DataHandler dataHandler);
/**
* 设置配置文件
* @param iec104Confiig
* @return
*/
Iec104Master setConfig(Iec104Config iec104Confiig);
}

View File

@ -0,0 +1,26 @@
package com.yfd.platform.component.iec104.server;
import com.yfd.platform.component.iec104.server.master.Iec104TcpClientMaster;
/**
* 主站 工厂类
* @ClassName: Iec104MasterFactory
* @Description: IEC104规约主站
* @author: YDL
* @date: 2020年5月19日 上午10:22:59
*/
public class Iec104MasterFactory {
/**
* @Title: createTcpClientMaster
* @Description: 创建一个TCM客户端的104主站
* @param host 从机地址
* @param port 端口
* @return
*/
public static Iec104Master createTcpClientMaster(String host, int port) {
return new Iec104TcpClientMaster(host, port);
}
}

View File

@ -0,0 +1,34 @@
package com.yfd.platform.component.iec104.server;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
/**
* 从站抽象类
*/
public interface Iec104Slave {
/**
*
* @Title: run
* @Description: 启动主机
* @throws Exception
*/
void run() throws Exception;
/**
*
* @Title: setDataHandler
* @Description: 设置数据处理类
* @param dataHandler
*/
Iec104Slave setDataHandler(DataHandler dataHandler);
/**
* 设置配置文件
* @param iec104Config
* @return
*/
Iec104Slave setConfig(Iec104Config iec104Config);
}

View File

@ -0,0 +1,24 @@
package com.yfd.platform.component.iec104.server;
import com.yfd.platform.component.iec104.server.slave.Iec104TcpServerSlave;
/**
*
* @ClassName: Iec104SlaveFactory
* @Description: 104从机工厂
* @author: YDL
* @date: 2020年5月19日 上午10:41:39
*/
public class Iec104SlaveFactory {
/**
*
* @Title: createTcpServerSlave
* @Description: 生产一个 iec104 协议TCP传输方式服务端做从机服务
* @param port 端口 从机端口
* @return Iec104Slave
*/
public static Iec104Slave createTcpServerSlave(int port) {
return new Iec104TcpServerSlave(port);
}
}

View File

@ -0,0 +1,21 @@
package com.yfd.platform.component.iec104.server.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* 数组编码器
* @author Admin
*
*/
public class BytesEncoder extends MessageToByteEncoder<byte[]> {
@Override
protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception {
out.writeBytes(msg);
}
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.message.MessageDetail;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @ClassName: ChannelHandler
* @Description: 处理数据
* @author: YDL
* @date: 2020年5月19日 上午11:41:58
*/
public interface ChannelHandler {
ChannelHandlerContext getCtx();
void writeAndFlush(MessageDetail ruleDetail104);
}

View File

@ -0,0 +1,29 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.message.MessageDetail;
import io.netty.channel.ChannelHandlerContext;
/**
*
* @ClassName: ChannelHandlerImpl
* @Description: 实现一个自定义发现消息的类
* @author: YDL
* @date: 2020年5月19日 上午11:47:16
*/
public class ChannelHandlerImpl implements ChannelHandler {
private ChannelHandlerContext ctx;
public ChannelHandlerImpl(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public ChannelHandlerContext getCtx() {
return this.ctx;
}
@Override
public void writeAndFlush(MessageDetail ruleDetail104) {
ctx.channel().writeAndFlush(ruleDetail104);
}
}

View File

@ -0,0 +1,51 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.common.Iec104Constant;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannel;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannelCache;
import com.ydl.iec.util.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @ClassName: Check104Handler
* @Description: 检查104报文
* @author YDL
* @date 2020年5月13日
*/
public class Check104Handler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
private static final int MAX_BUFFER_SIZE = 1024; // 缓存区大小
/**
* 拦截系统消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf result = (ByteBuf) msg;
// 检查ByteBuf中的数据长度是否超过最大缓冲区大小
if (result.readableBytes() > MAX_BUFFER_SIZE) {
LOGGER.error("接收的数据长度是否超过最大缓冲区大小!");
return;
}
byte[] bytes = new byte[result.readableBytes()];
result.readBytes(bytes);
BootNettyClientChannel Channel= BootNettyClientChannelCache.get(ctx.channel().id().asShortText());
String slave_ip= Channel.getCode();
LOGGER.info( String.format("接收到从站[%s]发送的报文: %s", slave_ip,ByteUtil.byteArrayToHexString(bytes) ));
if (bytes.length < Iec104Constant.APCI_LENGTH || bytes[0] != Iec104Constant.HEAD_DATA) {
LOGGER.error("报文无效");
ReferenceCountUtil.release(result);
} else {
result.writeBytes(bytes);
ctx.fireChannelRead(msg);
}
}
}

View File

@ -0,0 +1,34 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.ydl.iec.util.Iec104Util;
import com.yfd.platform.component.iec104.core.Decoder104;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.ydl.iec.util.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* 解码器
* @author Admin
*
*/
public class DataDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
byte[] data = new byte[in.readableBytes()];
in.readBytes(data);
short send = Iec104Util.getSend(ByteUtil.getByte(data, 2, 4));
Iec104ThreadLocal.getControlPool().setAccept(send);
MessageDetail ruleDetail104 = Decoder104.encoder(data);
out.add(ruleDetail104);
}
}

View File

@ -0,0 +1,48 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.ydl.iec.util.Iec104Util;
import com.yfd.platform.component.iec104.core.Encoder104;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.ydl.iec.util.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;
/**
* 编码器
* @author Admin
*
*/
@Slf4j
public class DataEncoder extends MessageToByteEncoder<MessageDetail> {
@Override
protected void encode(ChannelHandlerContext ctx, MessageDetail msg, ByteBuf out) throws Exception {
try {
byte[] bytes = Encoder104.encoder(msg);
short accept = Iec104ThreadLocal.getControlPool().getAccept();
short send = Iec104ThreadLocal.getControlPool().getSend();
short terminalAddress = Iec104ThreadLocal.getIec104Conig().getTerminnalAddress();
// 替换终端地址 发送序号和接收序号
byte[] terminalAddressBytes = Iec104Util.getTerminalAddressByte(terminalAddress);
byte[] icontrol = Iec104Util.getIcontrol(accept, send);
for (int i = 0; i < icontrol.length; i++) {
bytes[i + 2] = icontrol[i];
}
bytes[10] = terminalAddressBytes[0];
bytes[11] = terminalAddressBytes[1];
String hexString=ByteUtil.byteArrayToHexString(bytes);
log.info(hexString);
out.writeBytes(bytes);
} catch (Exception e) {
log.error("", e);
}
}
}

View File

@ -0,0 +1,32 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.message.MessageDetail;
/**
*
* @ClassName: DataHandler
* @Description: 数据处理
* @author: YDL
* @date: 2020年5月19日 上午11:27:04
*/
public interface DataHandler {
/**
* @param ctx
* @throws Exception
* @Title: handlerAdded
* @Description: 建立连接
*/
void handlerAdded(ChannelHandler ctx) throws Exception;
/**
* @param ctx
* @param ruleDetail104
* @throws Exception
* @Title: channelRead0
* @Description: 收到消息
*/
void channelRead(ChannelHandler ctx, MessageDetail ruleDetail104) throws Exception;
}

View File

@ -0,0 +1,62 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.common.Iec104Constant;
import com.ydl.iec.util.Iec104Util;
import com.ydl.iec.util.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @ClassName: SysSframeInboundHandler
* @Description: 处理S帧的问题
* @author YDL
* @date 2020年5月13日
*/
public class SysSframeHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
/**
* 拦截系统消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf result = (ByteBuf) msg;
byte[] bytes = new byte[result.readableBytes()];
result.readBytes(bytes);
if (isSysInstruction(bytes)) {
LOGGER.info("收到S帧" + Iec104Util.getAccept(ByteUtil.getByte(bytes, 2, 4)));
ReferenceCountUtil.release(result);
return;
}
result.writeBytes(bytes);
// LOGGER.info("普通指令");
ctx.fireChannelRead(result);
}
/**
*
* @Title: isSysInstruction
* @Description: TODO 判断是否是 系统报文
* @param @param bytes
* @param @return
* @return boolean
* @throws
*/
private boolean isSysInstruction(byte[] bytes) {
if (bytes.length != Iec104Constant.APCI_LENGTH) {
return false;
}
if (bytes[Iec104Constant.ACCEPT_LOW_INDEX] == 1 && bytes[Iec104Constant.ACCEPT_HIGH_INDEX] == 0) {
// 判断S帧的方法
return true;
}
// U帧只有6字节
return false;
}
}

View File

@ -0,0 +1,52 @@
package com.yfd.platform.component.iec104.server.handler;
import com.yfd.platform.component.iec104.common.Iec104Constant;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
/**
*
* @ClassName: Unpack104Util
* @Description: 解决TCP 拆包和沾包的问题
* @author YDL
* @date 2020年5月13日
*/
public class Unpack104Handler extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
// 记录包头开始的index
int beginReader;
int newDataLength = 0;
while (true) {
// 获取包头开始的index
beginReader = buffer.readerIndex();
// 记录一个标志用于重置
buffer.markReaderIndex();
// 读到了协议的开始标志结束while循环
if (buffer.readByte() == Iec104Constant.HEAD_DATA) {
// 标记当前包为新包
//读取包长度
byte newDataLengthByte = buffer.readByte();
newDataLength = newDataLengthByte & 0xFF;
break;
}
continue;
}
if (buffer.readableBytes() < newDataLength) {
buffer.readerIndex(beginReader);
return;
}
newDataLength = newDataLength + 2;
//恢复指针
buffer.readerIndex(beginReader);
ByteBuf data = buffer.readBytes(newDataLength);
out.add(data);
}
}

View File

@ -0,0 +1,62 @@
package com.yfd.platform.component.iec104.server.master;
import com.yfd.platform.component.iec104.core.ScheduledTaskPool;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
/**
* 建立channel保存多客户端BootNettyClientChannel
*/
public class BootNettyClientChannel {
// 连接客户端唯一的code
private String code;
// 客户端最新发送的消息内容
private String last_data;
private transient volatile Channel channel;
// 连接客户端资源组
private transient volatile EventLoopGroup group;
private transient volatile ScheduledTaskPool scheduledTaskPool;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public EventLoopGroup getGroup() {
return group;
}
public void setGroup(EventLoopGroup group) {
this.group = group;
}
public ScheduledTaskPool getScheduledTaskPool() {
return scheduledTaskPool;
}
public void setScheduledTaskPool(ScheduledTaskPool scheduledTaskPool) {
this.scheduledTaskPool = scheduledTaskPool;
}
public String getLast_data() {
return last_data;
}
public void setLast_data(String last_data) {
this.last_data = last_data;
}
}

View File

@ -0,0 +1,32 @@
package com.yfd.platform.component.iec104.server.master;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* TCP通道缓存
*/
public class BootNettyClientChannelCache {
public static volatile Map<String, BootNettyClientChannel> channelMapCache = new ConcurrentHashMap<String, BootNettyClientChannel>();
public static void add(String code, BootNettyClientChannel channel){
channelMapCache.put(code,channel);
}
public static BootNettyClientChannel get(String code){
return channelMapCache.get(code);
}
public static void remove(String code){
channelMapCache.remove(code);
}
public static void save(String code, BootNettyClientChannel channel) {
if(channelMapCache.get(code) == null) {
add(code,channel);
}
}
}

View File

@ -0,0 +1,40 @@
package com.yfd.platform.component.iec104.server.master;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import java.util.HashMap;
import java.util.Map;
public class EventLoopGroupManager {
private static final Map<String, EventLoopGroup> eventLoopGroups = new HashMap<>();
public static EventLoopGroup createEventLoopGroup(String key) {
EventLoopGroup group = new NioEventLoopGroup();
eventLoopGroups.put(key, group);
return group;
}
public static EventLoopGroup getEventLoopGroup(String key) {
EventLoopGroup group = eventLoopGroups.get(key);
return group;
}
public static void shutdownEventLoopGroup(String key) {
EventLoopGroup group = eventLoopGroups.get(key);
if (group != null) {
group.shutdownGracefully();
eventLoopGroups.remove(key);
}
}
// 如果需要手动关闭所有的EventLoopGroup
public static void shutdownAllEventLoopGroups() {
for (EventLoopGroup group : eventLoopGroups.values()) {
group.shutdownGracefully();
}
eventLoopGroups.clear();
}
}

View File

@ -0,0 +1,59 @@
package com.yfd.platform.component.iec104.server.master;
import com.yfd.platform.component.iec104.config.DefaultIec104Config;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.yfd.platform.component.iec104.server.handler.*;
import com.yfd.platform.component.iec104.server.master.handler.Iec104ClientHandler;
import com.yfd.platform.component.iec104.server.master.handler.SysUframeClientHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
*
* @ClassName: Iec104ServerInitializer
* @Description: 104协议 处理链
* @author YDL
* @date 2020年5月13日
*/
@Setter
@Accessors(chain = true)
public class Iec104ClientInitializer extends ChannelInitializer<SocketChannel> {
private DataHandler dataHandler;
private Iec104Config iec104Config;
/**
* 初始化处理链
*/
@Override
public void initChannel(SocketChannel ch) throws Exception {
if (iec104Config != null) {
Iec104ThreadLocal.setIec104Config(iec104Config);
} else {
Iec104ThreadLocal.setIec104Config(new DefaultIec104Config());
}
ChannelPipeline pipeline = ch.pipeline();
// 沾包拆包工具
pipeline.addLast("unpack", new Unpack104Handler());
// 数据检查工具
pipeline.addLast("check", new Check104Handler());
// /拦截 U帧处理器
pipeline.addLast("uframe", new SysUframeClientHandler());
//拦截 S帧处理器
pipeline.addLast("sframe", new SysSframeHandler());
//编码器
pipeline.addLast("byteencoder", new BytesEncoder());
//编码器
pipeline.addLast("encoder", new DataEncoder());
// 解码器
pipeline.addLast("decoder", new DataDecoder());
pipeline.addLast("handler", new Iec104ClientHandler(dataHandler));
}
}

View File

@ -0,0 +1,67 @@
package com.yfd.platform.component.iec104.server.master;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.server.Iec104Master;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
*
* @ClassName: Iec104TcpClientMaster
* @Description: 104 TCP 客户端 主机
* @author: YDL
* @date: 2020年5月19日 上午10:44:35
*/
public class Iec104TcpClientMaster implements Iec104Master {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
private int port;
private String host;
private DataHandler dataHandler;
private Iec104Config iec104Config;
private EventLoopGroup group;
public Iec104TcpClientMaster(String host, int port) {
this.port = port;
this.host = host;
this.group = EventLoopGroupManager.createEventLoopGroup(host);
}
@Override
public void run() throws Exception {
try {
Bootstrap bs = new Bootstrap();
bs.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new Iec104ClientInitializer().setDataHandler(dataHandler).setIec104Config(iec104Config));
Channel channel = bs.connect(host, port).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.error("Exception caught", e);
}
}
@Override
public Iec104Master setDataHandler(DataHandler dataHandler) {
this.dataHandler = dataHandler;
return this;
}
@Override
public Iec104Master setConfig(Iec104Config iec104Config) {
this.iec104Config = iec104Config;
return this;
}
}

View File

@ -0,0 +1,98 @@
package com.yfd.platform.component.iec104.server.master.handler;
import cn.hutool.core.util.ObjUtil;
import com.yfd.platform.component.iec104.core.CachedThreadPool;
import com.yfd.platform.component.iec104.core.ControlManageUtil;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.yfd.platform.component.iec104.core.ScheduledTaskPool;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.server.handler.ChannelHandlerImpl;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannel;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannelCache;
import com.yfd.platform.component.iec104.server.master.EventLoopGroupManager;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class Iec104ClientHandler extends SimpleChannelInboundHandler<MessageDetail> {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
private DataHandler dataHandler;
public Iec104ClientHandler(DataHandler dataHandler) {
this.dataHandler = dataHandler;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//------------获取当前激活的从站连接添加到全局列表中 -----------------//
BootNettyClientChannel Channel= new BootNettyClientChannel();
String slave_ip=Iec104ThreadLocal.getIec104Conig().getSlaveIP();
Channel.setCode(slave_ip);
Channel.setChannel(ctx.channel());
BootNettyClientChannelCache.save(ctx.channel().id().asShortText(),Channel);
//------------------------添加完成------------------------------//
// 启动成功后一直发启动链路命令
ScheduledTaskPool scheduledTaskPool=new ScheduledTaskPool(ctx) ;
Iec104ThreadLocal.setScheduledTaskPool(scheduledTaskPool);
Iec104ThreadLocal.getScheduledTaskPool().sendStartFrame();
Iec104ThreadLocal.setControlPool(new ControlManageUtil(ctx).setFrameAmountMax(Iec104ThreadLocal.getIec104Conig().getFrameAmountMax()));
Iec104ThreadLocal.getControlPool().startSendFrameTask();
Channel.setScheduledTaskPool(scheduledTaskPool);
BootNettyClientChannelCache.remove(ctx.channel().id().asShortText());
BootNettyClientChannelCache.save(ctx.channel().id().asShortText(),Channel);
if (dataHandler != null) {
CachedThreadPool.getCachedThreadPool().execute(new Runnable() {
@Override
public void run() {
try {
dataHandler.handlerAdded(new ChannelHandlerImpl(ctx));
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
}
});
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//关闭通道时移除
BootNettyClientChannel bootNettyChannel = BootNettyClientChannelCache.get(ctx.channel().id().asShortText());
if(ObjUtil.isNotEmpty(bootNettyChannel)){
bootNettyChannel.getScheduledTaskPool().stopSendCommandTask();
EventLoopGroupManager.shutdownEventLoopGroup(bootNettyChannel.getCode());
BootNettyClientChannelCache.remove(ctx.channel().id().asShortText());
LOGGER.info(bootNettyChannel.getCode()+"的连接被设备关闭!");
}
}
@Override
public void channelRead0(ChannelHandlerContext ctx, MessageDetail ruleDetail104) throws IOException {
if (dataHandler != null) {
CachedThreadPool.getCachedThreadPool().execute(new Runnable() {
@Override
public void run() {
try {
dataHandler.channelRead(new ChannelHandlerImpl(ctx), ruleDetail104);
} catch (Exception e) {
// TODO Auto-generated catch block
LOGGER.error("Exception caught", e);
}
}
});
}
}
}

View File

@ -0,0 +1,92 @@
package com.yfd.platform.component.iec104.server.master.handler;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.yfd.platform.component.iec104.enums.UControlEnum;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannel;
import com.yfd.platform.component.iec104.server.master.BootNettyClientChannelCache;
import com.ydl.iec.util.ByteUtil;
import com.ydl.iec.util.Iec104Util;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @ClassName: SysUframeInboundHandler
* @Description: 处理U帧的报文
* @author YDL
* @date 2020年5月13日
*/
public class SysUframeClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
/**
* 拦截系统消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf result = (ByteBuf) msg;
byte[] bytes = new byte[result.readableBytes()];
result.readBytes(bytes);
if (isSysInstruction(bytes)) {
UControlEnum uControlEnum = Iec104Util.getUcontrol(ByteUtil.getByte(bytes, 2, 4));
if (uControlEnum != null) {
uInstructionHandler(ctx, result, uControlEnum);
if(uControlEnum != UControlEnum.TESTFR_YES){//放行测试确认帧不拦截 zhengsl added at 20240517
return;
}
}
}
result.writeBytes(bytes);
ctx.fireChannelRead(result);
}
/**
*
* @Title: isSysInstruction
* @Description: TODO 判断是否是 系统报文
* @param @param bytes
* @param @return
* @return boolean
* @throws
*/
private boolean isSysInstruction(byte[] bytes) {
// U帧只有6字节
return bytes.length == 6;
}
/**
*
* @Title: uInstructionHandler
* @Description: 处理U帧
* @param ctx
* @param result
* @param uControlEnum
* @return void
* @throws
*/
private void uInstructionHandler(ChannelHandlerContext ctx, ByteBuf result, UControlEnum uControlEnum) {
result.readBytes(new byte[result.readableBytes()]);
BootNettyClientChannel Channel= BootNettyClientChannelCache.get(ctx.channel().id().asShortText());
String slave_ip= Channel.getCode();
if (uControlEnum == UControlEnum.TESTFR_YES) {
LOGGER.info(String.format("接收到从站[%s]的测试确认指令!",slave_ip));
} else if (uControlEnum == UControlEnum.STOPDT_YES) {
LOGGER.info(String.format("接收到从站[%s]的停止确认指令!",slave_ip));
}
else if (uControlEnum == UControlEnum.TESTFR) {
LOGGER.info(String.format("接收到从站[%s]的发出的测试命令!",slave_ip));
}
else if (uControlEnum == UControlEnum.STARTDT_YES) {
LOGGER.info(String.format("接收到从站[%s]的启动指令确认指令!",slave_ip));
Iec104ThreadLocal.getScheduledTaskPool().startTestLinkTask();
Iec104ThreadLocal.getScheduledTaskPool().startGeneralCallTask();
} else {
LOGGER.error("U报文无效:"+uControlEnum);
}
}
}

View File

@ -0,0 +1,62 @@
package com.yfd.platform.component.iec104.server.slave;
import com.yfd.platform.component.iec104.config.DefaultIec104Config;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.yfd.platform.component.iec104.server.Iec104Slave;
import com.yfd.platform.component.iec104.server.handler.*;
import com.yfd.platform.component.iec104.server.slave.handler.Iec104TcpSlaveHandler;
import com.yfd.platform.component.iec104.server.slave.handler.SysUframeServerHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
*
* @ClassName: Iec104ServerInitializer
* @Description: 104协议 处理链
* @author YDL
* @date 2020年5月13日
*/
@Setter
@Accessors(chain = true)
public class Iec104ServerInitializer extends ChannelInitializer<SocketChannel> {
private DataHandler dataHandler;
private Iec104Config iec104Config;
/**
* 初始化处理链
*/
@Override
public void initChannel(SocketChannel ch) {
if (iec104Config != null) {
Iec104ThreadLocal.setIec104Config(iec104Config);
} else {
Iec104ThreadLocal.setIec104Config(new DefaultIec104Config());
}
ChannelPipeline pipeline = ch.pipeline();
// 沾包拆包工具
pipeline.addLast("unpack", new Unpack104Handler());
// 数据检查工具
pipeline.addLast("check", new Check104Handler());
// byte[] 编码器
pipeline.addLast("bytesEncoder", new BytesEncoder());
//编码器 将对象编码成 字节码
pipeline.addLast("encoder", new DataEncoder());
//拦截 U帧处理器
pipeline.addLast("uframe", new SysUframeServerHandler());
//拦截 S帧处理器
pipeline.addLast("sframe", new SysSframeHandler());
//解码器
pipeline.addLast("decoder", new DataDecoder());
// 具体的处理器
pipeline.addLast("handler", new Iec104TcpSlaveHandler(dataHandler));
}
}

View File

@ -0,0 +1,85 @@
package com.yfd.platform.component.iec104.server.slave;
import com.yfd.platform.component.iec104.config.DefaultIec104Config;
import com.yfd.platform.component.iec104.config.Iec104Config;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.yfd.platform.component.iec104.server.Iec104Slave;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* iec 104 TCP 协议 服务端做从机
* @author Admin
*
*/
public class Iec104TcpServerSlave implements Iec104Slave {
private int port;
private Iec104Config iec104Config;
private DataHandler dataHandler;
public Iec104TcpServerSlave(int port) {
this.port = port;
}
/**
* 启动 从机
*/
@Override
public void run() throws Exception {
Iec104ThreadLocal.setIec104Config(iec104Config);
//1 EventLoopGroup是一个线程组它包含了一组nio线程专门用于网络事件的处理实际上他们就是Reactor线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 里创建2个的原因是一个用于服务端接收客户的连接另一个用于SockentChannel的网络读写
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 1
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
// (3) NIO
.channel(NioServerSocketChannel.class)
//(4)
.childHandler(getIec104ServerInitializer())
//(5) 请求的队列的最大长度
.option(ChannelOption.SO_BACKLOG, 128)
// (6) 保持长连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
//7绑定端口开始接收进来的连接
ChannelFuture f = b.bind(port).sync();
// 等待服务器 socket 关闭
// 在这个例子中这不会发生但你可以优雅地关闭你的服务器
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
private Iec104ServerInitializer getIec104ServerInitializer() {
Iec104ServerInitializer iec104ServerInitializer = new Iec104ServerInitializer();
iec104ServerInitializer.setDataHandler(dataHandler).setIec104Config(iec104Config);
return iec104ServerInitializer;
}
@Override
public Iec104Slave setDataHandler(DataHandler dataHandler) {
this.dataHandler = dataHandler;
return this;
}
@Override
public Iec104Slave setConfig(Iec104Config iec104Config) {
this.iec104Config = iec104Config;
return this;
}
}

View File

@ -0,0 +1,108 @@
package com.yfd.platform.component.iec104.server.slave.handler;
import com.yfd.platform.component.iec104.core.CachedThreadPool;
import com.yfd.platform.component.iec104.core.ControlManageUtil;
import com.yfd.platform.component.iec104.core.Iec104ThreadLocal;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.server.handler.ChannelHandlerImpl;
import com.yfd.platform.component.iec104.server.handler.DataHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
*
* @ClassName: Iec104TcpSlaveHandler
* @Description: 从机处理类
* @author YDL
* @date 2020年5月13日
*/
public class Iec104TcpSlaveHandler extends SimpleChannelInboundHandler<MessageDetail> {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
private DataHandler dataHandler;
public Iec104TcpSlaveHandler(DataHandler dataHandler) {
this.dataHandler = dataHandler;
}
/**
* 新连接
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Iec104ThreadLocal.setControlPool(new ControlManageUtil(ctx).setFrameAmountMax(Iec104ThreadLocal.getIec104Conig().getFrameAmountMax()));
/**
* 启动
*/
Iec104ThreadLocal.getControlPool().startSendFrameTask();
if (dataHandler != null) {
Runnable runnable = () -> {
try {
dataHandler.handlerAdded(new ChannelHandlerImpl(ctx));
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
};
CachedThreadPool.getCachedThreadPool().execute(runnable);
}
}
/**
* 断开连接
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
}
/**
* 收到消息
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, MessageDetail ruleDetail104) throws Exception {
if (dataHandler != null) {
Runnable runnable = () -> {
try {
dataHandler.channelRead(new ChannelHandlerImpl(ctx), ruleDetail104);
} catch (Exception e) {
LOGGER.error("Exception caught", e);
}
};
CachedThreadPool.getCachedThreadPool().execute(runnable);
}
}
/**
* 连接
*
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
LOGGER.error("SimpleChatClient:" + incoming.remoteAddress() + "在线");
}
/**
* 关闭
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
LOGGER.error("SimpleChatClient:" + incoming.remoteAddress() + "掉线");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Channel incoming = ctx.channel();
LOGGER.error("SimpleChatClient:" + incoming.remoteAddress() + "异常");
// 当出现异常就关闭连接
//cause.printStackTrace();
LOGGER.error("Exception caught", cause);
ctx.close();
}
}

View File

@ -0,0 +1,89 @@
package com.yfd.platform.component.iec104.server.slave.handler;
import com.yfd.platform.component.iec104.common.BasicInstruction104;
import com.ydl.iec.util.Iec104Util;
import com.yfd.platform.component.iec104.enums.UControlEnum;
import com.ydl.iec.util.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @ClassName: SysUframeInboundHandler
* @Description: 处理U帧的报文
* @author YDL
* @date 2020年5月13日
*/
public class SysUframeServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class);
/**
* 拦截系统消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf result = (ByteBuf) msg;
byte[] bytes = new byte[result.readableBytes()];
result.readBytes(bytes);
if (isSysInstruction(bytes)) {
UControlEnum uControlEnum = Iec104Util.getUcontrol(ByteUtil.getByte(bytes, 2, 4));
if (uControlEnum != null) {
uInstructionHandler(ctx, result, uControlEnum);
return;
}
}
result.writeBytes(bytes);
// LOGGER.info("普通指令");
ctx.fireChannelRead(result);
}
/**
*
* @Title: isSysInstruction
* @Description: TODO 判断是否是 系统报文
* @param @param bytes
* @param @return
* @return boolean
* @throws
*/
private boolean isSysInstruction(byte[] bytes) {
// U帧只有6字节
return bytes.length == 6;
}
/**
*
* @Title: uInstructionHandler
* @Description: 处理U帧
* @param ctx
* @param result
* @param uControlEnum
* @return void
* @throws
*/
private void uInstructionHandler(ChannelHandlerContext ctx, ByteBuf result, UControlEnum uControlEnum) {
result.readBytes(new byte[result.readableBytes()]);
byte[] resultBytes = null;
if (uControlEnum == UControlEnum.TESTFR) {
LOGGER.error("收到测试指令");
resultBytes = BasicInstruction104.TESTFR_YES;
} else if (uControlEnum == UControlEnum.STOPDT) {
LOGGER.error("收到停止指令");
resultBytes = BasicInstruction104.STOPDT_YES;
} else if (uControlEnum == UControlEnum.STARTDT) {
LOGGER.error("收到启动指令");
resultBytes = BasicInstruction104.STARTDT_YES;
} else {
LOGGER.error("U报文无效" + uControlEnum);
}
if (resultBytes != null) {
result.writeBytes(resultBytes);
LOGGER.error("回复U报");
ctx.writeAndFlush(result);
}
}
}

View File

@ -0,0 +1,247 @@
package com.ydl.iec.util;
import java.io.ByteArrayOutputStream;
import java.util.Calendar;
import java.util.Date;
/**
*
* @ClassName: ByteUtil
* @Description: byte 工具类
* @author YDL
* @date 2020年5月13日
*/
public class ByteUtil {
/**
*
* @Title: intToByteArray
* @Description: int 转换成 byte数组
* @param @param i
* @param @return
* @return byte[]
* @throws
*/
public static byte[] intToByteArray(int i) {
byte[] result = new byte[4];
result[0] = (byte) ((i >> 24) & 0xFF);
result[1] = (byte) ((i >> 16) & 0xFF);
result[2] = (byte) ((i >> 8) & 0xFF);
result[3] = (byte) (i & 0xFF);
return result;
}
/**
* @Title: shortToByteArray
* @Description: short 转换成 byte[]
* @param @param val
* @param @return
* @return byte[]
* @throws
*/
public static byte[] shortToByteArray(short val) {
byte[] b = new byte[2];
b[0] = (byte) ((val >> 8) & 0xff);
b[1] = (byte) (val & 0xff);
return b;
}
/**
*
* @Title: byteArrayToInt
* @Description: byte[] 转换成 int
* @param @param bytes
* @param @return
* @return int
* @throws
*/
public static int byteArrayToInt(byte[] bytes) {
int value = 0;
for (int i = 0; i < 4; i++) {
int shift = (3 - i) * 8;
value += (bytes[i] & 0xFF) << shift;
}
return value;
}
/**
*
* @Title: byteArrayToShort
* @Description: byte[] 转换成short
* @param @param bytes
* @param @return
* @return short
* @throws
*/
public static short byteArrayToShort(byte[] bytes) {
short value = 0;
for (int i = 0; i < 2; i++) {
int shift = (1 - i) * 8;
value += (bytes[i] & 0xFF) << shift;
}
return value;
}
// /**
// *
// * @Title: listToBytes
// * @Description: TODO
// * @param @param byteList
// * @param @return
// * @return byte[]
// * @throws
// */
// public static byte[] listToBytes(List<Byte> byteList) {
// byte[] bytes = new byte[byteList.size()];
// int index = 0;
// for (Byte item : byteList) {
// bytes[index++] = item;
// }
// return bytes;
// }
/**
*
* @Title: date2HByte
* @Description: 日期转换成 CP56Time2a
* @param @param date
* @param @return
* @return byte[]
* @throws
*/
public static byte[] date2Hbyte(Date date) {
ByteArrayOutputStream bOutput = new ByteArrayOutputStream();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
// 毫秒需要转换成两个字节其中 低位在前高位在后
// 先转换成short
int millisecond = calendar.get(Calendar.SECOND) * 1000 + calendar.get(Calendar.MILLISECOND);
// 默认的高位在前
byte[] millisecondByte = intToByteArray(millisecond);
bOutput.write((byte) millisecondByte[3]);
bOutput.write((byte) millisecondByte[2]);
// 分钟 只占6个比特位 需要把前两位置为零
bOutput.write((byte) calendar.get(Calendar.MINUTE));
// 小时需要把前三位置零
bOutput.write((byte) calendar.get(Calendar.HOUR_OF_DAY));
// 星期日的时候 week 是0
int week = calendar.get(Calendar.DAY_OF_WEEK);
if (week == Calendar.SUNDAY) {
week = 7;
} else {
week--;
}
// 前三个字节是 星期 因此需要将星期向左移5位 后五个字节是日期 需要将两个数字相加 相加之前需要先将前三位置零
bOutput.write((byte) (week << 5) + (calendar.get(Calendar.DAY_OF_MONTH)));
// 前四字节置零
bOutput.write((byte) ((byte) calendar.get(Calendar.MONTH) + 1));
bOutput.write((byte) (calendar.get(Calendar.YEAR) - 2000));
return bOutput.toByteArray();
}
/**
*
* @Title: date2HByte
* @Description:CP56Time2a转换成 时间
* @param @param date
* @param @return
* @return byte[]
* @throws
*/
public static Date byte2Hdate(byte[] dataByte) {
int year = (dataByte[6] & 0x7F) + 2000;
int month = dataByte[5] & 0x0F;
int day = dataByte[4] & 0x1F;
int hour = dataByte[3] & 0x1F;
int minute = dataByte[2] & 0x3F;
int second = dataByte[1] > 0 ? dataByte[1] : (int) (dataByte[1] & 0xff);
int millisecond = dataByte[0] > 0 ? dataByte[0] : (int) (dataByte[0] & 0xff);
millisecond = (second << 8) + millisecond;
second = millisecond / 1000;
millisecond = millisecond % 1000;
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, millisecond);
return calendar.getTime();
}
public static String byteArrayToHexString(byte[] array) {
return byteArray2HexString(array, Integer.MAX_VALUE, false);
}
public static String byteArray2HexString(byte[] arrBytes, int count, boolean blank) {
String ret = "";
if (arrBytes == null || arrBytes.length < 1) {
return ret;
}
if (count > arrBytes.length) {
count = arrBytes.length;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < count; i++) {
ret = Integer.toHexString(arrBytes[i] & 0xFF).toUpperCase();
if (ret.length() == 1) {
builder.append("0").append(ret);
} else {
builder.append(ret);
}
if (blank) {
builder.append(" ");
}
}
return builder.toString();
}
/**
* 返回指定位置的数组
* @param bytes
* @param start 开始位置
* @param length 截取长度
* @return
*/
public static byte[] getByte(byte[] bytes, int start, int length) {
byte[] ruleByte = new byte[length];
int index = 0;
while (index < length) {
ruleByte[index++] = bytes[start++];
}
return ruleByte;
}
/**
* 十六进制字符串转换成byte数组
* @param hexStr
* @return
*/
public static byte[] hexStringToBytes(String hexStr){
hexStr = hexStr.replaceAll(" ", "");
hexStr = hexStr.toUpperCase();
int len = (hexStr.length() / 2);
byte[] result = new byte[len];
char[] achar = hexStr.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
private static int toByte(char c) {
byte b = (byte) "0123456789ABCDEF".indexOf(c);
return b;
}
}

View File

@ -0,0 +1,258 @@
package com.ydl.iec.util;
import com.yfd.platform.component.iec104.message.MessageDetail;
import com.yfd.platform.component.iec104.enums.TypeIdentifierEnum;
import com.yfd.platform.component.iec104.enums.UControlEnum;
/**
*
* @ClassName: Iec104Util
* @Description: 工具类
* @author YDL
* @date 2020年5月13日
*/
public class Iec104Util {
private static int controlLength = 4;
/**
* I 格式 低位在前
* @param accept 接收序列号
* @param send 发送序列号
* @return
*/
public static byte[] getIcontrol(short accept, short send) {
byte[] control = new byte[4];
// 向左移动一位 保证低位的D0 是0
send = (short) (send << 1);
control[0] = (byte) ((send));
control[1] = (byte) ((send >> 8));
accept = (short) (accept << 1);
control[2] = (byte) ((accept));
control[3] = (byte) ((accept >> 8));
return control;
}
/**
* 返回控制域中的接收序号
* @param control
* @return
*/
public static short getAccept(byte[] control) {
int accept = 0;
short acceptLow = (short) (control[2] & 0xff);
short acceptHigh = (short) (control[3] & 0xff);
accept += acceptLow;
accept += acceptHigh << 8;
accept = accept >> 1;
return (short) accept;
}
/**
* 返回控制域中的发送序号
* @param control
* @return
*/
public static short getSend(byte[] control) {
int send = 0;
short acceptLow = (short) (control[0] & 0xff);
short acceptHigh = (short) (control[1] & 0xff);
send += acceptLow;
send += acceptHigh << 8;
send = send >> 1;
return (short) send;
}
/**
* S 格式
* @param accept
* @return
*/
public static byte[] getScontrol(short accept) {
byte[] control = new byte[4];
// 向左移动一位 保证低位的D0 是0
short send = 1;
control[0] = (byte) ((send));
control[1] = (byte) ((send >> 8));
accept = (short) (accept << 1);
control[2] = (byte) ((accept));
control[3] = (byte) ((accept >> 8));
return control;
}
/**
*
* @Title: 返回U帧
* @Description: 判断是否是
* @param @param control
* @param @return
* @return boolean
* @throws
*/
public static UControlEnum getUcontrol(byte[] control) {
if (control.length < controlLength || control[1] != 0 || control[2] != 0 || control[3] != 0) {
return null;
}
int controlInt = ByteUtil.byteArrayToInt(control);
for (UControlEnum ucontrolEnum : UControlEnum.values()) {
if (ucontrolEnum.getValue() == controlInt) {
return ucontrolEnum;
}
}
return null;
}
/**
* 返回消息地址 其中低位在前
* @param i
* @return
*/
public static byte[] intToMessageAddress(int i) {
byte[] result = new byte[3];
result[0] = (byte) (i & 0xFF);
result[1] = (byte) ((i >> 8) & 0xFF);
result[2] = (byte) ((i >> 16) & 0xFF);
return result;
}
/**
* 消息地址 只有三个
* @param bytes
* @return
*/
public static int messageAddressToInt(byte[] bytes) {
int value = 0;
for (int i = 2; i >= 0; i--) {
int shift = (2 - i) * 8;
value += (bytes[2 - i] & 0xFF) << shift;
}
return value;
}
/**
* 设置可以变限定词
* @param ruleDetail104
* @param byteItem
*/
public static void setChanged(MessageDetail ruleDetail104, byte byteItem) {
// 第一位是 0 则是有序的
ruleDetail104.setContinuous((byteItem & 0x80) == 0 ? false : true);
// 先将第一位数置零 然后转换成int
ruleDetail104.setMeasgLength(byteItem & (byte) 0x7F);
}
/**
* 返回可变限定词数组
* @param ruleDetail104
* @return
*/
public static byte getChangedQualifiers(MessageDetail ruleDetail104) {
// 将长度转换成 byte
byte changedQualifiers = (byte) ruleDetail104.getMeasgLength();
// 判断SQ isContinuous false SQ = 0;否则 SQ =1 , 同时将SQ置 设置在 可变限定词的 D7位置
int sq = ruleDetail104.isContinuous() ? 0x80 : 0;
changedQualifiers = (byte) (sq | changedQualifiers);
return changedQualifiers;
}
public static void setMeaageAttribute(MessageDetail ruleDetail104) {
boolean isMessage = !(TypeIdentifierEnum.generalCall.equals(ruleDetail104.getTypeIdentifier()) //总召唤无此项
|| TypeIdentifierEnum.timeSynchronization.equals(ruleDetail104.getTypeIdentifier()) // 时钟同步
|| TypeIdentifierEnum.resetPprocess.equals(ruleDetail104.getTypeIdentifier()) // 复位进程
|| TypeIdentifierEnum.initEnd.equals(ruleDetail104.getTypeIdentifier()));
ruleDetail104.setMessage(isMessage);
boolean isQualifiers = !(TypeIdentifierEnum.timeSynchronization.equals(ruleDetail104.getTypeIdentifier()) // 时钟同步
|| TypeIdentifierEnum.onePointTeleindication.equals(ruleDetail104.getTypeIdentifier()) // 单点 遥信
|| TypeIdentifierEnum.twoPointTeleindication.equals(ruleDetail104.getTypeIdentifier()) // 双点 遥信
|| TypeIdentifierEnum.onePointTimeTeleindication.equals(ruleDetail104.getTypeIdentifier()) // 单点 遥信带时标
|| TypeIdentifierEnum.twoPointTimeTeleindication.equals(ruleDetail104.getTypeIdentifier()) // 双点 遥信带时标
|| TypeIdentifierEnum.onePointTelecontrol.equals(ruleDetail104.getTypeIdentifier()) // 单命令遥控
|| TypeIdentifierEnum.twoPointTelecontrol.equals(ruleDetail104.getTypeIdentifier())); // 双命令遥控
ruleDetail104.setQualifiers(isQualifiers);
boolean isTimeScale = TypeIdentifierEnum.timeSynchronization.equals(ruleDetail104.getTypeIdentifier()) // 时钟同步
|| TypeIdentifierEnum.onePointTimeTeleindication.equals(ruleDetail104.getTypeIdentifier()) // 摇信带时标 单点
|| TypeIdentifierEnum.twoPointTimeTeleindication.equals(ruleDetail104.getTypeIdentifier()) //摇信带时标 双点
|| TypeIdentifierEnum.normalizedTimeTelemetry.equals(ruleDetail104.getTypeIdentifier()) //遥测带时标 归一化值
|| TypeIdentifierEnum.scaledTimeTelemetry.equals(ruleDetail104.getTypeIdentifier()) //遥测带时标 标度化值
|| TypeIdentifierEnum.shortFloatingPointTimeTelemetry.equals(ruleDetail104.getTypeIdentifier()); //遥测带时标 短浮点数
ruleDetail104.setTimeScaleExit(isTimeScale);
}
/**
* short 转换成两个 字节后是163 00 也就是 value[1] 中才有值
* test 在D7位置 因此 值应该和 01000000 做与运算
* P/N 0肯定确认 1否定确认
* @return 肯定或否定确认
*/
public static boolean isYes(byte[] values) {
return (values[0] & 1 << 6) == 0;
}
/**
* short 转换成两个 字节后是163 00 也就是 value[1] 中才有值
* test 在D7位置 因此 值应该和 10000000 做与运算
* tets 0 为试验 1 试验
* @return 是否试验
*/
public static boolean isTets(byte[] values) {
return (values[0] & 1 << 7) != 0;
}
/**
* 返回具体的原因
* @param values
* @return
*/
public static short getTransferReasonShort(byte[] values) {
byte transferReason = values[0];
// 前两位置零
transferReason = (byte) (transferReason & 0x3E);
return transferReason;
}
public static short getTransferReasonShort(boolean isTets, boolean isYes, short transferReason) {
int t = isTets ? 1 : 0;
int y = isYes ? 0 : 1;
int transferReasonInt = t << 7 | transferReason;
transferReasonInt = y << 6 | transferReasonInt;
short transferReasonShort = (short) (transferReasonInt << 8);
return transferReasonShort;
}
/**
* 返回终端地址对应的byte数组 其中低位在前
* @param terminalAddress
* @return
*/
public static byte[] getTerminalAddressByte(short terminalAddress) {
byte[] b = new byte[2];
b[1] = (byte) ((terminalAddress >> 8) & 0xff);
b[0] = (byte) (terminalAddress & 0xff);
return b;
}
/**
* 返回回终端地址 其中低位在前
* @param terminalAddress
* @return
*/
public static short getTerminalAddressShort(byte[] terminalAddress) {
short value = 0;
value += (terminalAddress[0] & 0xFF);
value += (terminalAddress[1] & 0xFF) << 8;
return value;
}
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 变电站-辅控设备-告警参数设置 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@RestController
@RequestMapping("/auxcontrol/device-alarm-parameter")
public class DeviceAlarmParameterController {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 变电站-辅控设备-告警记录 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@RestController
@RequestMapping("/auxcontrol/device-alarm-record")
public class DeviceAlarmRecordController {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 变电站-辅控设备-信号 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@RestController
@RequestMapping("/auxcontrol/device-signal")
public class DeviceSignalController {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 变电站-设备-运行数据 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@RestController
@RequestMapping("/auxcontrol/device-work-data")
public class DeviceWorkDataController {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 变电站-通讯网关设备 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@RestController
@RequestMapping("/auxcontrol/gateway-device")
public class GatewayDeviceController {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
* 变电站二次设备中的智能仪表监控设备 前端控制器
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@RestController
@RequestMapping("/auxcontrol/meter-device")
public class MeterDeviceController {
}

View File

@ -0,0 +1,115 @@
package com.yfd.platform.modules.auxcontrol.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 变电站-辅控设备-告警参数设置
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fk_device_alarm_parameter")
public class DeviceAlarmParameter implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 参数ID
*/
private String paramId;
/**
* 设备ID
*/
private String deviceId;
/**
* 信号ID
*/
private String signalId;
/**
* 信号名称
*/
private String signalName;
/**
* 告警类型温度过高压力异常电流过载
*/
private String alarmType;
/**
* 告警等级一般严重危急
*/
private String alarmLevel;
/**
* 告警触发条件
*/
private String alarmCondition;
/**
* 告警抑制条件
*/
private String inhibitionCondition;
/**
* 告警描述模板
*/
private String messageTemplate;
/**
* 01-组件变色 02-系统弹窗 03-邮件通知 04-触发联动 可多选01,02
*/
private String noticeType;
/**
* 触发联动[{name:"XX联动",id:"12345678"}]
*/
private String noticeLinkages;
/**
* 邮件通知[{name:"张三",email:"1122@163.com"}]
*/
private String noticeEmails;
/**
* 修改人
*/
private String lastmodifier;
/**
* 最近修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Timestamp lastmodifydate;
/**
* 备用1
*/
private String custom1;
/**
* 备用2
*/
private String custom2;
/**
* 备用3
*/
private String custom3;
}

View File

@ -0,0 +1,160 @@
package com.yfd.platform.modules.auxcontrol.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 变电站-辅控设备-告警记录
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fk_device_alarm_record")
public class DeviceAlarmRecord implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
private String recordId;
/**
* 变电站ID
*/
private String stationId;
/**
* 所属系统01-一次设备监控 02-消防系统 03-安防系统 04-动环系统 05-锁控系统 06-视频系统 07-照明系统
*/
private String systemcode;
/**
* 告警时间
*/
private LocalDateTime alarmTime;
/**
* 告警设备ID
*/
private String deviceId;
/**
* 告警设备名称
*/
private String deviceName;
/**
* 告警信号ID
*/
private String signalId;
/**
* 告警信号名称
*/
private String signalName;
/**
* 告警类型
*/
private String alarmType;
/**
* 告警等级一般严重危急
*/
private String alarmLevel;
/**
* 告警值
*/
private String alarmValue;
/**
* 值单位
*/
private String signalUnit;
/**
* 正常范围
*/
private String normalRange;
/**
* 告警信息
*/
private String alarmMessage;
/**
* 01-组件变色 02-系统弹窗 03-邮件通知 04-触发联动 可多选01,02
*/
private String noticeAction;
/**
* 邮件通知[{name:"张三",email:"1122@163.com"}]
*/
private String noticeEmails;
/**
* 触发联动[{name:"XX联动",id:"12345678"}]
*/
private String noticeLinkages;
/**
* 状态01-初始生成 02-已执行动作 03-手动关闭 09-故障消除
*/
private String status;
/**
* 备用1
*/
private String custom1;
/**
* 备用2
*/
private String custom2;
/**
* 备用3
*/
private String custom3;
/**
* 告警分类1-越限告警 2-设备报警
*/
private String alarmClass;
/**
* 故障消除时间
*/
private LocalDateTime fixTime;
/**
* 区域标识
*/
private String region;
/**
* 是否关闭告警0为关闭 1关闭
*/
private String confirmClosure;
/**
* 操作人
*/
private String operator;
/**
* 操作时间
*/
private LocalDateTime operationTime;
}

View File

@ -0,0 +1,141 @@
package com.yfd.platform.modules.auxcontrol.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 变电站-辅控设备-信号
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fk_device_signal")
public class DeviceSignal implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 信号ID
*/
private String signalId;
/**
* 辅控设备ID--fk_meter_device
*/
private String meterDeviceId;
/**
* 变电站一次设备idiis_substation_maindevice
*/
private String mainDeviceId;
/**
* 关联一次设备中部件ID,iis_substation_component
*/
private String mainCompnentId;
/**
* 信号类型 0-普通信号 1-告警信号
*/
private String signalType;
/**
* 信号编号
*/
private String signalCode;
/**
* 信号名称
*/
private String signalName;
/**
* 信号单位类似
*/
private String signalUnit;
/**
* 正常范围
*/
private String normalRange;
/**
* 顺序号
*/
private Integer orderno;
/**
* 遥控遥调类型01-单点遥控 02- -双点遥控 03-归一化值遥调 04-标度化遥调 05-浮点数遥调
*/
private String ykytType;
/**
* 遥控地址(不测可为空)
*/
private String ykAddr;
/**
* 遥信地址(不测可为空)
*/
private String yxAddr;
/**
* 遥测地址(不测可为空)
*/
private String ycAddr;
/**
* 遥调地址(不测可为空)
*/
private String ytAddr;
/**
* 修改人
*/
private String lastmodifier;
/**
* 最近修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Timestamp lastmodifydate;
/**
* 信号当前状态
*/
private Integer yxValue;
/**
* 信号当前状态
*/
private BigDecimal ycValue;
/**
* 备用1
*/
private String custom1;
/**
* 备用2
*/
private String custom2;
/**
* 备用3
*/
private String custom3;
}

View File

@ -0,0 +1,91 @@
package com.yfd.platform.modules.auxcontrol.domain;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import java.time.LocalDateTime;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 变电站-设备-运行数据
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fk_device_work_data")
public class DeviceWorkData implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 所属系统01-一次设备监控 02-消防系统 03-安防系统 04-动环系统 05-锁控系统 06-视频系统 07-照明系统
*/
private String systemcode;
/**
* 变电站ID
*/
private String stationId;
/**
* 监控设备ID
*/
private String deviceId;
/**
* 监控设备名称
*/
private String deviceName;
/**
* 监控信号ID
*/
private String signalId;
/**
* 监控信号名称
*/
private String signalName;
/**
* 采集时间
*/
private LocalDateTime startTime;
/**
* 单位
*/
private String unit;
/**
*
*/
private BigDecimal value;
/**
* 备用1
*/
private String custom1;
/**
* 备用2
*/
private String custom2;
/**
* 备用3
*/
private String custom3;
}

View File

@ -0,0 +1,152 @@
package com.yfd.platform.modules.auxcontrol.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 变电站-通讯网关设备
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fk_gateway_device")
public class GatewayDevice implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 变电站ID
*/
private String stationId;
/**
* 设备类型01-监控主机 02-网关机 03-通信服务器
*/
private String deviceType;
/**
* 设备编号
*/
private String deviceCode;
/**
* 设备名称
*/
private String deviceName;
/**
* 中文描述
*/
private String deviceDesc;
/**
* 设备型号
*/
private String deviceModel;
/**
* 出厂编号
*/
private String productionNum;
/**
* 生产日期
*/
private String productionDate;
/**
* 生产厂家
*/
private String manufacturer;
/**
* 生产国家
*/
private String madein;
/**
* 实物ID
*/
private String phyassetId;
/**
* 以太网IP地址
*/
private String ipAddr;
/**
* 以太网Mac地址
*/
private String macAddr;
/**
* 设备状态00-停用 01-使用-在线 02-使用-不在线
*/
private String status;
/**
* IEC104协议地址
*/
private Integer iecAddr;
/**
* 心跳保持更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Timestamp keepliveTime;
/**
* 备用1
*/
private String custom1;
/**
* 备用2
*/
private String custom2;
/**
* 备用3
*/
private String custom3;
/**
* 遥测类报表控制块
*/
private String urcb;
/**
* 遥信类报告控制块
*/
private String brcb;
/**
* 设备使用的协议类型 IEC104,IEC61850
*/
@TableField("Protocol")
private String protocol;
/**
* 采集频率
*/
private String frequency;
}

View File

@ -0,0 +1,150 @@
package com.yfd.platform.modules.auxcontrol.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
* 变电站二次设备中的智能仪表监控设备
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("fk_meter_device")
public class MeterDevice implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 设备ID
*/
private String deviceId;
/**
* 变电站ID
*/
private String stationId;
/**
* 所属系统01-一次设备监控 02-消防系统 03-安防系统 04-动环系统 05-锁控系统 06-视频系统 07-照明系统
*/
private String systemcode;
/**
* 设备类型01-温度传感器02-湿度传感器03-SF?传感器04-水浸传感器05-风速传感器06-空调控制器07-门禁08-电子围栏09-红外10-火灾告警控制器11-智能感温控制器12-烟感探测器13声光报警器14-照明
*/
private String deviceType;
/**
* 设备编号
*/
private String deviceCode;
/**
* 设备名称
*/
private String deviceName;
/**
* 中文描述
*/
private String deviceDesc;
/**
* 设备型号
*/
private String deviceModel;
/**
* 出厂编号
*/
private String productionNum;
/**
* 出厂日期
*/
private String productionDate;
/**
* 生产厂家
*/
private String manufacturer;
/**
* 生产国家
*/
private String madein;
/**
* 实物ID
*/
private String phyassetId;
/**
* 区域标识
*/
private String region;
/**
* 安装位置预留
*/
private String place;
/**
* 以太网IP地址
*/
private String ipAddr;
/**
* 以太网Mac地址
*/
private String macAddr;
/**
* 对应网关设备IP
*/
private String netdeviceIp;
/**
* 设备状态00-停用 01-使用-在线 02-使用-不在线
*/
private String status;
/**
* 修改人
*/
private String lastmodifier;
/**
* 最近修改时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Timestamp lastmodifydate;
/**
* 备用1
*/
private String custom1;
/**
* 备用2
*/
private String custom2;
/**
* 备用3
*/
private String custom3;
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.mapper;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmParameter;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 变电站-辅控设备-告警参数设置 Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface DeviceAlarmParameterMapper extends BaseMapper<DeviceAlarmParameter> {
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.mapper;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmRecord;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 变电站-辅控设备-告警记录 Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface DeviceAlarmRecordMapper extends BaseMapper<DeviceAlarmRecord> {
}

View File

@ -0,0 +1,23 @@
package com.yfd.platform.modules.auxcontrol.mapper;
import com.yfd.platform.modules.auxcontrol.domain.DeviceSignal;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import java.util.Map;
/**
* <p>
* 变电站-辅控设备-信号 Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface DeviceSignalMapper extends BaseMapper<DeviceSignal> {
List<DeviceSignal> selectDeviceSignalYx(String slave_ip, String address);
List<DeviceSignal> selectDeviceSignalYc(String slave_ip, String address);
List<Map<String,Object>> selectDeviceSignal_Map(String slave_ip, String type, String address);
List<Map<String,Object>> selectDeviceSignal_Map2(String type,String address);
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.mapper;
import com.yfd.platform.modules.auxcontrol.domain.DeviceWorkData;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 变电站-设备-运行数据 Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface DeviceWorkDataMapper extends BaseMapper<DeviceWorkData> {
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.mapper;
import com.yfd.platform.modules.auxcontrol.domain.GatewayDevice;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 变电站-通讯网关设备 Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface GatewayDeviceMapper extends BaseMapper<GatewayDevice> {
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.mapper;
import com.yfd.platform.modules.auxcontrol.domain.MeterDevice;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 变电站二次设备中的智能仪表监控设备 Mapper 接口
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface MeterDeviceMapper extends BaseMapper<MeterDevice> {
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.service;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmParameter;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 变电站-辅控设备-告警参数设置 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface IDeviceAlarmParameterService extends IService<DeviceAlarmParameter> {
}

View File

@ -0,0 +1,27 @@
package com.yfd.platform.modules.auxcontrol.service;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmRecord;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 变电站-辅控设备-告警记录 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface IDeviceAlarmRecordService extends IService<DeviceAlarmRecord> {
/**********************************
* 用途说明: 生成报警记录
* 参数说明
* from 'IEC104','IEC61850'
* type 'yx','yc'
* slave_ip 服务器IP
* address 地址
* value
* 返回值说明: 无返回值
***********************************/
void doAlaramRecord(String from,String type,String slaveIp,String address,String value);
}

View File

@ -0,0 +1,29 @@
package com.yfd.platform.modules.auxcontrol.service;
import com.yfd.platform.modules.auxcontrol.domain.DeviceSignal;
import com.baomidou.mybatisplus.extension.service.IService;
import java.text.ParseException;
/**
* <p>
* 变电站-辅控设备-信号 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface IDeviceSignalService extends IService<DeviceSignal> {
/**********************************
* 用途说明: 遥测 遥信数据更新到数据库
* 参数说明
* netaddres 网关机设备地址
* address 信号地址
* type 信号类型 yx-遥信 yc-遥测
* value 数据采集结果
* dateTime 数据采集时标
* 返回值说明: bool 更新成功或者失败
***********************************/
boolean updateDeviceSignalValue(String slaveIp, String address, String type, String value, String dateTime) throws ParseException;
}

View File

@ -0,0 +1,27 @@
package com.yfd.platform.modules.auxcontrol.service;
import com.yfd.platform.modules.auxcontrol.domain.DeviceWorkData;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 变电站-设备-运行数据 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface IDeviceWorkDataService extends IService<DeviceWorkData> {
/**********************************
* 用途说明: 从设备中采集数据并保存到数据库中
* 参数说明
* from 'IEC104','IEC61850'
* slave_ip 通信网关机地址
* address 信号地址
* value 数据采集结果
* dateTime 数据采集时标
* 返回值说明:
***********************************/
void insertData(String from, String slave_ip, String address, String value, String dateTimeString);
}

View File

@ -0,0 +1,23 @@
package com.yfd.platform.modules.auxcontrol.service;
import com.yfd.platform.modules.auxcontrol.domain.GatewayDevice;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 变电站-通讯网关设备 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface IGatewayDeviceService extends IService<GatewayDevice> {
/**********************************
* 用途说明: 根据IP,更新心跳保持时间
* 参数说明
* ip 终端对应IP
* 返回值说明:
***********************************/
void updateKeepLiveTime(String ip);
}

View File

@ -0,0 +1,16 @@
package com.yfd.platform.modules.auxcontrol.service;
import com.yfd.platform.modules.auxcontrol.domain.MeterDevice;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 变电站二次设备中的智能仪表监控设备 服务类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
public interface IMeterDeviceService extends IService<MeterDevice> {
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.service.impl;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmParameter;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceAlarmParameterMapper;
import com.yfd.platform.modules.auxcontrol.service.IDeviceAlarmParameterService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 变电站-辅控设备-告警参数设置 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Service
public class DeviceAlarmParameterServiceImpl extends ServiceImpl<DeviceAlarmParameterMapper, DeviceAlarmParameter> implements IDeviceAlarmParameterService {
}

View File

@ -0,0 +1,438 @@
package com.yfd.platform.modules.auxcontrol.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmParameter;
import com.yfd.platform.modules.auxcontrol.domain.DeviceAlarmRecord;
import com.yfd.platform.modules.auxcontrol.domain.MeterDevice;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceAlarmParameterMapper;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceAlarmRecordMapper;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceSignalMapper;
import com.yfd.platform.modules.auxcontrol.mapper.MeterDeviceMapper;
import com.yfd.platform.modules.auxcontrol.service.IDeviceAlarmRecordService;
import com.yfd.platform.system.domain.SysDictionaryItems;
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>
* 变电站-辅控设备-告警记录 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Service
public class DeviceAlarmRecordServiceImpl extends ServiceImpl<DeviceAlarmRecordMapper, DeviceAlarmRecord> implements IDeviceAlarmRecordService {
@Resource
private DeviceSignalMapper deviceSignalMapper;
@Resource
private MeterDeviceMapper meterDeviceMapper;
@Resource
private DeviceAlarmParameterMapper deviceAlarmParameterMapper;
@Resource
private DeviceAlarmRecordMapper deviceAlarmRecordMapper;
@Resource
private SysDictionaryItemsMapper sysDictionaryItemsMapper;
/**********************************
* 用途说明: 生成报警记录IEC104
* 参数说明
* from 'IEC104','IEC61850'
* type 'yx','yc'
* slave_ip IP
* address 遥信要测地址
* value
* 返回值说明: 无返回值
***********************************/
@Override
@Transactional
public void doAlaramRecord(String from, String type, String slaveIp, String address, String value) {
List<Map<String, Object>> list = new ArrayList<>();
if ("IEC104".equals(from)) {
list = deviceSignalMapper.selectDeviceSignal_Map(slaveIp, "yc", address);
} else if ("IEC61850".equals(from)) {
list = deviceSignalMapper.selectDeviceSignal_Map2(type, address);
}
if (list.size() == 0) {
return;
}
Map<String, Object> map = list.get(0);
//如果是yx 遥信 设备自身报警记录
if ("yx".equals(type)) {
//如果信号类型是1 设备告警
if ("1".equals(map.get("signal_type").toString())) {
//紧接者判断 value 1是告警 0是正常 status
if (1 == Integer.parseInt(value)) {
//填充告警记录信息接口
DeviceAlarmRecord deviceAlarmRecord = FillingDeviceAlarmRecord(type, null, map, value);
//首先判断 在不在时间范围之内 判断时间在不在时间范围之内接口
Map<String, Object> timeInterval = getTimeInterval(7);
//如果在时间范围内
if ("1".equals(timeInterval.get("beforeclock").toString())) {
//查询表中是否存在该报警记录
List<DeviceAlarmRecord> alarmRecords = queryDeviceAlarmRecord(deviceAlarmRecord, timeInterval);
//如果没有有报警记录 新增记录
if (alarmRecords.size() == 0) {
doAlarmAction(deviceAlarmRecord);
} else if (alarmRecords.size() > 1) {
this.removeById(alarmRecords.get(0).getRecordId());
}
} else {
List<DeviceAlarmRecord> alarmRecords = queryDeviceAlarmRecord(deviceAlarmRecord, timeInterval);
if (alarmRecords.size() == 0) {
doAlarmAction(deviceAlarmRecord);
} else if (alarmRecords.size() > 1) {
this.removeById(alarmRecords.get(0).getRecordId());
}
}
} else {
//紧接者判断 value 1是告警 0是正常 status 同一信号监测到报警状态消除对当前信号所有未处理历史报警打上已处理标签并记录消除时间
LambdaQueryWrapper<DeviceAlarmRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeviceAlarmRecord::getSignalId, map.get("signal_id").toString());
queryWrapper.eq(DeviceAlarmRecord::getAlarmClass, "2");
queryWrapper.in(DeviceAlarmRecord::getStatus, "01", "02");
DeviceAlarmRecord deviceAlarmRecord = new DeviceAlarmRecord();
deviceAlarmRecord.setStatus("09");
//状态 故障消除
deviceAlarmRecord.setFixTime(DateUtil.toLocalDateTime(DateUtil.date()));
//故障消除时间
this.update(deviceAlarmRecord, queryWrapper);
}
}
} else if ("yc".equals(type)) {
//1.通过设备id 和信号id 查询告警参数设置表告警规则
LambdaQueryWrapper<DeviceAlarmParameter> queryWrapperDAP = new LambdaQueryWrapper<>();
if (StrUtil.isNotEmpty(map.get("device_id").toString())) {
queryWrapperDAP.eq(DeviceAlarmParameter::getDeviceId, map.get("device_id").toString());
}
if (StrUtil.isNotEmpty(map.get("signal_id").toString())) {
queryWrapperDAP.eq(DeviceAlarmParameter::getSignalId, map.get("signal_id").toString());
}
List<DeviceAlarmParameter> deviceAlarmParameters = deviceAlarmParameterMapper.selectList(queryWrapperDAP);
if (deviceAlarmParameters != null && deviceAlarmParameters.size() > 0) {
for (DeviceAlarmParameter deviceAlarmParameter : deviceAlarmParameters) {
//如果告警触发条件 不为空 1<=value<=100
if (deviceAlarmParameter.getAlarmCondition() != null) {
//根据告警规则 判断是否触发告警
boolean alarmTrigger = TriggerAlarm(deviceAlarmParameter, value);
//2.通过告警触发条件 value比较 满足条件 触发告警
if (alarmTrigger) {
DeviceAlarmRecord deviceAlarmRecord = FillingDeviceAlarmRecord(type, deviceAlarmParameter
, map, value);
//首先判断 在不在时间范围之内 判断时间在不在时间范围之内接口
Map<String, Object> timeInterval = getTimeInterval(7);
//如果在时间范围内
if ("1".equals(timeInterval.get("beforeclock").toString())) {
//查询表中是否存在该报警记录
List<DeviceAlarmRecord> alarmRecords = queryDeviceAlarmRecord(deviceAlarmRecord,
timeInterval);
//如果没有有报警记录 新增记录
if (alarmRecords.size() == 0) {
// 新增
deviceAlarmRecordMapper.insert(deviceAlarmRecord);
//TODO 邮件推送暂时删除了
}
} else {
List<DeviceAlarmRecord> alarmRecords = queryDeviceAlarmRecord(deviceAlarmRecord,
timeInterval);
if (alarmRecords.size() == 0) {
deviceAlarmRecordMapper.insert(deviceAlarmRecord);
//TODO 邮件推送暂时删除了
}
}
} else {
//不满足条件 同一信号监测到报警状态消除对当前信号所有未处理历史报警打上已处理标签并记录消除时间
LambdaUpdateWrapper<DeviceAlarmRecord> queryWrapper = new LambdaUpdateWrapper<>();
queryWrapper.eq(DeviceAlarmRecord::getSignalId, map.get("signal_id").toString());
queryWrapper.eq(DeviceAlarmRecord::getAlarmClass, "1");
queryWrapper.eq(DeviceAlarmRecord::getAlarmLevel, deviceAlarmParameter.getAlarmLevel());
queryWrapper.in(DeviceAlarmRecord::getStatus, "01", "02")
.set(DeviceAlarmRecord::getStatus, "09")
.set(DeviceAlarmRecord::getFixTime, LocalDateTime.now());
//故障消除时间
deviceAlarmRecordMapper.update(null, queryWrapper);
}
}
}
}
}
}
/**
* 填充告警记录表
*
* @return 返回告警记录信息
*/
public DeviceAlarmRecord FillingDeviceAlarmRecord(String type, DeviceAlarmParameter deviceAlarmParameter,
Map singaldata, String value) {
DeviceAlarmRecord deviceAlarmRecord = new DeviceAlarmRecord();
MeterDevice device = meterDeviceMapper.selectById(singaldata.get("device_id").toString());
// 创建 SimpleDateFormat 对象指定日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if ("yx".equals(type)) {
deviceAlarmRecord.setRecordId(IdUtil.fastSimpleUUID());
deviceAlarmRecord.setStationId(singaldata.get("station_id").toString());
//变电站id
deviceAlarmRecord.setSystemcode(singaldata.get("systemcode").toString());
//所属系统
deviceAlarmRecord.setAlarmTime(DateUtil.toLocalDateTime(DateUtil.date()));
//告警时间
deviceAlarmRecord.setDeviceId(singaldata.get("device_id").toString());
//告警设备ID
deviceAlarmRecord.setDeviceName(singaldata.get("device_name").toString());
//告警设名称
deviceAlarmRecord.setSignalId(singaldata.get("signal_id").toString());
//告警信号id
deviceAlarmRecord.setSignalName(singaldata.get("signal_name").toString());
//告警信号名称
deviceAlarmRecord.setAlarmValue(value);
//告警值
deviceAlarmRecord.setSignalUnit("");
//值单位
String alarmMessage = String.format("[%s]发生了%s", singaldata.get("device_name").toString(),
singaldata.get("signal_name").toString());
deviceAlarmRecord.setAlarmMessage(alarmMessage);
//告警信息
deviceAlarmRecord.setStatus("01");
//状态
deviceAlarmRecord.setAlarmLevel("03");
deviceAlarmRecord.setAlarmClass("2");
//2-设备报警
deviceAlarmRecord.setRegion(device.getRegion());
//区域标识
}
if ("yc".equals(type)) {
deviceAlarmRecord.setRecordId(IdUtil.fastSimpleUUID());
//变电站id
deviceAlarmRecord.setStationId(singaldata.get("station_id").toString());
//所属系统
deviceAlarmRecord.setSystemcode(singaldata.get("systemcode").toString());
//告警时间
deviceAlarmRecord.setAlarmTime(DateUtil.toLocalDateTime(DateUtil.date()));
//告警设备ID
deviceAlarmRecord.setDeviceId(singaldata.get("device_id").toString());
//告警设备名称
deviceAlarmRecord.setDeviceName(singaldata.get("device_name").toString());
//告警信号id
deviceAlarmRecord.setSignalId(singaldata.get("signal_id").toString());
//告警信号名称
deviceAlarmRecord.setSignalName(singaldata.get("signal_name").toString());
//告警类型
deviceAlarmRecord.setAlarmType(deviceAlarmParameter.getAlarmType());
//告警等级
deviceAlarmRecord.setAlarmLevel(deviceAlarmParameter.getAlarmLevel());
//告警值
deviceAlarmRecord.setAlarmValue(value);
String sinalUnit = ObjUtil.isNotEmpty(singaldata.get("signal_unit")) ?
singaldata.get("signal_unit").toString() : "";
//值单位
deviceAlarmRecord.setSignalUnit(sinalUnit);
//正常范围
deviceAlarmRecord.setNormalRange(deviceAlarmParameter.getAlarmCondition());
String alarmLevelname = getDictName(deviceAlarmParameter.getAlarmLevel(), "alarmLevel");
String alarmMessage = String.format("信号[%s]监测值为[%s]%s,达到了设置的报警阈值范围[%s],发生告警,告警级别为[%s]",
singaldata.get("signal_name").toString(), value, sinalUnit,
deviceAlarmParameter.getAlarmCondition(), alarmLevelname
);
//告警信息
deviceAlarmRecord.setAlarmMessage(alarmMessage);
//告警方式
deviceAlarmRecord.setNoticeAction(deviceAlarmParameter.getNoticeType());
//邮件通知
deviceAlarmRecord.setNoticeEmails(deviceAlarmParameter.getNoticeEmails());
//触发联动
deviceAlarmRecord.setNoticeLinkages(deviceAlarmParameter.getNoticeLinkages());
//状态
deviceAlarmRecord.setStatus("01");
//1-越限报警
deviceAlarmRecord.setAlarmClass("1");
//区域标识
deviceAlarmRecord.setRegion(device.getRegion());
}
return deviceAlarmRecord;
}
//获取字典名称 根据项目编码和父项编码
public String getDictName(String itemcode, String parentcode) {
QueryWrapper<SysDictionaryItems> queryWrapperSysDictionaryItems = new QueryWrapper<>();
String dictname = "";
queryWrapperSysDictionaryItems.eq("itemcode", itemcode);
queryWrapperSysDictionaryItems.eq("parentcode", parentcode);
SysDictionaryItems sysDictionaryItems = sysDictionaryItemsMapper.selectOne(queryWrapperSysDictionaryItems);
if (sysDictionaryItems != null) {
dictname = sysDictionaryItems.getDictName();
}
return dictname;
}
/**
* 根据告警规则 判断是否触发告警
*
* @return true 触发恭敬规则
* false 没有触发告警规则
*/
public boolean TriggerAlarm(DeviceAlarmParameter deviceAlarmParameter, String value) {
boolean alramTrigger = true;
List<String> listSymbol = new ArrayList<>();
List<Double> values = new ArrayList<>();
// //获取告警触发条件
String str = deviceAlarmParameter.getAlarmCondition();
// 定义正则表达式
String regex = "([0-9]+(\\.[0-9]+)?|value)([<>=!]{1,2})([0-9]+(\\.[0-9]+)?|value)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
// 查找并输出比较符号和数值
while (matcher.find()) {
String value1 = matcher.group(1);
String operator = matcher.group(3);
String value2 = matcher.group(4);
if ("value".equals(value1)) {
values.add(Double.parseDouble(value));
} else {
values.add(Double.valueOf(value1));
}
listSymbol.add(operator);
if ("value".equals(value2)) {
values.add(Double.parseDouble(value));
} else {
values.add(Double.valueOf(value2));
}
}
// 比较操作
boolean result = compare(values.get(0), listSymbol.get(0), values.get(1));
boolean resultTwo = true;
if (listSymbol.size() > 1) {
resultTwo = compare(values.get(1), listSymbol.get(1), values.get(2));
}
if (result && resultTwo) {
alramTrigger = true;
} else {
alramTrigger = false;
}
return alramTrigger;
}
private static boolean compare(double value1, String operator, double value2) {
switch (operator) {
case "<":
return value1 < value2;
case "<=":
return value1 <= value2;
case ">":
return value1 > value2;
case ">=":
return value1 >= value2;
case "==":
return value1 == value2;
case "!=":
return value1 != value2;
case "=":
return value1 == value2;
default:
throw new IllegalArgumentException("Unsupported comparison operator: " + operator);
}
}
//执行告警相关动作 插入数据库
private void doAlarmAction(DeviceAlarmRecord alarmRecord) {
//1.通过设备id 和信号id 查询告警参数设置表告警规则
LambdaQueryWrapper<DeviceAlarmParameter> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeviceAlarmParameter::getDeviceId, alarmRecord.getDeviceId());
queryWrapper.eq(DeviceAlarmParameter::getSignalId, alarmRecord.getSignalId());
List<DeviceAlarmParameter> alarmParameters = deviceAlarmParameterMapper.selectList(queryWrapper);
for (DeviceAlarmParameter alarmParameter : alarmParameters) {
alarmRecord.setAlarmType(alarmParameter.getAlarmType());
alarmRecord.setNoticeAction(alarmParameter.getNoticeType());
alarmRecord.setNoticeLinkages(alarmParameter.getNoticeLinkages());
//todo 发送邮件
// pushEmail(alarmRecord,alarmParameter);
}
this.saveOrUpdate(alarmRecord);
}
/**
* 判断当前时间在不在区间范围之内
*
* @return true 在区间范围之内
* false 不在时间范围之内
*/
public Map<String, Object> getTimeInterval(int clock) {
int beforeclock = 0;
Map<String, Object> map = new HashMap<>();
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 获取昨天的 7
LocalDateTime yesterday7AM = LocalDateTime.of(now.minusDays(1).toLocalDate(), LocalTime.of(clock, 0));
map.put("starttime", yesterday7AM);
// 获取今天的 7
LocalDateTime today7AM = LocalDateTime.of(now.toLocalDate(), LocalTime.of(clock, 0));
LocalDateTime nextday7AM = LocalDateTime.of(now.plusDays(1).toLocalDate(), LocalTime.of(clock, 0));
map.put("endtime", today7AM);
// 判断时间是否在范围内
if (now.isAfter(yesterday7AM) && now.isBefore(today7AM)) {
beforeclock = 1;
} else {
beforeclock = 0;
map.put("starttime", today7AM);
map.put("endtime", nextday7AM);
}
map.put("beforeclock", beforeclock);
return map;
}
/**
* 根据条件查询告警记录表 判断是否存在告警记录
*
* @return true 存在告警记录
* false 不存在告警记录
*/
public List<DeviceAlarmRecord> queryDeviceAlarmRecord(DeviceAlarmRecord deviceAlarmRecord,
Map<String, Object> timeInterval) {
LambdaQueryWrapper<DeviceAlarmRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(DeviceAlarmRecord::getSignalId, deviceAlarmRecord.getSignalId());
queryWrapper.eq(DeviceAlarmRecord::getAlarmLevel, deviceAlarmRecord.getAlarmLevel());
LocalDateTime starttime = (LocalDateTime) timeInterval.get("starttime");
LocalDateTime endtime = (LocalDateTime) timeInterval.get("endtime");
queryWrapper.between(DeviceAlarmRecord::getAlarmTime, starttime, endtime);
queryWrapper.in(DeviceAlarmRecord::getStatus, "01", "02");
return this.list(queryWrapper);
}
}

View File

@ -0,0 +1,85 @@
package com.yfd.platform.modules.auxcontrol.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.yfd.platform.modules.auxcontrol.domain.DeviceSignal;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceSignalMapper;
import com.yfd.platform.modules.auxcontrol.service.IDeviceSignalService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* <p>
* 变电站-辅控设备-信号 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Service
public class DeviceSignalServiceImpl extends ServiceImpl<DeviceSignalMapper, DeviceSignal> implements IDeviceSignalService {
@Resource
private DeviceSignalMapper deviceSignalMapper;
/**********************************
* 用途说明: 修改变电站辅控设备信号
* 参数说明 deviceSignal 变电站辅控设备信号
* 返回值说明: com.yfd.platform.config.ResponseResult 返回修改成功或者失败
***********************************/
@Override
public boolean updateDeviceSignalValue(String slaveIp, String address, String type, String value,
String dateString) throws ParseException {
List<DeviceSignal> list = null;
if ("yx".equals(type)) {
// 遥信
list = deviceSignalMapper.selectDeviceSignalYx(slaveIp, address);
} else if ("yc".equals(type)) {
// 遥测
list = deviceSignalMapper.selectDeviceSignalYc(slaveIp, address);
}
if (list != null && !list.isEmpty()) {
DeviceSignal deviceSignal = list.get(0);
try {
if ("yx".equals(type)) {
// 遥信
deviceSignal.setYxValue(Integer.parseInt(value));
} else {
// 遥测
deviceSignal.setYcValue(Convert.toBigDecimal(value));
}
} catch (NumberFormatException e) {
// 处理转换异常例如返回false或抛出更具体的异常
return false;
}
deviceSignal.setLastmodifier("系统更新");
// 设置最后修改时间
Date parsedDate;
if (StrUtil.isEmpty(dateString) || "null".equals(dateString)) {
parsedDate = new Date();
} else {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
parsedDate = dateFormat.parse(dateString);
}
Timestamp timestamp = new Timestamp(parsedDate.getTime());
deviceSignal.setLastmodifydate(timestamp);
// 更新并检查结果
return this.updateById(deviceSignal);
}
return false;
}
}

View File

@ -0,0 +1,72 @@
package com.yfd.platform.modules.auxcontrol.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.yfd.platform.modules.auxcontrol.domain.DeviceWorkData;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceSignalMapper;
import com.yfd.platform.modules.auxcontrol.mapper.DeviceWorkDataMapper;
import com.yfd.platform.modules.auxcontrol.service.IDeviceWorkDataService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* <p>
* 变电站-设备-运行数据 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Service
public class DeviceWorkDataServiceImpl extends ServiceImpl<DeviceWorkDataMapper, DeviceWorkData> implements IDeviceWorkDataService {
@Resource
private DeviceSignalMapper deviceSignalMapper;
/**********************************
* 用途说明: 导出变电站设备运行数据
* 参数说明
* deviceWorkDatas 所需导出的变电站设备运行数据项集合
* 返回值说明: com.yfd.platform.config.ResponseResult 返回导出成功或者失败
***********************************/
@Override
public void insertData(String from, String slaveIp, String address, String value, String dateTimeString) {
DeviceWorkData workdata = new DeviceWorkData();
List<Map<String, Object>> list = new ArrayList<>();
if ("IEC104".equals(from)) {
list = deviceSignalMapper.selectDeviceSignal_Map(slaveIp, "yc", address);
} else if ("IEC61850".equals(from)) {
list = deviceSignalMapper.selectDeviceSignal_Map2("yc", address);
}
if (list.size() > 0) {
Map<String, Object> map = list.get(0);
workdata.setId(IdUtil.fastSimpleUUID());
workdata.setSystemcode(map.get("systemcode").toString());
workdata.setStationId(map.get("station_id").toString());
workdata.setDeviceId(map.get("device_id").toString());
workdata.setDeviceName(map.get("device_name").toString());
workdata.setSignalId(map.get("signal_id").toString());
workdata.setSignalName(map.get("signal_name").toString());
workdata.setUnit(ObjUtil.isNotEmpty(map.get("signal_unit")) ? map.get("signal_unit").toString() : "");
workdata.setValue(new BigDecimal(value));
if (StrUtil.isEmpty(dateTimeString) || "null".equals(dateTimeString)) {
workdata.setStartTime(LocalDateTime.now());
} else {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
LocalDateTime localDateTime = LocalDateTime.parse(dateTimeString, formatter);
workdata.setStartTime(localDateTime);
}
this.saveOrUpdate(workdata);
}
}
}

View File

@ -0,0 +1,38 @@
package com.yfd.platform.modules.auxcontrol.service.impl;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.yfd.platform.modules.auxcontrol.domain.GatewayDevice;
import com.yfd.platform.modules.auxcontrol.mapper.GatewayDeviceMapper;
import com.yfd.platform.modules.auxcontrol.service.IGatewayDeviceService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
/**
* <p>
* 变电站-通讯网关设备 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Service
public class GatewayDeviceServiceImpl extends ServiceImpl<GatewayDeviceMapper, GatewayDevice> implements IGatewayDeviceService {
/**********************************
* 用途说明: 根据IP,更新心跳保持时间
* 参数说明
* ip 终端对应IP
* 返回值说明:
***********************************/
@Override
public void updateKeepLiveTime(String ip) {
LambdaUpdateWrapper<GatewayDevice> queryWrapper = new LambdaUpdateWrapper<>();
queryWrapper.eq(GatewayDevice::getIpAddr, ip).set(GatewayDevice::getStatus, "01").set(GatewayDevice::getKeepliveTime, new Timestamp(System.currentTimeMillis()));
this.update(queryWrapper);
}
}

View File

@ -0,0 +1,20 @@
package com.yfd.platform.modules.auxcontrol.service.impl;
import com.yfd.platform.modules.auxcontrol.domain.MeterDevice;
import com.yfd.platform.modules.auxcontrol.mapper.MeterDeviceMapper;
import com.yfd.platform.modules.auxcontrol.service.IMeterDeviceService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 变电站二次设备中的智能仪表监控设备 服务实现类
* </p>
*
* @author zhengsl
* @since 2025-04-23
*/
@Service
public class MeterDeviceServiceImpl extends ServiceImpl<MeterDeviceMapper, MeterDevice> implements IMeterDeviceService {
}

View File

@ -63,7 +63,7 @@ public class CodeGenerator {
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://43.138.168.68:3306/riisdb500_zhuangzhou?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai");
dsc.setUrl("jdbc:mysql://43.138.168.68:3306/smartsubstationdb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("ylfw20230626@");
@ -171,7 +171,7 @@ public class CodeGenerator {
//rca_project,rca_projectanalysisrd,rca_projectinvestigaterd,rca_projectreport,rca_projectsummary,rca_projecttarget,rca_projecttrackrd,rca_analysisguide,rca_eventeffect,rca_failurecase,rca_failurecause,rca_failureclass,rca_failuremode
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("iis_");
strategy.setTablePrefix("fk_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.auxcontrol.mapper.DeviceAlarmParameterMapper">
</mapper>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.auxcontrol.mapper.DeviceAlarmRecordMapper">
</mapper>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.auxcontrol.mapper.DeviceSignalMapper">
<!-- 查询辅控设备信号表-->
<select id="selectDeviceSignalYx" resultType="com.yfd.platform.modules.auxcontrol.domain.DeviceSignal">
SELECT
b.*
FROM
fk_device a
JOIN fk_device_signal b ON ( a.device_id = b.device_id )
JOIN fk_system_device c ON ( a.netdevice_ip = c.ip_addr )
WHERE
c.ip_addr = #{slave_ip}
AND b.yx_addr = #{address}
</select>
<select id="selectDeviceSignalYc" resultType="com.yfd.platform.modules.auxcontrol.domain.DeviceSignal">
SELECT
b.*
FROM
fk_device a
JOIN fk_device_signal b ON ( a.device_id = b.device_id )
JOIN fk_system_device c ON ( a.netdevice_ip = c.ip_addr )
WHERE
c.ip_addr = #{slave_ip}
AND b.yc_addr = #{address}
</select>
<!-- 查询辅控设备信号表-->
<select id="selectDeviceSignal_Map" resultType="java.util.Map">
SELECT
a.station_id,
a.systemcode,
a.device_name,
b.*
FROM
fk_device a
JOIN fk_device_signal b ON ( a.device_id = b.device_id )
JOIN fk_system_device c ON ( a.netdevice_ip = c.ip_addr )
WHERE
c.ip_addr = #{slave_ip}
<if test="type=='yx'">
and b.yx_addr = #{address}
</if>
<if test="type=='yc'">
and b.yc_addr = #{address}
</if>
</select>
<!-- 查询辅控设备信号表-->
<select id="selectDeviceSignal_Map2" resultType="java.util.Map">
SELECT
a.station_id,
a.systemcode,
a.device_name,
b.*
FROM
fk_device a
JOIN fk_device_signal b ON ( a.device_id = b.device_id )
WHERE
1 =1
<if test="type=='yx'">
and b.yx_addr = #{address}
</if>
<if test="type=='yc'">
and b.yc_addr = #{address}
</if>
</select>
</mapper>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.auxcontrol.mapper.DeviceWorkDataMapper">
</mapper>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.auxcontrol.mapper.GatewayDeviceMapper">
</mapper>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yfd.platform.modules.auxcontrol.mapper.MeterDeviceMapper">
</mapper>