package com.tsi.comm.server.protocol; import com.tsi.comm.server.dto.TsiCvimAbnormal; import com.tsi.comm.server.dto.TsiCvimControl; import com.tsi.comm.server.dto.TsiCvimStatus; import com.tsi.comm.server.protocol.enums.eLightsStatus; import com.tsi.comm.server.protocol.enums.eLightsType; import com.tsi.comm.server.protocol.enums.eOpCode; import com.tsi.comm.server.protocol.enums.eTimeReliability; import com.tsi.comm.server.xnet.NettyUtils; import com.tsi.comm.server.dto.mongodb.TcsNodeStatus; import com.tsi.comm.server.repository.TsiNodeAddManager; import com.tsi.comm.server.vo.TsiNodeAddDetailVo; import com.tsi.comm.server.vo.TsiNodeAddVo; import com.tsi.comm.server.vo.TsiNodeVo; import com.tsi.common.utils.ByteUtils; import com.tsi.common.utils.CRC16Utils; import io.netty.channel.Channel; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.text.SimpleDateFormat; import java.util.*; import static com.tsi.comm.server.protocol.TsiCvibProtocolSpec.*; @Slf4j @Getter @Setter public class TsiCpuPacket extends AbstractTsiPacket { protected static TsiNodeAddManager nodeAddManager; private Object obj; private int length; private byte dataVer; private int count; protected byte[] cvimData; // for cvim-raw topic protected byte[] nodeData; // for node topic protected List addNodes; public static void setNodeAddManager(TsiNodeAddManager nodeAddManager) { TsiCpuPacket.nodeAddManager = nodeAddManager; } public TsiCpuPacket(long nodeId, long msec, long nsec, Channel channel) { super(nodeId, msec, nsec, NettyUtils.getRemoteIpAddressToLong(channel), NettyUtils.getRemotePort(channel)); setOpCode(eOpCode.TSI_CPU_SIGNAL_NOTIFY.getValue()); } public TsiCpuPacket(long nodeId, long msec, long nsec, long remoteIpAddressToLong, int remotePort) { super(nodeId, msec, nsec, remoteIpAddressToLong, remotePort); } // FOR CVIM packet // public TsiCpuPacket(long nodeId, byte[] value) { // super(nodeId, TimeUtils.currentTimeSeconds(), System.nanoTime(), 0, 0); // this.buf = value; // CVIM Header 를 포함한 내부 IPC 형식 데이터임 // int nodeLength = this.buf.length - TsiCpuPacket.SIZE_IPC_SIZE; // this.cvimData = new byte[TsiCpuPacket.SIZE_IPC_SIZE]; // System.arraycopy(this.buf, 0, this.cvimData, 0, TsiCpuPacket.SIZE_IPC_SIZE); // if (nodeLength > 0) { // this.nodeData = new byte[nodeLength]; // System.arraycopy(this.buf, TsiCpuPacket.SIZE_IPC_SIZE, this.nodeData, 0, nodeLength); // } // } protected byte getStx1() { if (this.buf != null) return this.buf[INDEX_STX1]; return 0x00; } protected byte getStx2() { if (this.buf != null) return this.buf[INDEX_STX2]; return 0x00; } public byte[] getTestData() { return this.buf; } /* * Make cvim-raw packet */ protected void makeCvimPacket() { final int packetLength = (this.buf == null) ? 0 : this.buf.length; this.cvimData = new byte[SIZE_IPC_SIZE + packetLength]; // cvim-raw header System.arraycopy(this.timespec.bytes(), 0, this.cvimData, POS_IPC_TIMESPEC, SIZE_TIMESPEC); ByteUtils.setUnsignedInt(this.cvimData, POS_IPC_IPADDR, getRemoteIp()); ByteUtils.setUnsignedShort(this.cvimData, POS_IPC_PORT, getRemotePort()); this.cvimData[POS_IPC_CONNECT] = (opCode == (byte)eOpCode.TSI_CPU_SIGNAL_NOTIFY.getValue()) ? CONNECT : DISCONNECT; ByteUtils.setUnsignedInt(this.cvimData, POS_IPC_NODEID, this.nodeId); // cvim-raw body if (packetLength > 0) { System.arraycopy(this.buf, 0, this.cvimData, POS_IPC_PACKET, packetLength); } //log.error("CVIM: {}", HexString.fromBytes(this.cvimData)); } /* * Make cvim-raw packet */ protected void makeAddNodeCvimPaket(TsiCpuPacket cpuPacket, byte[] packet) { final int packetLength = (packet == null) ? 0 : packet.length; final int headSize = SIZE_IPC_SIZE + 6; cpuPacket.cvimData = new byte[headSize + packetLength + 2]; // cpu packet 6 byte(nodeid 제외), crc 2 byte // cvim-raw header System.arraycopy(timespec.bytes(), 0, cpuPacket.cvimData, POS_IPC_TIMESPEC, SIZE_TIMESPEC); ByteUtils.setUnsignedInt(cpuPacket.cvimData, POS_IPC_IPADDR, getRemoteIp()); ByteUtils.setUnsignedShort(cpuPacket.cvimData, POS_IPC_PORT, getRemotePort()); cpuPacket.cvimData[POS_IPC_CONNECT] = (opCode == (byte)eOpCode.TSI_CPU_SIGNAL_NOTIFY.getValue()) ? CONNECT : DISCONNECT; ByteUtils.setUnsignedInt(cpuPacket.cvimData, POS_IPC_NODEID, cpuPacket.nodeId); cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_STX1] = getStx1(); cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_STX2] = getStx2(); ByteUtils.setUnsignedShort(cpuPacket.cvimData, SIZE_IPC_SIZE+INDEX_LENGTH, packetLength+2); cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_OPCODE] = (byte)getOpCode(); cpuPacket.cvimData[SIZE_IPC_SIZE+INDEX_VERSION] = getDataVer(); // cvim-raw body if (packetLength > 0) { System.arraycopy(packet, 0, cpuPacket.cvimData, headSize, packetLength); } // 체크섬 계산하지 않음 } protected int checkPacket(TsiNodeVo obj, boolean checkCrc) { // 0 단계. STX1, STX2 체크 if (this.buf[INDEX_STX1] != STX1 || this.buf[INDEX_STX2] != STX2) { log.error("parsing: errno(-1), NodeId: {}, stx1: {}, stx2: {}", this.nodeId, this.buf[INDEX_STX1], this.buf[INDEX_STX2]); return -1; } // 1 단계. 패킷 길이 체크 // TODO: 20240722, Protocol R28 ADDED // int reqLength = SIZE_PACKET_DATA + (SIZE_STATUS_DATA * this.count); // if (this.buf[INDEX_VERSION] == PROTOCOL_R28) { // reqLength = SIZE_PACKET_DATA + (SIZE_STATUS_DATA * this.count) + 1; // ADD CPU byte packet // } // if (this.length != reqLength ) { // return false; // } final int reqLength = SIZE_PACKET_DATA + (SIZE_STATUS_DATA * this.count); if (this.length < reqLength ) { log.error("parsing: errno(-2), NodeId: {}, reqLength: {}, recvLength: {}", this.nodeId, reqLength, this.length); return -2; } // 2단계. 체크섬 final int receivedCrc = ByteUtils.getUnsignedShort(this.buf, this.buf.length-2); final int calculatedCrc = CRC16Utils.CRC16_ccitt_cvim(this.buf, INDEX_LENGTH, this.length-2); // 시작인덱스가 있으므로 전체길이로 계산 if (receivedCrc != calculatedCrc) { if (checkCrc || (obj != null && obj.isDump())) { log.error("parsing: errno(-3), NodeId: {}, crc(recv/calc): {}/{}", this.nodeId, receivedCrc, calculatedCrc); } return -3; } return 0; } public int parsing(TsiNodeVo obj, boolean isCheckPacket) { if (this.buf == null || this.buf.length < INDEX_STATUS_HDR+3) { log.error("parsing: errno(-4), NodeId: {}, buf==null: {}, length: {}", this.nodeId, this.buf == null, this.buf == null ? 0 : this.buf.length); return -4; } this.opCode = this.buf[INDEX_OPCODE]; this.dataVer = this.buf[INDEX_VERSION]; this.length = ByteUtils.getUnsignedShort(this.buf, INDEX_LENGTH); this.count = (int)(this.buf[INDEX_STATUS_HDR+3] & 0x7F); obj.setSigCount(this.count); // 신호현시 갯수 저장 // CVIM 데이터 및 TEST 데이터가 생성됨 makeCvimPacket(); int result = checkPacket(obj, isCheckPacket); if (0 != result) { if (-3 == result) { obj.setCrcError(true); } if (isCheckPacket) { // 20250425: CRC 체크여부에 따라 바로 리턴(기본값은 체크여부가 true 임) return result; } } TsiNodeAddVo tsiNodeAddVo = TsiCpuPacket.nodeAddManager.get(this.nodeId); if (tsiNodeAddVo == null) { // 연등지 정보가 없는 경우 // R25 인 경우 마지막 CPU 제조사 코드를 복사하지 않도록 수정 int addLength = this.buf.length-SIZE_NODE_DUMMY; final int r25Length = SIZE_NODE_HEAD + (this.count * SIZE_STATUS_DATA); if (addLength > r25Length) { addLength = r25Length; } this.nodeData = new byte[addLength]; System.arraycopy(this.buf, INDEX_NODE_ID, this.nodeData, 0, addLength); return 0; } // 연등지 교차로 데이터 파싱 byte[] head = new byte[SIZE_NODE_HEAD]; List nodeStatus = new ArrayList<>(); Map> addStatus = new HashMap<>(); System.arraycopy(this.buf, INDEX_NODE_ID, head, 0, SIZE_NODE_HEAD); int loop = 0; for (int ii = INDEX_STATUS_DATA; loop < this.count; ii += SIZE_STATUS_DATA, loop++) { byte[] status = new byte[SIZE_STATUS_DATA]; System.arraycopy(this.buf, ii, status, 0, SIZE_STATUS_DATA); int dirAdd = status[INDEX_STATUS_DIR_ADD] & 0x0F; if (dirAdd == 0) { // 원천 노드 정보 nodeStatus.add(status); } else { // 추가 노드 정보 int originDirCode = status[INDEX_STATUS_DIRECTION]; // 원천방향코드 int direction = originDirCode / 10; // 인천공항 노드 확인용 int addNodeIdx = originDirCode % 10; // 인천공항 노드 확인용 if (addNodeIdx > 4) { // 인천공항 연등지 인 경우 2개 이상이기 때문에 연등지1 = 15, 연등지2=16, .... // 즉, 일의 자리 5,6,7,8,9로 설정하고 1,2,3,4,5로 된다. 십의자리는 *10을 해서 원천방향코드가 된다. dirAdd = addNodeIdx - 4; // 인천공항이 아닌경우는 위에서 구한 dirAdd 을 그대로 사용한다(1,2 만 가능) originDirCode = direction * 10; // 15,16,17,18,19=10, 25,26,27,28,29=20, .... } status[INDEX_STATUS_DIR_ADD] = (byte) (status[INDEX_STATUS_DIR_ADD] & 0xF0); // 방향추가정보를 0 으로 초기화 TsiNodeAddDetailVo detailVo = tsiNodeAddVo.getAddNodeMap().get(originDirCode * 1000 + dirAdd); if (detailVo != null && detailVo.getAddDirCode() != null) { for (int dirIdx = 0; dirIdx < detailVo.getAddDirCode().length; dirIdx++) { // if (detailVo.getAddDirCode()[dirIdx] == (byte)0x00) { // continue; // } // 연등지 방향코드를 배열에 가지고 있기 때문에 List list = addStatus.get(detailVo.getNodeId()); if (list == null) { list = new ArrayList<>(); addStatus.put(detailVo.getNodeId(), list); } if (dirIdx == 0) { status[INDEX_STATUS_DIRECTION] = detailVo.getAddDirCode()[dirIdx]; list.add(status); } else { byte[] status2 = new byte[SIZE_STATUS_DATA]; System.arraycopy(status, 0, status2, 0, SIZE_STATUS_DATA); status2[INDEX_STATUS_DIRECTION] = detailVo.getAddDirCode()[dirIdx]; list.add(status2); } } } } } byte splitFlag = 0x01; int statusCount = nodeStatus.size(); this.nodeData = new byte[SIZE_NODE_HEAD + (statusCount*SIZE_STATUS_DATA)]; System.arraycopy(head, 0, this.nodeData, 0, SIZE_NODE_HEAD); this.nodeData[POS_NODE_HEAD_COUNT] = (byte) (statusCount | (splitFlag << 7)); for (int ii = 0; ii < statusCount; ii++) { System.arraycopy(nodeStatus.get(ii), 0, this.nodeData, SIZE_NODE_HEAD+(ii*SIZE_STATUS_DATA), SIZE_STATUS_DATA); } makeAddNodeCvimPaket(this, this.nodeData); if (addStatus.isEmpty()) { // 연등지 정보가 설정된게 없을까??? return 0; } // 연등지 노드 카프카 패킷 생성 this.addNodes = new ArrayList<>(); for (Map.Entry> entry: addStatus.entrySet()) { statusCount = entry.getValue().size(); TsiCpuAddPacket addPacket = new TsiCpuAddPacket(entry.getKey(), this.timespec, this.remoteIp, this.remotePort); addPacket.setObj(TsiCpuPacket.nodeAddManager.get(entry.getKey())); addPacket.setNodeData(new byte[SIZE_NODE_HEAD + (statusCount*SIZE_STATUS_DATA)]); System.arraycopy(head, 0, addPacket.getNodeData(), 0, SIZE_NODE_HEAD); ByteUtils.setUnsignedInt(addPacket.getNodeData(), POS_NODE_HEAD_NODEID, entry.getKey()); addPacket.getNodeData()[POS_NODE_HEAD_COUNT] = (byte) (statusCount | (splitFlag << 7)); for (int ii = 0; ii < statusCount; ii++) { System.arraycopy(entry.getValue().get(ii), 0, addPacket.getNodeData(), SIZE_NODE_HEAD+(ii*SIZE_STATUS_DATA), SIZE_STATUS_DATA); } makeAddNodeCvimPaket(addPacket, addPacket.getNodeData()); this.addNodes.add(addPacket); } /* log.error(" RAW: {}, {}", this.nodeId, HexString.fromBytes(getBuf())); log.error("NODE: {}, {}", this.nodeId, HexString.fromBytes(getNodeData())); log.error("CVIM: {}, {}", this.nodeId, HexString.fromBytes(getCvimData())); for (int ii = 0; ii < addNodes.size(); ii++) { log.error("NODE: {}, {}", addNodes.get(ii).nodeId, HexString.fromBytes(addNodes.get(ii).getNodeData())); log.error("CVIM: {}, {}", addNodes.get(ii).nodeId, HexString.fromBytes(addNodes.get(ii).getCvimData())); }*/ return 0; } public TcsNodeStatus getNodeStatus() { int nodeLength = this.buf.length - SIZE_IPC_SIZE; byte[] msec = new byte[8]; byte[] nsec = new byte[8]; System.arraycopy(this.cvimData, 0, msec, 0, 8); System.arraycopy(this.cvimData, 8, nsec, 0, 8); ByteBuffer byteMSec = ByteBuffer.wrap(msec); ByteBuffer byteNSec = ByteBuffer.wrap(nsec); byteMSec.order(ByteOrder.LITTLE_ENDIAN); byteNSec.order(ByteOrder.LITTLE_ENDIAN); long milliSeconds = byteMSec.getLong(); long nanoSeconds = byteNSec.getLong(); this.remoteIp = ByteUtils.getUnsignedInt(this.cvimData, POS_IPC_IPADDR); this.remotePort = ByteUtils.getUnsignedShort(this.cvimData, POS_IPC_PORT); ///// getTimespec().tv_nsec(nanoSeconds); // recv tcp, Date date = new Date(milliSeconds * 1000L); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9")); String collectTime = sdf.format(date); if (this.cvimData[POS_IPC_CONNECT] == DISCONNECT) { return TcsNodeStatus.builder() ._id(this.nodeId) .nodeId(this.nodeId) .isConnect(false) .collectTime(collectTime) .build(); } if (nodeLength < SIZE_HEAD + SIZE_STATUS_HDR) { // packet error return null; } String tscDateTime; int cycleElapsedTime; TsiCvimControl tscControlInfo = new TsiCvimControl(); TsiCvimAbnormal tscAbnormalInfo = new TsiCvimAbnormal(); int signalStatusInfoCount; int divFlag; long localTime; List signalStatusInfos = new ArrayList<>(); //this.opCode = this.nodeData[TsiCpuPacket.INDEX_OPCODE]; //byte dataVer = this.nodeData[TsiCpuPacket.INDEX_VERSION]; //int length = ByteUtils.getUnsignedShort(this.nodeData, TsiCpuPacket.INDEX_LENGTH); //int count = (int)(this.nodeData[TsiCpuPacket.INDEX_STATUS_HDR+3] & 0x7F); byte control = this.cvimData[SIZE_IPC_SIZE + INDEX_STATUS_HDR]; tscControlInfo.inManualControl = ((control ) & 0x01) == 0x01; //수동; tscControlInfo.inFlashingControl = ((control >> 1) & 0x01) == 0x01; //점멸; tscControlInfo.inLightsOutControl = ((control >> 2) & 0x01) == 0x01; //소등 tscControlInfo.inActuationControl = ((control >> 3) & 0x01) == 0x01; //감응; tscControlInfo.inTransitionControl = ((control >> 4) & 0x01) == 0x01; //전이; byte abnormal = this.cvimData[SIZE_IPC_SIZE + INDEX_STATUS_HDR + 1]; tscAbnormalInfo.inSignalConflict = ((abnormal ) & 0x01) == 0x01; //모순상태; tscAbnormalInfo.inCenterComm = ((abnormal >> 1) & 0x01) == 0x01; //센터상태; tscAbnormalInfo.inScuComm = ((abnormal >> 2) & 0x01) == 0x01; //SCU 상태 cycleElapsedTime = (int)(this.cvimData[SIZE_IPC_SIZE + INDEX_STATUS_HDR + 2] & 0xFF); byte stts = this.cvimData[SIZE_IPC_SIZE + INDEX_STATUS_HDR + 3];; signalStatusInfoCount = (int)(stts & 0x7F); //divFlag = (int)((stts >> 7) & 0x01); localTime = ByteUtils.getUnsignedInt(this.cvimData, SIZE_IPC_SIZE + INDEX_STATUS_HDR + 4); int ii = SIZE_IPC_SIZE + INDEX_STATUS_DATA; for (int idx = 0; idx < signalStatusInfoCount; idx++) { TsiCvimStatus status = new TsiCvimStatus(); //int dirAdd = (buffer.get(ii)) & 0x0F; //연등지 int lightsType = (int)((this.cvimData[ii ] >> 4) & 0x0F); int lightsStatus = (int)((this.cvimData[ii+1] ) & 0x07); int reliability = (int)((this.cvimData[ii+1] >> 7) & 0x01); boolean readyPedestrian = ((this.cvimData[ii+1] >> 6) & 0x01) == 0x01; boolean unProtected = ((this.cvimData[ii+1] >> 3) & 0x01) == 0x01; int totalSeconds = (int)(this.cvimData[ii+2] & 0xFF); int remainSeconds = (int)(this.cvimData[ii+3] & 0xFF); int directionCode = (int)(this.cvimData[ii+4] & 0xFF); status.setLightsType(eLightsType.getByValue(lightsType)); // 신호등정보 [직진,좌,보행] status.setLightsStatus(eLightsStatus.getByValue(lightsStatus)); //신호등상태 status.setTimeReliability(eTimeReliability.getByValue(reliability)); //시간정보신뢰성 status.setReadyPedestrianSignal(readyPedestrian); //보행자 status.setUnProtectedSignal(unProtected); //비보호 상태 status.setTotalSeconds(totalSeconds); //표출시간 status.setRemainingSeconds(remainSeconds); //잔여시간 status.setDirectionCode(directionCode); //방향코드 signalStatusInfos.add(status); ii += SIZE_STATUS_DATA; } date = new Date(localTime * 1000L); sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT+9")); tscDateTime = sdf.format(date); //log.error("Packet Received: NodeId: {}, {}", this.nodeId, TimeUtils.elapsedTimeStr(System.nanoTime()-nanoSeconds)); return TcsNodeStatus.builder() ._id(this.nodeId) .nodeId(this.nodeId) .isConnect(true) .collectTime(collectTime) .tscDateTime(tscDateTime) .cycleElapsedTime(cycleElapsedTime) .tscControlInfo(tscControlInfo) .tscAbnormalInfo(tscAbnormalInfo) .signalStatusInfoCount(signalStatusInfoCount) .signalStatusInfos(signalStatusInfos) .build(); } }